Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
bf58610
Added otel to sei-cosmos package
amir-deris May 19, 2026
2adef34
Added bounded routes as the metric label
amir-deris May 20, 2026
18f8dae
Fixed description of metrics
amir-deris May 20, 2026
2b240fa
Added context to interface Queryable
amir-deris May 20, 2026
cfaaa99
Added bounded buckets for denom cardinality
amir-deris May 20, 2026
d476732
Dropped the proposal id from new metric
amir-deris May 20, 2026
e1348dc
Removed info attribute, resolved plan height metric collision
amir-deris May 20, 2026
47ba551
Merge branch 'main' into amir/plt-352-migrate-sei-cosmos-to-otel
amir-deris May 21, 2026
43c6641
Converted succes and proof to bool attribute
amir-deris May 21, 2026
5613762
Fixed abciQueryMetricRoute test
amir-deris May 21, 2026
bf31afc
Updated utils_test for query context
amir-deris May 21, 2026
3732641
Updated metric units
amir-deris May 21, 2026
7389653
Moved DenomClass method to its own file
amir-deris May 21, 2026
254e9f5
Added last to metric names
amir-deris May 21, 2026
42da9c0
Added extra buckets for abci_query_duration
amir-deris May 21, 2026
6771eb0
Merge branch 'main' into amir/plt-352-migrate-sei-cosmos-to-otel
amir-deris May 21, 2026
ad8921b
Fixing lint errors
amir-deris May 21, 2026
198db78
Merge branch 'main' into amir/plt-352-migrate-sei-cosmos-to-otel
amir-deris May 21, 2026
51d0617
Merge branch 'main' into amir/plt-352-migrate-sei-cosmos-to-otel
amir-deris May 22, 2026
6fdfa8f
Merge branch 'main' into amir/plt-352-migrate-sei-cosmos-to-otel
masih May 26, 2026
363bbe2
Reused time.Now in process proposal and finalize block
amir-deris May 26, 2026
84551ab
Merge branch 'main' into amir/plt-352-migrate-sei-cosmos-to-otel
amir-deris May 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion evmrpc/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func (a *StateAPI) GetProof(ctx context.Context, address common.Address, storage
for _, key := range storageKeys {
paddedKey := common.BytesToHash([]byte(key))
formattedKey := append(types.StateKey(address), paddedKey[:]...)
qres := queryStore.Query(abci.RequestQuery{
qres := queryStore.Query(ctx, abci.RequestQuery{
Path: "/key",
Data: formattedKey,
Height: block.Block.Height,
Expand Down
79 changes: 62 additions & 17 deletions sei-cosmos/baseapp/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"github.com/sei-protocol/sei-chain/sei-cosmos/types/legacytm"
abci "github.com/sei-protocol/sei-chain/sei-tendermint/abci/types"
tmproto "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/types"
"go.opentelemetry.io/otel/attribute"
otelmetric "go.opentelemetry.io/otel/metric"
"google.golang.org/grpc/codes"
grpcstatus "google.golang.org/grpc/status"
)
Expand Down Expand Up @@ -102,7 +104,12 @@ func (app *BaseApp) Info(ctx context.Context, req *abci.RequestInfo) (*abci.Resp
}

func (app *BaseApp) MidBlock(ctx sdk.Context, height int64) (events []abci.Event) {
defer telemetry.MeasureSince(time.Now(), "abci", "mid_block")
start := time.Now()
defer func() {
baseappMetrics.midBlockDuration.Record(ctx.Context(), time.Since(start).Seconds())
// TODO(PLT-353): remove once baseapp_mid_block_duration verified
telemetry.MeasureSince(start, "abci", "mid_block")
}()

if app.midBlocker != nil {
midBlockEvents := app.midBlocker(ctx, height)
Expand All @@ -114,7 +121,12 @@ func (app *BaseApp) MidBlock(ctx sdk.Context, height int64) (events []abci.Event

// EndBlock implements the ABCI interface.
func (app *BaseApp) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) (res abci.ResponseEndBlock) {
defer telemetry.MeasureSince(time.Now(), "abci", "end_block")
start := time.Now()
defer func() {
baseappMetrics.endBlockDuration.Record(ctx.Context(), time.Since(start).Seconds())
// TODO(PLT-353): remove once baseapp_end_block_duration verified
telemetry.MeasureSince(start, "abci", "end_block")
}()

if app.endBlocker != nil {
res = app.endBlocker(ctx, req)
Expand Down Expand Up @@ -166,15 +178,25 @@ func (app *BaseApp) DeliverTxBatch(ctx sdk.Context, req sdk.DeliverTxBatchReques
// Regardless of tx execution outcome, the ResponseDeliverTx will contain relevant
// gas execution context.
func (app *BaseApp) DeliverTx(ctx sdk.Context, req abci.RequestDeliverTxV2, tx sdk.Tx, checksum [32]byte) (res abci.ResponseDeliverTx) {
defer telemetry.MeasureSince(time.Now(), "abci", "deliver_tx")

deliverTxStart := time.Now()
gInfo := sdk.GasInfo{}
resultStr := "successful"

defer func() {
baseappMetrics.deliverTxDuration.Record(ctx.Context(), time.Since(deliverTxStart).Seconds())
// TODO(PLT-353): remove once baseapp_deliver_tx_duration verified
telemetry.MeasureSince(deliverTxStart, "abci", "deliver_tx")
baseappMetrics.txCount.Add(ctx.Context(), 1)
// TODO(PLT-353): remove once baseapp_tx_count verified
telemetry.IncrCounter(1, "tx", "count")
baseappMetrics.txResult.Add(ctx.Context(), 1, otelmetric.WithAttributes(attribute.String("result", resultStr)))
// TODO(PLT-353): remove once baseapp_tx_result verified
telemetry.IncrCounter(1, "tx", resultStr)
baseappMetrics.txGasUsed.Record(ctx.Context(), int64(gInfo.GasUsed)) //nolint:gosec
// TODO(PLT-353): remove once baseapp_tx_gas_used verified
telemetry.SetGauge(float32(gInfo.GasUsed), "tx", "gas", "used")
baseappMetrics.txGasWanted.Record(ctx.Context(), int64(gInfo.GasWanted)) //nolint:gosec
// TODO(PLT-353): remove once baseapp_tx_gas_wanted verified
telemetry.SetGauge(float32(gInfo.GasWanted), "tx", "gas", "wanted")
}()

Expand Down Expand Up @@ -245,8 +267,12 @@ func (app *BaseApp) SetDeliverStateToCommit() {
// against that height and gracefully halt if it matches the latest committed
// height.
func (app *BaseApp) Commit(ctx context.Context) (res *abci.ResponseCommit, err error) {
defer telemetry.MeasureSince(time.Now(), "abci", "commit")
commitStart := time.Now()
defer func() {
baseappMetrics.commitDuration.Record(ctx, time.Since(commitStart).Seconds())
// TODO(PLT-353): remove once baseapp_commit_duration verified
telemetry.MeasureSince(commitStart, "abci", "commit")
}()
app.commitLock.Lock()
defer app.commitLock.Unlock()

Expand Down Expand Up @@ -388,7 +414,13 @@ func (app *BaseApp) Snapshot(height int64) {
// Query implements the ABCI interface. It delegates to CommitMultiStore if it
// implements Queryable.
func (app *BaseApp) Query(ctx context.Context, req *abci.RequestQuery) (res *abci.ResponseQuery, err error) {
defer telemetry.MeasureSinceWithLabels([]string{"abci", "query"}, time.Now(), []metrics.Label{{Name: "path", Value: req.Path}})
queryStart := time.Now()
defer func() {
route := app.abciQueryMetricRoute(req.Path)
baseappMetrics.abciQueryDuration.Record(ctx, time.Since(queryStart).Seconds(), otelmetric.WithAttributes(attribute.String(abciQueryMetricRouteLabel, route)))
// TODO(PLT-353): remove once baseapp_abci_query_duration verified
telemetry.MeasureSinceWithLabels([]string{"abci", "query"}, queryStart, []metrics.Label{{Name: "path", Value: req.Path}})
}()

// Add panic recovery for all queries.
// ref: https://github.com/cosmos/cosmos-sdk/pull/8039
Expand Down Expand Up @@ -425,7 +457,7 @@ func (app *BaseApp) Query(ctx context.Context, req *abci.RequestQuery) (res *abc
resp = handleQueryApp(app, path, *req)

case "store":
resp = handleQueryStore(app, path, *req)
resp = handleQueryStore(ctx, app, path, *req)

case "custom":
resp = handleQueryCustom(app, path, *req)
Expand Down Expand Up @@ -846,7 +878,7 @@ func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) abci.Res
), app.trace)
}

func handleQueryStore(app *BaseApp, path []string, req abci.RequestQuery) abci.ResponseQuery {
func handleQueryStore(ctx context.Context, app *BaseApp, path []string, req abci.RequestQuery) abci.ResponseQuery {
var (
queryable sdk.Queryable
ok bool
Expand Down Expand Up @@ -875,7 +907,7 @@ func handleQueryStore(app *BaseApp, path []string, req abci.RequestQuery) abci.R
), app.trace)
}

resp := queryable.Query(req)
resp := queryable.Query(ctx, req)
resp.Height = req.Height

return resp
Expand Down Expand Up @@ -934,9 +966,13 @@ func splitPath(requestPath string) (path []string) {

// ABCI++
func (app *BaseApp) ProcessProposal(ctx context.Context, req *abci.RequestProcessProposal) (resp *abci.ResponseProcessProposal, err error) {
defer telemetry.MeasureSince(time.Now(), "abci", "process_proposal")
ppStart := time.Now()
defer func() { app.execProcessProposalMs = time.Since(ppStart).Milliseconds() }()
processProposalStart := time.Now()
defer func() {
baseappMetrics.processProposalDuration.Record(ctx, time.Since(processProposalStart).Seconds())
// TODO(PLT-353): remove once baseapp_process_proposal_duration verified
telemetry.MeasureSince(processProposalStart, "abci", "process_proposal")
}()
Comment thread
cursor[bot] marked this conversation as resolved.
defer func() { app.execProcessProposalMs = time.Since(processProposalStart).Milliseconds() }()
if app.ChainID != req.Header.ChainID {
return nil, fmt.Errorf("unexpected ChainID, got %q, want %q", req.Header.ChainID, app.ChainID)
}
Expand Down Expand Up @@ -997,10 +1033,14 @@ func (app *BaseApp) ProcessProposal(ctx context.Context, req *abci.RequestProces
}

func (app *BaseApp) FinalizeBlock(ctx context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) {
defer telemetry.MeasureSince(time.Now(), "abci", "finalize_block")
fbStart := time.Now()
finalizeBlockStart := time.Now()
defer func() {
baseappMetrics.finalizeBlockDuration.Record(ctx, time.Since(finalizeBlockStart).Seconds())
// TODO(PLT-353): remove once baseapp_finalize_block_duration verified
telemetry.MeasureSince(finalizeBlockStart, "abci", "finalize_block")
}()
app.execBlockTxCount = len(req.Txs)
defer func() { app.execFinalizeBlockMs = time.Since(fbStart).Milliseconds() }()
defer func() { app.execFinalizeBlockMs = time.Since(finalizeBlockStart).Milliseconds() }()

if app.cms.TracingEnabled() {
app.cms.SetTracingContext(sdk.TraceContext(
Expand Down Expand Up @@ -1040,7 +1080,7 @@ func (app *BaseApp) FinalizeBlock(ctx context.Context, req *abci.RequestFinalize
}
}

func (app *BaseApp) GetTxPriorityHint(_ context.Context, req *abci.RequestGetTxPriorityHintV2) (_resp *abci.ResponseGetTxPriorityHint, _err error) {
func (app *BaseApp) GetTxPriorityHint(ctx context.Context, req *abci.RequestGetTxPriorityHintV2) (_resp *abci.ResponseGetTxPriorityHint, _err error) {
defer func() {
if r := recover(); r != nil {
// Fall back to no-op priority if we panic for any reason. This is to avoid DoS
Expand All @@ -1056,7 +1096,12 @@ func (app *BaseApp) GetTxPriorityHint(_ context.Context, req *abci.RequestGetTxP
}
}()

defer telemetry.MeasureSince(time.Now(), "abci", "get_tx_priority_hint")
priorityHintStart := time.Now()
defer func() {
baseappMetrics.getTxPriorityHintDuration.Record(ctx, time.Since(priorityHintStart).Seconds())
// TODO(PLT-353): remove once baseapp_get_tx_priority_hint_duration verified
telemetry.MeasureSince(priorityHintStart, "abci", "get_tx_priority_hint")
}()

tx, err := app.txDecoder(req.Tx)
if err != nil {
Expand Down
86 changes: 86 additions & 0 deletions sei-cosmos/baseapp/abci_query_metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package baseapp

import storetypes "github.com/sei-protocol/sei-chain/sei-cosmos/store/types"

// storeByNameLookup is implemented by CommitMultiStore backends (rootmulti, storev2).
type storeByNameLookup interface {
GetStoreByName(name string) storetypes.Store
}

// abciStoreQuerySubpaths are the only subpath segments used by mounted store
// Query implementations (/key, /subspace). See store/rootmulti and storev2/commitment.
var abciStoreQuerySubpaths = map[string]struct{}{
"key": {},
"subspace": {},
}

// abciQueryMetricRoute returns a bounded label for ABCI query metrics.
// Raw client paths are not used directly to avoid unbounded metric cardinality.
//
// Rules:
// - Registered gRPC query paths are returned as-is (finite set at startup).
// - Legacy paths use a fixed prefix + registered segment shape.
// - Everything else is "other".
func (app *BaseApp) abciQueryMetricRoute(reqPath string) string {
if app.grpcQueryRouter != nil && app.grpcQueryRouter.Route(reqPath) != nil {
return reqPath
}

parts := splitPath(reqPath)
if len(parts) == 0 {
return "other"
}

switch parts[0] {
case "app":
if len(parts) >= 2 {
switch parts[1] {
case "simulate", "version", "snapshots":
return "app/" + parts[1]
}
}
return "app/unknown"

case "store":
return app.abciStoreQueryMetricRoute(parts)

case "custom":
if len(parts) >= 2 && parts[1] != "" && app.queryRouter != nil && app.queryRouter.Route(parts[1]) != nil {
return "custom/" + parts[1]
}
return "custom/unknown"

default:
return "other"
}
}

func (app *BaseApp) abciStoreQueryMetricRoute(parts []string) string {
unknownRoute := "store/unknown"
if len(parts) < 3 {
return unknownRoute
}
storeName, subpath := parts[1], parts[2]
if _, ok := abciStoreQuerySubpaths[subpath]; !ok {
return unknownRoute
}
if !app.storeRegisteredForQuery(storeName) {
return unknownRoute
}
return "store/" + storeName + "/" + subpath
}

func (app *BaseApp) storeRegisteredForQuery(name string) bool {
if lookup, ok := app.cms.(storeByNameLookup); ok && lookup.GetStoreByName(name) != nil {
return true
}
if app.qms != nil {
if lookup, ok := app.qms.(storeByNameLookup); ok && lookup.GetStoreByName(name) != nil {
return true
}
}
return false
}

// abciQueryMetricRouteLabel is kept as "path" for compatibility with existing dashboards.
const abciQueryMetricRouteLabel = "path"
76 changes: 76 additions & 0 deletions sei-cosmos/baseapp/abci_query_metrics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package baseapp

import (
"fmt"
"testing"

"github.com/sei-protocol/sei-chain/sei-cosmos/testutil"
"github.com/sei-protocol/sei-chain/sei-cosmos/testutil/testdata"
sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types"
abci "github.com/sei-protocol/sei-chain/sei-tendermint/abci/types"
"github.com/stretchr/testify/require"
dbm "github.com/tendermint/tm-db"
)

func TestAbciQueryMetricRoute(t *testing.T) {
db := dbm.NewMemDB()
app := NewBaseApp(t.Name(), db, nil, nil, &testutil.TestAppOpts{})

grpcQR := app.GRPCQueryRouter()
grpcQR.SetInterfaceRegistry(testdata.NewTestInterfaceRegistry())
testdata.RegisterQueryServer(grpcQR, testdata.QueryImpl{})

app.QueryRouter().AddRoute("bank", func(_ sdk.Context, _ []string, _ abci.RequestQuery) ([]byte, error) {
return nil, nil
})

grpcEcho := "/testdata.Query/Echo"

tests := map[string]struct {
reqPath string
expected string
}{
// Production gRPC paths (3-month sample); registered via testdata.Query.
"grpc registered": {
reqPath: grpcEcho,
expected: grpcEcho,
},
"grpc unregistered": {
reqPath: "/cosmos.bank.v1beta1.Query/TotalSupply",
expected: "other",
},

// Legacy app paths.
"app snapshots": {reqPath: "app/snapshots", expected: "app/snapshots"},
"app version": {reqPath: "app/version", expected: "app/version"},
"app unknown": {reqPath: "app/unknown-action", expected: "app/unknown"},

// Legacy store paths (unknown store name or subpath → single bucket).
"store ibc key unregistered": {reqPath: "store/ibc/key", expected: "store/unknown"},
"store random attack": {reqPath: "store/random1/random2", expected: "store/unknown"},
"store bad subpath": {reqPath: "store/key1/evil", expected: "store/unknown"},
"store short": {reqPath: "store/bank", expected: "store/unknown"},

// Legacy custom paths; subpath segments are not part of the metric label.
"custom bank": {reqPath: "custom/bank/all_balances", expected: "custom/bank"},
"custom unregistered": {reqPath: "custom/unknown/foo", expected: "custom/unknown"},

// Garbage / attack paths.
"empty": {reqPath: "", expected: "other"},
"random": {reqPath: "/totally/made/up", expected: "other"},
"leading slash app": {reqPath: "/app/snapshots", expected: "app/snapshots"},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
require.Equal(t, tc.expected, app.abciQueryMetricRoute(tc.reqPath))
})
}
}

func TestAbciQueryMetricRoute_RegisteredStore(t *testing.T) {
app := setupBaseApp(t)
route := fmt.Sprintf("store/%s/key", capKey1.Name())
require.Equal(t, route, app.abciQueryMetricRoute(route))
require.Equal(t, "store/unknown", app.abciQueryMetricRoute("store/random1/key"))
}
2 changes: 1 addition & 1 deletion sei-cosmos/baseapp/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ func TestHandleQueryStore_NonQueryableMultistore(t *testing.T) {
Height: 1,
}

resp := handleQueryStore(app, path, req)
resp := handleQueryStore(context.Background(), app, path, req)
require.True(t, resp.IsErr())
require.Contains(t, resp.Log, "multistore doesn't support queries")
}
Expand Down
Loading
Loading