Skip to content

Commit

Permalink
rpcserver metrics (#30)
Browse files Browse the repository at this point in the history
* rpcserver metrics

* rpcserver: add server name
  • Loading branch information
dvush authored Oct 30, 2024
1 parent f6b894b commit 501d395
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 1 deletion.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/flashbots/go-utils
go 1.20

require (
github.com/VictoriaMetrics/metrics v1.35.1
github.com/ethereum/go-ethereum v1.13.14
github.com/google/uuid v1.3.1
github.com/sirupsen/logrus v1.9.3
Expand Down Expand Up @@ -34,6 +35,8 @@ require (
github.com/supranational/blst v0.3.11 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/valyala/fastrand v1.1.0 // indirect
github.com/valyala/histogram v1.2.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/sync v0.5.0 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40=
github.com/VictoriaMetrics/metrics v1.35.1 h1:o84wtBKQbzLdDy14XeskkCZih6anG+veZ1SwJHFGwrU=
github.com/VictoriaMetrics/metrics v1.35.1/go.mod h1:r7hveu6xMdUACXvB8TYdAj8WEsKzWB0EkpJN+RDtOf8=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88=
Expand Down Expand Up @@ -106,6 +108,10 @@ github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+F
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ=
github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
Expand Down
27 changes: 26 additions & 1 deletion rpcserver/jsonrpc_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"log/slog"
"net/http"
"strings"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/flashbots/go-utils/signature"
Expand Down Expand Up @@ -75,6 +76,8 @@ type Methods map[string]any
type JSONRPCHandlerOpts struct {
// Logger, can be nil
Log *slog.Logger
// Server name. Used to separate logs and metrics when having multiple servers in one binary.
ServerName string
// Max size of the request payload
MaxRequestBodySizeBytes int64
// If true payload signature from X-Flashbots-Signature will be verified
Expand Down Expand Up @@ -120,9 +123,10 @@ func (h *JSONRPCHandler) writeJSONRPCResponse(w http.ResponseWriter, response js
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(response); err != nil {
if h.Log != nil {
h.Log.Error("failed to marshall response", slog.Any("error", err))
h.Log.Error("failed to marshall response", slog.Any("error", err), slog.String("serverName", h.ServerName))
}
http.Error(w, errMarshalResponse, http.StatusInternalServerError)
incInternalErrors(h.ServerName)
return
}
}
Expand All @@ -142,15 +146,25 @@ func (h *JSONRPCHandler) writeJSONRPCError(w http.ResponseWriter, id any, code i
}

func (h *JSONRPCHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
startAt := time.Now()
methodForMetrics := unknownMethodLabel

ctx := r.Context()

defer func() {
incRequestCount(methodForMetrics, h.ServerName)
incRequestDuration(methodForMetrics, time.Since(startAt).Milliseconds(), h.ServerName)
}()

if r.Method != http.MethodPost {
http.Error(w, errMethodNotAllowed, http.StatusMethodNotAllowed)
incIncorrectRequest(h.ServerName)
return
}

if r.Header.Get("Content-Type") != "application/json" {
http.Error(w, errWrongContentType, http.StatusUnsupportedMediaType)
incIncorrectRequest(h.ServerName)
return
}

Expand All @@ -159,6 +173,7 @@ func (h *JSONRPCHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err != nil {
msg := fmt.Sprintf("request body is too big, max size: %d", h.MaxRequestBodySizeBytes)
h.writeJSONRPCError(w, nil, CodeInvalidRequest, msg)
incIncorrectRequest(h.ServerName)
return
}

Expand All @@ -167,6 +182,7 @@ func (h *JSONRPCHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
signer, err := signature.Verify(signatureHeader, body)
if err != nil {
h.writeJSONRPCError(w, nil, CodeInvalidRequest, err.Error())
incIncorrectRequest(h.ServerName)
return
}
ctx = context.WithValue(ctx, signerKey{}, signer)
Expand All @@ -176,11 +192,13 @@ func (h *JSONRPCHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var req jsonRPCRequest
if err := json.Unmarshal(body, &req); err != nil {
h.writeJSONRPCError(w, nil, CodeParseError, err.Error())
incIncorrectRequest(h.ServerName)
return
}

if req.JSONRPC != "2.0" {
h.writeJSONRPCError(w, req.ID, CodeParseError, "invalid jsonrpc version")
incIncorrectRequest(h.ServerName)
return
}
if req.ID != nil {
Expand All @@ -189,6 +207,8 @@ func (h *JSONRPCHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
case string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
default:
h.writeJSONRPCError(w, req.ID, CodeParseError, "invalid id type")
incIncorrectRequest(h.ServerName)
return
}
}

Expand All @@ -210,6 +230,7 @@ func (h *JSONRPCHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if origin != "" {
if len(origin) > maxOriginIDLength {
h.writeJSONRPCError(w, req.ID, CodeInvalidRequest, "x-flashbots-origin header is too long")
incIncorrectRequest(h.ServerName)
return
}
ctx = context.WithValue(ctx, originKey{}, origin)
Expand All @@ -220,19 +241,23 @@ func (h *JSONRPCHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
method, ok := h.methods[req.Method]
if !ok {
h.writeJSONRPCError(w, req.ID, CodeMethodNotFound, "method not found")
incIncorrectRequest(h.ServerName)
return
}
methodForMetrics = req.Method

// call method
result, err := method.call(ctx, req.Params)
if err != nil {
h.writeJSONRPCError(w, req.ID, CodeCustomError, err.Error())
incRequestErrorCount(methodForMetrics, h.ServerName)
return
}

marshaledResult, err := json.Marshal(result)
if err != nil {
h.writeJSONRPCError(w, req.ID, CodeInternalError, err.Error())
incInternalErrors(h.ServerName)
return
}

Expand Down
51 changes: 51 additions & 0 deletions rpcserver/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package rpcserver

import (
"fmt"

"github.com/VictoriaMetrics/metrics"
)

const (
// we use unknown method label for methods that server does not support because otherwise
// users can create arbitrary number of metrics
unknownMethodLabel = "unknown"

// incremented when user made incorrect request
incorrectRequestCounter = `goutils_rpcserver_incorrect_request_total{server_name="%s"}`

// incremented when server has a bug (e.g. can't marshall response)
internalErrorsCounter = `goutils_rpcserver_internal_errors_total{server_name="%s"}`

// incremented when request comes in
requestCountLabel = `goutils_rpcserver_request_count{method="%s",server_name="%s"}`
// incremented when handler method returns JSONRPC error
errorCountLabel = `goutils_rpcserver_error_count{method="%s",server_name="%s"}`
// total duration of the request
requestDurationLabel = `goutils_rpcserver_request_duration_milliseconds{method="%s",server_name="%s"}`
)

func incRequestCount(method, serverName string) {
l := fmt.Sprintf(requestCountLabel, method, serverName)
metrics.GetOrCreateCounter(l).Inc()
}

func incIncorrectRequest(serverName string) {
l := fmt.Sprintf(incorrectRequestCounter, serverName)
metrics.GetOrCreateCounter(l).Inc()
}

func incRequestErrorCount(method, serverName string) {
l := fmt.Sprintf(errorCountLabel, method, serverName)
metrics.GetOrCreateCounter(l).Inc()
}

func incRequestDuration(method string, duration int64, serverName string) {
l := fmt.Sprintf(requestDurationLabel, method, serverName)
metrics.GetOrCreateSummary(l).Update(float64(duration))
}

func incInternalErrors(serverName string) {
l := fmt.Sprintf(internalErrorsCounter, serverName)
metrics.GetOrCreateCounter(l).Inc()
}

0 comments on commit 501d395

Please sign in to comment.