Commit a762fa74 authored by Joshua Gutow's avatar Joshua Gutow Committed by GitHub

Merge pull request #7482 from seungjulee/issue-7417-standard-admin-rpc

op-service: create a common admin API RPC and a common RPC request metrics
parents b25bb66d 1bc388ec
...@@ -5,7 +5,6 @@ import ( ...@@ -5,7 +5,6 @@ import (
"fmt" "fmt"
_ "net/http/pprof" _ "net/http/pprof"
gethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-batcher/flags" "github.com/ethereum-optimism/optimism/op-batcher/flags"
...@@ -27,11 +26,15 @@ func Main(version string, cliCtx *cli.Context) error { ...@@ -27,11 +26,15 @@ func Main(version string, cliCtx *cli.Context) error {
return err return err
} }
cfg := NewConfig(cliCtx) cfg := NewConfig(cliCtx)
if err := cfg.Check(); err != nil {
return fmt.Errorf("invalid CLI flags: %w", err)
}
l := oplog.NewLogger(oplog.AppOut(cliCtx), cfg.LogConfig) l := oplog.NewLogger(oplog.AppOut(cliCtx), cfg.LogConfig)
oplog.SetGlobalLogHandler(l.GetHandler()) oplog.SetGlobalLogHandler(l.GetHandler())
opservice.ValidateEnvVars(flags.EnvVarPrefix, flags.Flags, l) opservice.ValidateEnvVars(flags.EnvVarPrefix, flags.Flags, l)
m := metrics.NewMetrics("default") procName := "default"
m := metrics.NewMetrics(procName)
l.Info("Initializing Batch Submitter") l.Info("Initializing Batch Submitter")
batchSubmitter, err := NewBatchSubmitterFromCLIConfig(cfg, l, m) batchSubmitter, err := NewBatchSubmitterFromCLIConfig(cfg, l, m)
...@@ -72,18 +75,15 @@ func Main(version string, cliCtx *cli.Context) error { ...@@ -72,18 +75,15 @@ func Main(version string, cliCtx *cli.Context) error {
m.StartBalanceMetrics(ctx, l, batchSubmitter.L1Client, batchSubmitter.TxManager.From()) m.StartBalanceMetrics(ctx, l, batchSubmitter.L1Client, batchSubmitter.TxManager.From())
} }
rpcCfg := cfg.RPCConfig
server := oprpc.NewServer( server := oprpc.NewServer(
rpcCfg.ListenAddr, cfg.RPCFlag.ListenAddr,
rpcCfg.ListenPort, cfg.RPCFlag.ListenPort,
version, version,
oprpc.WithLogger(l), oprpc.WithLogger(l),
) )
if rpcCfg.EnableAdmin { if cfg.RPCFlag.EnableAdmin {
server.AddAPI(gethrpc.API{ adminAPI := rpc.NewAdminAPI(batchSubmitter, &m.RPCMetrics, l)
Namespace: "admin", server.AddAPI(rpc.GetAdminAPI(adminAPI))
Service: rpc.NewAdminAPI(batchSubmitter),
})
l.Info("Admin RPC enabled") l.Info("Admin RPC enabled")
} }
if err := server.Start(); err != nil { if err := server.Start(); err != nil {
......
...@@ -10,12 +10,12 @@ import ( ...@@ -10,12 +10,12 @@ import (
"github.com/ethereum-optimism/optimism/op-batcher/compressor" "github.com/ethereum-optimism/optimism/op-batcher/compressor"
"github.com/ethereum-optimism/optimism/op-batcher/flags" "github.com/ethereum-optimism/optimism/op-batcher/flags"
"github.com/ethereum-optimism/optimism/op-batcher/metrics" "github.com/ethereum-optimism/optimism/op-batcher/metrics"
"github.com/ethereum-optimism/optimism/op-batcher/rpc"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-node/sources"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof" oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
) )
...@@ -88,17 +88,16 @@ type CLIConfig struct { ...@@ -88,17 +88,16 @@ type CLIConfig struct {
Stopped bool Stopped bool
TxMgrConfig txmgr.CLIConfig TxMgrConfig txmgr.CLIConfig
RPCConfig rpc.CLIConfig
LogConfig oplog.CLIConfig LogConfig oplog.CLIConfig
MetricsConfig opmetrics.CLIConfig MetricsConfig opmetrics.CLIConfig
PprofConfig oppprof.CLIConfig PprofConfig oppprof.CLIConfig
CompressorConfig compressor.CLIConfig CompressorConfig compressor.CLIConfig
RPCFlag oprpc.CLIConfig
} }
func (c CLIConfig) Check() error { func (c CLIConfig) Check() error {
if err := c.RPCConfig.Check(); err != nil { // TODO: check the sanity of flags loaded directly https://github.com/ethereum-optimism/optimism/issues/7512
return err
}
if err := c.MetricsConfig.Check(); err != nil { if err := c.MetricsConfig.Check(); err != nil {
return err return err
} }
...@@ -108,6 +107,9 @@ func (c CLIConfig) Check() error { ...@@ -108,6 +107,9 @@ func (c CLIConfig) Check() error {
if err := c.TxMgrConfig.Check(); err != nil { if err := c.TxMgrConfig.Check(); err != nil {
return err return err
} }
if err := c.RPCFlag.Check(); err != nil {
return err
}
return nil return nil
} }
...@@ -127,10 +129,10 @@ func NewConfig(ctx *cli.Context) CLIConfig { ...@@ -127,10 +129,10 @@ func NewConfig(ctx *cli.Context) CLIConfig {
MaxL1TxSize: ctx.Uint64(flags.MaxL1TxSizeBytesFlag.Name), MaxL1TxSize: ctx.Uint64(flags.MaxL1TxSizeBytesFlag.Name),
Stopped: ctx.Bool(flags.StoppedFlag.Name), Stopped: ctx.Bool(flags.StoppedFlag.Name),
TxMgrConfig: txmgr.ReadCLIConfig(ctx), TxMgrConfig: txmgr.ReadCLIConfig(ctx),
RPCConfig: rpc.ReadCLIConfig(ctx),
LogConfig: oplog.ReadCLIConfig(ctx), LogConfig: oplog.ReadCLIConfig(ctx),
MetricsConfig: opmetrics.ReadCLIConfig(ctx), MetricsConfig: opmetrics.ReadCLIConfig(ctx),
PprofConfig: oppprof.ReadCLIConfig(ctx), PprofConfig: oppprof.ReadCLIConfig(ctx),
CompressorConfig: compressor.ReadCLIConfig(ctx), CompressorConfig: compressor.ReadCLIConfig(ctx),
RPCFlag: oprpc.ReadCLIConfig(ctx),
} }
} }
...@@ -7,7 +7,6 @@ import ( ...@@ -7,7 +7,6 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-batcher/compressor" "github.com/ethereum-optimism/optimism/op-batcher/compressor"
"github.com/ethereum-optimism/optimism/op-batcher/rpc"
opservice "github.com/ethereum-optimism/optimism/op-service" opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
...@@ -102,7 +101,6 @@ func init() { ...@@ -102,7 +101,6 @@ func init() {
optionalFlags = append(optionalFlags, oplog.CLIFlags(EnvVarPrefix)...) optionalFlags = append(optionalFlags, oplog.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, opmetrics.CLIFlags(EnvVarPrefix)...) optionalFlags = append(optionalFlags, opmetrics.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, oppprof.CLIFlags(EnvVarPrefix)...) optionalFlags = append(optionalFlags, oppprof.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, rpc.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, txmgr.CLIFlags(EnvVarPrefix)...) optionalFlags = append(optionalFlags, txmgr.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, compressor.CLIFlags(EnvVarPrefix)...) optionalFlags = append(optionalFlags, compressor.CLIFlags(EnvVarPrefix)...)
......
...@@ -51,6 +51,7 @@ type Metrics struct { ...@@ -51,6 +51,7 @@ type Metrics struct {
opmetrics.RefMetrics opmetrics.RefMetrics
txmetrics.TxMetrics txmetrics.TxMetrics
opmetrics.RPCMetrics
info prometheus.GaugeVec info prometheus.GaugeVec
up prometheus.Gauge up prometheus.Gauge
...@@ -93,6 +94,7 @@ func NewMetrics(procName string) *Metrics { ...@@ -93,6 +94,7 @@ func NewMetrics(procName string) *Metrics {
RefMetrics: opmetrics.MakeRefMetrics(ns, factory), RefMetrics: opmetrics.MakeRefMetrics(ns, factory),
TxMetrics: txmetrics.MakeTxMetrics(ns, factory), TxMetrics: txmetrics.MakeTxMetrics(ns, factory),
RPCMetrics: opmetrics.MakeRPCMetrics(ns, factory),
info: *factory.NewGaugeVec(prometheus.GaugeOpts{ info: *factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Namespace: ns,
......
...@@ -2,6 +2,12 @@ package rpc ...@@ -2,6 +2,12 @@ package rpc
import ( import (
"context" "context"
"github.com/ethereum/go-ethereum/log"
gethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum-optimism/optimism/op-service/rpc"
) )
type batcherClient interface { type batcherClient interface {
...@@ -10,12 +16,21 @@ type batcherClient interface { ...@@ -10,12 +16,21 @@ type batcherClient interface {
} }
type adminAPI struct { type adminAPI struct {
*rpc.CommonAdminAPI
b batcherClient b batcherClient
} }
func NewAdminAPI(dr batcherClient) *adminAPI { func NewAdminAPI(dr batcherClient, m metrics.RPCMetricer, log log.Logger) *adminAPI {
return &adminAPI{ return &adminAPI{
b: dr, CommonAdminAPI: rpc.NewCommonAdminAPI(m, log),
b: dr,
}
}
func GetAdminAPI(api *adminAPI) gethrpc.API {
return gethrpc.API{
Namespace: "admin",
Service: api,
} }
} }
......
package rpc
import (
"github.com/urfave/cli/v2"
opservice "github.com/ethereum-optimism/optimism/op-service"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
)
const (
EnableAdminFlagName = "rpc.enable-admin"
)
func CLIFlags(envPrefix string) []cli.Flag {
return []cli.Flag{
&cli.BoolFlag{
Name: EnableAdminFlagName,
Usage: "Enable the admin API (experimental)",
EnvVars: opservice.PrefixEnvVar(envPrefix, "RPC_ENABLE_ADMIN"),
},
}
}
type CLIConfig struct {
oprpc.CLIConfig
EnableAdmin bool
}
func ReadCLIConfig(ctx *cli.Context) CLIConfig {
return CLIConfig{
CLIConfig: oprpc.ReadCLIConfig(ctx),
EnableAdmin: ctx.Bool(EnableAdminFlagName),
}
}
...@@ -3,8 +3,6 @@ package metrics ...@@ -3,8 +3,6 @@ package metrics
import ( import (
"context" "context"
"errors"
"fmt"
"net" "net"
"strconv" "strconv"
"time" "time"
...@@ -21,9 +19,7 @@ import ( ...@@ -21,9 +19,7 @@ import (
"github.com/prometheus/client_golang/prometheus/collectors" "github.com/prometheus/client_golang/prometheus/collectors"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
) )
...@@ -31,9 +27,6 @@ import ( ...@@ -31,9 +27,6 @@ import (
const ( const (
Namespace = "op_node" Namespace = "op_node"
RPCServerSubsystem = "rpc_server"
RPCClientSubsystem = "rpc_client"
BatchMethod = "<batch>" BatchMethod = "<batch>"
) )
...@@ -87,11 +80,7 @@ type Metrics struct { ...@@ -87,11 +80,7 @@ type Metrics struct {
Info *prometheus.GaugeVec Info *prometheus.GaugeVec
Up prometheus.Gauge Up prometheus.Gauge
RPCServerRequestsTotal *prometheus.CounterVec metrics.RPCMetrics
RPCServerRequestDurationSeconds *prometheus.HistogramVec
RPCClientRequestsTotal *prometheus.CounterVec
RPCClientRequestDurationSeconds *prometheus.HistogramVec
RPCClientResponsesTotal *prometheus.CounterVec
L1SourceCache *CacheMetrics L1SourceCache *CacheMetrics
L2SourceCache *CacheMetrics L2SourceCache *CacheMetrics
...@@ -186,49 +175,7 @@ func NewMetrics(procName string) *Metrics { ...@@ -186,49 +175,7 @@ func NewMetrics(procName string) *Metrics {
Help: "1 if the op node has finished starting up", Help: "1 if the op node has finished starting up",
}), }),
RPCServerRequestsTotal: factory.NewCounterVec(prometheus.CounterOpts{ RPCMetrics: metrics.MakeRPCMetrics(ns, factory),
Namespace: ns,
Subsystem: RPCServerSubsystem,
Name: "requests_total",
Help: "Total requests to the RPC server",
}, []string{
"method",
}),
RPCServerRequestDurationSeconds: factory.NewHistogramVec(prometheus.HistogramOpts{
Namespace: ns,
Subsystem: RPCServerSubsystem,
Name: "request_duration_seconds",
Buckets: []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10},
Help: "Histogram of RPC server request durations",
}, []string{
"method",
}),
RPCClientRequestsTotal: factory.NewCounterVec(prometheus.CounterOpts{
Namespace: ns,
Subsystem: RPCClientSubsystem,
Name: "requests_total",
Help: "Total RPC requests initiated by the opnode's RPC client",
}, []string{
"method",
}),
RPCClientRequestDurationSeconds: factory.NewHistogramVec(prometheus.HistogramOpts{
Namespace: ns,
Subsystem: RPCClientSubsystem,
Name: "request_duration_seconds",
Buckets: []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10},
Help: "Histogram of RPC client request durations",
}, []string{
"method",
}),
RPCClientResponsesTotal: factory.NewCounterVec(prometheus.CounterOpts{
Namespace: ns,
Subsystem: RPCClientSubsystem,
Name: "responses_total",
Help: "Total RPC request responses received by the opnode's RPC client",
}, []string{
"method",
"error",
}),
L1SourceCache: NewCacheMetrics(factory, ns, "l1_source_cache", "L1 Source cache"), L1SourceCache: NewCacheMetrics(factory, ns, "l1_source_cache", "L1 Source cache"),
L2SourceCache: NewCacheMetrics(factory, ns, "l2_source_cache", "L2 Source cache"), L2SourceCache: NewCacheMetrics(factory, ns, "l2_source_cache", "L2 Source cache"),
...@@ -467,53 +414,6 @@ func (m *Metrics) RecordUp() { ...@@ -467,53 +414,6 @@ func (m *Metrics) RecordUp() {
m.Up.Set(1) m.Up.Set(1)
} }
// RecordRPCServerRequest is a helper method to record an incoming RPC
// call to the opnode's RPC server. It bumps the requests metric,
// and tracks how long it takes to serve a response.
func (m *Metrics) RecordRPCServerRequest(method string) func() {
m.RPCServerRequestsTotal.WithLabelValues(method).Inc()
timer := prometheus.NewTimer(m.RPCServerRequestDurationSeconds.WithLabelValues(method))
return func() {
timer.ObserveDuration()
}
}
// RecordRPCClientRequest is a helper method to record an RPC client
// request. It bumps the requests metric, tracks the response
// duration, and records the response's error code.
func (m *Metrics) RecordRPCClientRequest(method string) func(err error) {
m.RPCClientRequestsTotal.WithLabelValues(method).Inc()
timer := prometheus.NewTimer(m.RPCClientRequestDurationSeconds.WithLabelValues(method))
return func(err error) {
m.RecordRPCClientResponse(method, err)
timer.ObserveDuration()
}
}
// RecordRPCClientResponse records an RPC response. It will
// convert the passed-in error into something metrics friendly.
// Nil errors get converted into <nil>, RPC errors are converted
// into rpc_<error code>, HTTP errors are converted into
// http_<status code>, and everything else is converted into
// <unknown>.
func (m *Metrics) RecordRPCClientResponse(method string, err error) {
var errStr string
var rpcErr rpc.Error
var httpErr rpc.HTTPError
if err == nil {
errStr = "<nil>"
} else if errors.As(err, &rpcErr) {
errStr = fmt.Sprintf("rpc_%d", rpcErr.ErrorCode())
} else if errors.As(err, &httpErr) {
errStr = fmt.Sprintf("http_%d", httpErr.StatusCode)
} else if errors.Is(err, ethereum.NotFound) {
errStr = "<not found>"
} else {
errStr = "<unknown>"
}
m.RPCClientResponsesTotal.WithLabelValues(method, errStr).Inc()
}
func (m *Metrics) SetDerivationIdle(status bool) { func (m *Metrics) SetDerivationIdle(status bool) {
var val float64 var val float64
if status { if status {
......
...@@ -11,7 +11,8 @@ import ( ...@@ -11,7 +11,8 @@ import (
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/version" "github.com/ethereum-optimism/optimism/op-node/version"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
oplog "github.com/ethereum-optimism/optimism/op-service/log" "github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum-optimism/optimism/op-service/rpc"
) )
type l2EthClient interface { type l2EthClient interface {
...@@ -31,79 +32,51 @@ type driverClient interface { ...@@ -31,79 +32,51 @@ type driverClient interface {
SequencerActive(context.Context) (bool, error) SequencerActive(context.Context) (bool, error)
} }
type rpcMetrics interface {
// RecordRPCServerRequest returns a function that records the duration of serving the given RPC method
RecordRPCServerRequest(method string) func()
}
type adminAPI struct { type adminAPI struct {
dr driverClient *rpc.CommonAdminAPI
m rpcMetrics dr driverClient
log log.Logger
} }
func NewAdminAPI(dr driverClient, m rpcMetrics, log log.Logger) *adminAPI { func NewAdminAPI(dr driverClient, m metrics.RPCMetricer, log log.Logger) *adminAPI {
return &adminAPI{ return &adminAPI{
dr: dr, CommonAdminAPI: rpc.NewCommonAdminAPI(m, log),
m: m, dr: dr,
log: log,
} }
} }
func (n *adminAPI) ResetDerivationPipeline(ctx context.Context) error { func (n *adminAPI) ResetDerivationPipeline(ctx context.Context) error {
recordDur := n.m.RecordRPCServerRequest("admin_resetDerivationPipeline") recordDur := n.M.RecordRPCServerRequest("admin_resetDerivationPipeline")
defer recordDur() defer recordDur()
return n.dr.ResetDerivationPipeline(ctx) return n.dr.ResetDerivationPipeline(ctx)
} }
func (n *adminAPI) StartSequencer(ctx context.Context, blockHash common.Hash) error { func (n *adminAPI) StartSequencer(ctx context.Context, blockHash common.Hash) error {
recordDur := n.m.RecordRPCServerRequest("admin_startSequencer") recordDur := n.M.RecordRPCServerRequest("admin_startSequencer")
defer recordDur() defer recordDur()
return n.dr.StartSequencer(ctx, blockHash) return n.dr.StartSequencer(ctx, blockHash)
} }
func (n *adminAPI) StopSequencer(ctx context.Context) (common.Hash, error) { func (n *adminAPI) StopSequencer(ctx context.Context) (common.Hash, error) {
recordDur := n.m.RecordRPCServerRequest("admin_stopSequencer") recordDur := n.M.RecordRPCServerRequest("admin_stopSequencer")
defer recordDur() defer recordDur()
return n.dr.StopSequencer(ctx) return n.dr.StopSequencer(ctx)
} }
func (n *adminAPI) SequencerActive(ctx context.Context) (bool, error) { func (n *adminAPI) SequencerActive(ctx context.Context) (bool, error) {
recordDur := n.m.RecordRPCServerRequest("admin_sequencerActive") recordDur := n.M.RecordRPCServerRequest("admin_sequencerActive")
defer recordDur() defer recordDur()
return n.dr.SequencerActive(ctx) return n.dr.SequencerActive(ctx)
} }
func (n *adminAPI) SetLogLevel(ctx context.Context, lvlStr string) error {
recordDur := n.m.RecordRPCServerRequest("admin_setLogLevel")
defer recordDur()
h := n.log.GetHandler()
lvl, err := log.LvlFromString(lvlStr)
if err != nil {
return err
}
// We set the log level, and do not wrap the handler with an additional filter handler,
// as the underlying handler would otherwise also still filter with the previous log level.
lvlSetter, ok := h.(oplog.LvlSetter)
if !ok {
return fmt.Errorf("log handler type %T cannot change log level", h)
}
lvlSetter.SetLogLevel(lvl)
return nil
}
type nodeAPI struct { type nodeAPI struct {
config *rollup.Config config *rollup.Config
client l2EthClient client l2EthClient
dr driverClient dr driverClient
log log.Logger log log.Logger
m rpcMetrics m metrics.RPCMetricer
} }
func NewNodeAPI(config *rollup.Config, l2Client l2EthClient, dr driverClient, log log.Logger, m rpcMetrics) *nodeAPI { func NewNodeAPI(config *rollup.Config, l2Client l2EthClient, dr driverClient, log log.Logger, m metrics.RPCMetricer) *nodeAPI {
return &nodeAPI{ return &nodeAPI{
config: config, config: config,
client: l2Client, client: l2Client,
......
...@@ -58,3 +58,9 @@ type TestRPCMetrics struct{} ...@@ -58,3 +58,9 @@ type TestRPCMetrics struct{}
func (n *TestRPCMetrics) RecordRPCServerRequest(method string) func() { func (n *TestRPCMetrics) RecordRPCServerRequest(method string) func() {
return func() {} return func() {}
} }
func (n *TestRPCMetrics) RecordRPCClientRequest(method string) func(err error) {
return func(err error) {}
}
func (n *TestRPCMetrics) RecordRPCClientResponse(method string, err error) {}
package metrics
import (
"errors"
"fmt"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/rpc"
"github.com/prometheus/client_golang/prometheus"
)
const (
RPCServerSubsystem = "rpc_server"
RPCClientSubsystem = "rpc_client"
)
type RPCMetricer interface {
RecordRPCServerRequest(method string) func()
RecordRPCClientRequest(method string) func(err error)
RecordRPCClientResponse(method string, err error)
}
// RPCMetrics tracks all the RPC metrics for the op-service RPC.
type RPCMetrics struct {
RPCServerRequestsTotal *prometheus.CounterVec
RPCServerRequestDurationSeconds *prometheus.HistogramVec
RPCClientRequestsTotal *prometheus.CounterVec
RPCClientRequestDurationSeconds *prometheus.HistogramVec
RPCClientResponsesTotal *prometheus.CounterVec
}
// MakeRPCMetrics creates a new RPCMetrics instance with the given process name, and
// namespace for the service.
func MakeRPCMetrics(ns string, factory Factory) RPCMetrics {
return RPCMetrics{
RPCServerRequestsTotal: factory.NewCounterVec(prometheus.CounterOpts{
Namespace: ns,
Subsystem: RPCServerSubsystem,
Name: "requests_total",
Help: "Total requests to the RPC server",
}, []string{
"method",
}),
RPCServerRequestDurationSeconds: factory.NewHistogramVec(prometheus.HistogramOpts{
Namespace: ns,
Subsystem: RPCServerSubsystem,
Name: "request_duration_seconds",
Buckets: []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10},
Help: "Histogram of RPC server request durations",
}, []string{
"method",
}),
RPCClientRequestsTotal: factory.NewCounterVec(prometheus.CounterOpts{
Namespace: ns,
Subsystem: RPCClientSubsystem,
Name: "requests_total",
Help: "Total RPC requests initiated by the opnode's RPC client",
}, []string{
"method",
}),
RPCClientRequestDurationSeconds: factory.NewHistogramVec(prometheus.HistogramOpts{
Namespace: ns,
Subsystem: RPCClientSubsystem,
Name: "request_duration_seconds",
Buckets: []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10},
Help: "Histogram of RPC client request durations",
}, []string{
"method",
}),
RPCClientResponsesTotal: factory.NewCounterVec(prometheus.CounterOpts{
Namespace: ns,
Subsystem: RPCClientSubsystem,
Name: "responses_total",
Help: "Total RPC request responses received by the opnode's RPC client",
}, []string{
"method",
"error",
}),
}
}
// RecordRPCServerRequest is a helper method to record an incoming RPC
// call to the opnode's RPC server. It bumps the requests metric,
// and tracks how long it takes to serve a response.
func (m *RPCMetrics) RecordRPCServerRequest(method string) func() {
m.RPCServerRequestsTotal.WithLabelValues(method).Inc()
timer := prometheus.NewTimer(m.RPCServerRequestDurationSeconds.WithLabelValues(method))
return func() {
timer.ObserveDuration()
}
}
// RecordRPCClientRequest is a helper method to record an RPC client
// request. It bumps the requests metric, tracks the response
// duration, and records the response's error code.
func (m *RPCMetrics) RecordRPCClientRequest(method string) func(err error) {
m.RPCClientRequestsTotal.WithLabelValues(method).Inc()
timer := prometheus.NewTimer(m.RPCClientRequestDurationSeconds.WithLabelValues(method))
return func(err error) {
m.RecordRPCClientResponse(method, err)
timer.ObserveDuration()
}
}
// RecordRPCClientResponse records an RPC response. It will
// convert the passed-in error into something metrics friendly.
// Nil errors get converted into <nil>, RPC errors are converted
// into rpc_<error code>, HTTP errors are converted into
// http_<status code>, and everything else is converted into
// <unknown>.
func (m *RPCMetrics) RecordRPCClientResponse(method string, err error) {
var errStr string
var rpcErr rpc.Error
var httpErr rpc.HTTPError
if err == nil {
errStr = "<nil>"
} else if errors.As(err, &rpcErr) {
errStr = fmt.Sprintf("rpc_%d", rpcErr.ErrorCode())
} else if errors.As(err, &httpErr) {
errStr = fmt.Sprintf("http_%d", httpErr.StatusCode)
} else if errors.Is(err, ethereum.NotFound) {
errStr = "<not found>"
} else {
errStr = "<unknown>"
}
m.RPCClientResponsesTotal.WithLabelValues(method, errStr).Inc()
}
package rpc
import (
"context"
"fmt"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum/go-ethereum/log"
)
type CommonAdminAPI struct {
M metrics.RPCMetricer
log log.Logger
}
func NewCommonAdminAPI(m metrics.RPCMetricer, log log.Logger) *CommonAdminAPI {
return &CommonAdminAPI{
M: m,
log: log,
}
}
func (n *CommonAdminAPI) SetLogLevel(ctx context.Context, lvlStr string) error {
recordDur := n.M.RecordRPCServerRequest("admin_setLogLevel")
defer recordDur()
h := n.log.GetHandler()
lvl, err := log.LvlFromString(lvlStr)
if err != nil {
return err
}
// We set the log level, and do not wrap the handler with an additional filter handler,
// as the underlying handler would otherwise also still filter with the previous log level.
lvlSetter, ok := h.(oplog.LvlSetter)
if !ok {
return fmt.Errorf("log handler type %T cannot change log level", h)
}
lvlSetter.SetLogLevel(lvl)
return nil
}
...@@ -9,8 +9,9 @@ import ( ...@@ -9,8 +9,9 @@ import (
) )
const ( const (
ListenAddrFlagName = "rpc.addr" ListenAddrFlagName = "rpc.addr"
PortFlagName = "rpc.port" PortFlagName = "rpc.port"
EnableAdminFlagName = "rpc.enable-admin"
) )
func CLIFlags(envPrefix string) []cli.Flag { func CLIFlags(envPrefix string) []cli.Flag {
...@@ -27,12 +28,18 @@ func CLIFlags(envPrefix string) []cli.Flag { ...@@ -27,12 +28,18 @@ func CLIFlags(envPrefix string) []cli.Flag {
Value: 8545, Value: 8545,
EnvVars: opservice.PrefixEnvVar(envPrefix, "RPC_PORT"), EnvVars: opservice.PrefixEnvVar(envPrefix, "RPC_PORT"),
}, },
&cli.BoolFlag{
Name: EnableAdminFlagName,
Usage: "Enable the admin API",
EnvVars: opservice.PrefixEnvVar(envPrefix, "RPC_ENABLE_ADMIN"),
},
} }
} }
type CLIConfig struct { type CLIConfig struct {
ListenAddr string ListenAddr string
ListenPort int ListenPort int
EnableAdmin bool
} }
func (c CLIConfig) Check() error { func (c CLIConfig) Check() error {
...@@ -45,7 +52,8 @@ func (c CLIConfig) Check() error { ...@@ -45,7 +52,8 @@ func (c CLIConfig) Check() error {
func ReadCLIConfig(ctx *cli.Context) CLIConfig { func ReadCLIConfig(ctx *cli.Context) CLIConfig {
return CLIConfig{ return CLIConfig{
ListenAddr: ctx.String(ListenAddrFlagName), ListenAddr: ctx.String(ListenAddrFlagName),
ListenPort: ctx.Int(PortFlagName), ListenPort: ctx.Int(PortFlagName),
EnableAdmin: ctx.Bool(EnableAdminFlagName),
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment