Skip to content

Commit f3b450e

Browse files
committed
feat(zetaclient): add generic rpc metrics (#2597)
* feat(zetaclient): add generic rpc metrics * feedback * changelog * fmt
1 parent 2d1baa7 commit f3b450e

File tree

5 files changed

+116
-9
lines changed

5 files changed

+116
-9
lines changed

changelog.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* [2524](https://github.com/zeta-chain/node/pull/2524) - add inscription envolop parsing
66
* [2533](https://github.com/zeta-chain/node/pull/2533) - parse memo from both OP_RETURN and inscription
77
* [2568](https://github.com/zeta-chain/node/pull/2568) - improve AppContext by converging chains, chainParams, enabledChains, and additionalChains into a single zctx.Chain
8+
* [2597](https://github.com/zeta-chain/node/pull/2597) - Add generic rpc metrics to zetaclient
89

910

1011
## v19.0.1

zetaclient/chains/evm/signer/signer.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
ethtypes "github.com/ethereum/go-ethereum/core/types"
1818
"github.com/ethereum/go-ethereum/crypto"
1919
"github.com/ethereum/go-ethereum/ethclient"
20+
ethrpc "github.com/ethereum/go-ethereum/rpc"
2021
"github.com/rs/zerolog"
2122
"github.com/rs/zerolog/log"
2223
"github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol"
@@ -866,11 +867,16 @@ func getEVMRPC(ctx context.Context, endpoint string) (interfaces.EVMRPCClient, e
866867
client := &mocks.MockEvmClient{}
867868
return client, ethSigner, nil
868869
}
870+
httpClient, err := metrics.GetInstrumentedHTTPClient(endpoint)
871+
if err != nil {
872+
return nil, nil, errors.Wrap(err, "unable to get instrumented HTTP client")
873+
}
869874

870-
client, err := ethclient.Dial(endpoint)
875+
rpcClient, err := ethrpc.DialHTTPWithClient(endpoint, httpClient)
871876
if err != nil {
872877
return nil, nil, errors.Wrapf(err, "unable to dial EVM client (endpoint %q)", endpoint)
873878
}
879+
client := ethclient.NewClient(rpcClient)
874880

875881
chainID, err := client.ChainID(ctx)
876882
if err != nil {

zetaclient/metrics/metrics.go

+57
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package metrics
44
import (
55
"context"
66
"net/http"
7+
"net/url"
78
"time"
89

910
"github.com/prometheus/client_golang/prometheus"
@@ -112,6 +113,34 @@ var (
112113
Help: "Histogram of the TSS keysign latency",
113114
Buckets: []float64{1, 7, 15, 30, 60, 120, 240},
114115
}, []string{"result"})
116+
117+
// RPCInProgress is a gauge that contains the number of RPCs requests in progress
118+
RPCInProgress = promauto.NewGaugeVec(prometheus.GaugeOpts{
119+
Namespace: ZetaClientNamespace,
120+
Name: "rpc_in_progress",
121+
Help: "Number of RPC requests in progress",
122+
}, []string{"host"})
123+
124+
// RPCCount is a counter that contains the number of total RPC requests
125+
RPCCount = promauto.NewCounterVec(
126+
prometheus.CounterOpts{
127+
Namespace: ZetaClientNamespace,
128+
Name: "rpc_count",
129+
Help: "A counter for number of total RPC requests",
130+
},
131+
[]string{"host", "code"},
132+
)
133+
134+
// RPCLatency is a histogram of the RPC latency
135+
RPCLatency = promauto.NewHistogramVec(
136+
prometheus.HistogramOpts{
137+
Namespace: ZetaClientNamespace,
138+
Name: "rpc_duration_seconds",
139+
Help: "A histogram of the RPC duration in seconds",
140+
Buckets: prometheus.DefBuckets,
141+
},
142+
[]string{"host"},
143+
)
115144
)
116145

117146
// NewMetrics creates a new Metrics instance
@@ -151,3 +180,31 @@ func (m *Metrics) Stop() error {
151180
defer cancel()
152181
return m.s.Shutdown(ctx)
153182
}
183+
184+
// GetInstrumentedHTTPClient sets up a http client that emits prometheus metrics
185+
func GetInstrumentedHTTPClient(endpoint string) (*http.Client, error) {
186+
host := endpoint
187+
// try to parse as url (so that we do not expose auth uuid in metrics)
188+
endpointURL, err := url.Parse(endpoint)
189+
if err == nil {
190+
host = endpointURL.Host
191+
}
192+
labels := prometheus.Labels{"host": host}
193+
rpcCounterMetric, err := RPCCount.CurryWith(labels)
194+
if err != nil {
195+
return nil, err
196+
}
197+
rpcLatencyMetric, err := RPCLatency.CurryWith(labels)
198+
if err != nil {
199+
return nil, err
200+
}
201+
202+
transport := http.DefaultTransport
203+
transport = promhttp.InstrumentRoundTripperDuration(rpcLatencyMetric, transport)
204+
transport = promhttp.InstrumentRoundTripperCounter(rpcCounterMetric, transport)
205+
transport = promhttp.InstrumentRoundTripperInFlight(RPCInProgress.With(labels), transport)
206+
207+
return &http.Client{
208+
Transport: transport,
209+
}, nil
210+
}

zetaclient/metrics/metrics_test.go

+43-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package metrics
22

33
import (
4+
"fmt"
5+
"io"
46
"net/http"
7+
"strings"
58
"testing"
69
"time"
710

11+
"github.com/prometheus/client_golang/prometheus"
12+
"github.com/prometheus/client_golang/prometheus/testutil"
813
. "gopkg.in/check.v1"
914
)
1015

@@ -23,20 +28,52 @@ func (ms *MetricsSuite) SetUpSuite(c *C) {
2328
ms.m = m
2429
}
2530

31+
// assert that the curried metric actually uses the same underlying storage
32+
func (ms *MetricsSuite) TestCurryWith(c *C) {
33+
rpcTotalsC := RPCCount.MustCurryWith(prometheus.Labels{"host": "test"})
34+
rpcTotalsC.With(prometheus.Labels{"code": "400"}).Add(1.0)
35+
36+
rpcCtr := testutil.ToFloat64(RPCCount.With(prometheus.Labels{"host": "test", "code": "400"}))
37+
c.Assert(rpcCtr, Equals, 1.0)
38+
39+
RPCCount.Reset()
40+
}
41+
2642
func (ms *MetricsSuite) TestMetrics(c *C) {
2743
GetFilterLogsPerChain.WithLabelValues("chain1").Inc()
2844
GetFilterLogsPerChain.WithLabelValues("chain2").Inc()
2945
GetFilterLogsPerChain.WithLabelValues("chain2").Inc()
3046
time.Sleep(1 * time.Second)
31-
res, err := http.Get("http://127.0.0.1:8886/metrics")
47+
48+
chain1Ctr := testutil.ToFloat64(GetFilterLogsPerChain.WithLabelValues("chain1"))
49+
c.Assert(chain1Ctr, Equals, 1.0)
50+
51+
httpClient, err := GetInstrumentedHTTPClient("http://127.0.0.1:8886/myauthuuid")
3252
c.Assert(err, IsNil)
33-
c.Assert(res.StatusCode, Equals, http.StatusOK)
34-
defer res.Body.Close()
35-
//out, err := ioutil.ReadAll(res.Body)
36-
//fmt.Println(string(out))
3753

38-
res, err = http.Get("http://127.0.0.1:8886")
54+
res, err := httpClient.Get("http://127.0.0.1:8886")
3955
c.Assert(err, IsNil)
56+
defer res.Body.Close()
4057
c.Assert(res.StatusCode, Equals, http.StatusOK)
58+
59+
res, err = httpClient.Get("http://127.0.0.1:8886/metrics")
60+
c.Assert(err, IsNil)
4161
defer res.Body.Close()
62+
c.Assert(res.StatusCode, Equals, http.StatusOK)
63+
body, err := io.ReadAll(res.Body)
64+
c.Assert(err, IsNil)
65+
metricsBody := string(body)
66+
c.Assert(strings.Contains(metricsBody, fmt.Sprintf("%s_%s", ZetaClientNamespace, "rpc_count")), Equals, true)
67+
68+
// assert that rpc count is being incremented at all
69+
rpcCount := testutil.ToFloat64(RPCCount)
70+
c.Assert(rpcCount, Equals, 2.0)
71+
72+
// assert that rpc count is being incremented correctly
73+
rpcCount = testutil.ToFloat64(RPCCount.With(prometheus.Labels{"host": "127.0.0.1:8886", "code": "200"}))
74+
c.Assert(rpcCount, Equals, 2.0)
75+
76+
// assert that rpc count is not being incremented incorrectly
77+
rpcCount = testutil.ToFloat64(RPCCount.With(prometheus.Labels{"host": "127.0.0.1:8886", "code": "502"}))
78+
c.Assert(rpcCount, Equals, 0.0)
4279
}

zetaclient/orchestrator/bootstrap.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
ethcommon "github.com/ethereum/go-ethereum/common"
77
"github.com/ethereum/go-ethereum/ethclient"
8+
ethrpc "github.com/ethereum/go-ethereum/rpc"
89
solrpc "github.com/gagliardetto/solana-go/rpc"
910
"github.com/pkg/errors"
1011

@@ -279,12 +280,17 @@ func syncObserverMap(
279280
continue
280281
}
281282

282-
// create EVM client
283-
evmClient, err := ethclient.DialContext(ctx, cfg.Endpoint)
283+
httpClient, err := metrics.GetInstrumentedHTTPClient(cfg.Endpoint)
284+
if err != nil {
285+
logger.Std.Error().Err(err).Str("rpc.endpoint", cfg.Endpoint).Msgf("Unable to create HTTP client")
286+
continue
287+
}
288+
rpcClient, err := ethrpc.DialHTTPWithClient(cfg.Endpoint, httpClient)
284289
if err != nil {
285290
logger.Std.Error().Err(err).Str("rpc.endpoint", cfg.Endpoint).Msgf("Unable to dial EVM RPC")
286291
continue
287292
}
293+
evmClient := ethclient.NewClient(rpcClient)
288294

289295
database, err := db.NewFromSqlite(dbpath, chainName, true)
290296
if err != nil {

0 commit comments

Comments
 (0)