mirror of
https://github.com/GMWalletApp/epusdt.git
synced 2026-07-07 10:16:15 +00:00
feat: add in-memory RPC runtime stats SSE endpoint
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RpcRuntimeStatsSnapshot is an in-process snapshot of RPC activity for one
|
||||
// chain. It is intentionally not persisted; restarting the process resets it.
|
||||
type RpcRuntimeStatsSnapshot struct {
|
||||
Network string
|
||||
SuccessCount int64
|
||||
FailureCount int64
|
||||
LatestBlockHeight int64
|
||||
LastSyncAt time.Time
|
||||
}
|
||||
|
||||
type rpcRuntimeStatsCounter struct {
|
||||
successCount int64
|
||||
failureCount int64
|
||||
latestBlockHeight int64
|
||||
lastSyncAt time.Time
|
||||
}
|
||||
|
||||
var gRpcRuntimeStats = struct {
|
||||
sync.RWMutex
|
||||
byNetwork map[string]*rpcRuntimeStatsCounter
|
||||
}{
|
||||
byNetwork: make(map[string]*rpcRuntimeStatsCounter),
|
||||
}
|
||||
|
||||
func normalizeRpcStatsNetwork(network string) string {
|
||||
return strings.ToLower(strings.TrimSpace(network))
|
||||
}
|
||||
|
||||
func rpcStatsCounterLocked(network string) *rpcRuntimeStatsCounter {
|
||||
counter := gRpcRuntimeStats.byNetwork[network]
|
||||
if counter == nil {
|
||||
counter = &rpcRuntimeStatsCounter{}
|
||||
gRpcRuntimeStats.byNetwork[network] = counter
|
||||
}
|
||||
return counter
|
||||
}
|
||||
|
||||
// RecordRpcSuccess records one successful application-level RPC operation.
|
||||
func RecordRpcSuccess(network string) {
|
||||
network = normalizeRpcStatsNetwork(network)
|
||||
if network == "" {
|
||||
return
|
||||
}
|
||||
gRpcRuntimeStats.Lock()
|
||||
defer gRpcRuntimeStats.Unlock()
|
||||
counter := rpcStatsCounterLocked(network)
|
||||
counter.successCount++
|
||||
counter.lastSyncAt = time.Now()
|
||||
}
|
||||
|
||||
// RecordRpcFailure records one failed application-level RPC operation.
|
||||
func RecordRpcFailure(network string) {
|
||||
network = normalizeRpcStatsNetwork(network)
|
||||
if network == "" {
|
||||
return
|
||||
}
|
||||
gRpcRuntimeStats.Lock()
|
||||
defer gRpcRuntimeStats.Unlock()
|
||||
counter := rpcStatsCounterLocked(network)
|
||||
counter.failureCount++
|
||||
}
|
||||
|
||||
// RecordRpcBlockHeight records the latest observed chain height.
|
||||
func RecordRpcBlockHeight(network string, height int64) {
|
||||
network = normalizeRpcStatsNetwork(network)
|
||||
if network == "" || height <= 0 {
|
||||
return
|
||||
}
|
||||
gRpcRuntimeStats.Lock()
|
||||
defer gRpcRuntimeStats.Unlock()
|
||||
counter := rpcStatsCounterLocked(network)
|
||||
counter.lastSyncAt = time.Now()
|
||||
if height > counter.latestBlockHeight {
|
||||
counter.latestBlockHeight = height
|
||||
}
|
||||
}
|
||||
|
||||
// SnapshotRpcRuntimeStats returns a copy of the current per-chain counters.
|
||||
func SnapshotRpcRuntimeStats() map[string]RpcRuntimeStatsSnapshot {
|
||||
gRpcRuntimeStats.RLock()
|
||||
defer gRpcRuntimeStats.RUnlock()
|
||||
out := make(map[string]RpcRuntimeStatsSnapshot, len(gRpcRuntimeStats.byNetwork))
|
||||
for network, counter := range gRpcRuntimeStats.byNetwork {
|
||||
out[network] = RpcRuntimeStatsSnapshot{
|
||||
Network: network,
|
||||
SuccessCount: counter.successCount,
|
||||
FailureCount: counter.failureCount,
|
||||
LatestBlockHeight: counter.latestBlockHeight,
|
||||
LastSyncAt: counter.lastSyncAt,
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ResetRpcRuntimeStatsForTest clears process-local stats for isolated tests.
|
||||
func ResetRpcRuntimeStatsForTest() {
|
||||
gRpcRuntimeStats.Lock()
|
||||
defer gRpcRuntimeStats.Unlock()
|
||||
gRpcRuntimeStats.byNetwork = make(map[string]*rpcRuntimeStatsCounter)
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRpcRuntimeStatsRecordsCountsAndLatestHeight(t *testing.T) {
|
||||
ResetRpcRuntimeStatsForTest()
|
||||
t.Cleanup(ResetRpcRuntimeStatsForTest)
|
||||
|
||||
start := time.Now()
|
||||
RecordRpcSuccess(" TRON ")
|
||||
RecordRpcSuccess("tron")
|
||||
RecordRpcFailure("TRON")
|
||||
RecordRpcBlockHeight("Tron", 100)
|
||||
RecordRpcBlockHeight("tron", 90)
|
||||
|
||||
stats := SnapshotRpcRuntimeStats()
|
||||
got, ok := stats["tron"]
|
||||
if !ok {
|
||||
t.Fatalf("missing tron stats: %#v", stats)
|
||||
}
|
||||
if got.Network != "tron" {
|
||||
t.Fatalf("network = %q, want tron", got.Network)
|
||||
}
|
||||
if got.SuccessCount != 2 {
|
||||
t.Fatalf("success count = %d, want 2", got.SuccessCount)
|
||||
}
|
||||
if got.FailureCount != 1 {
|
||||
t.Fatalf("failure count = %d, want 1", got.FailureCount)
|
||||
}
|
||||
if got.LatestBlockHeight != 100 {
|
||||
t.Fatalf("latest block height = %d, want 100", got.LatestBlockHeight)
|
||||
}
|
||||
if got.LastSyncAt.Before(start) {
|
||||
t.Fatalf("last sync at = %s, want after %s", got.LastSyncAt, start)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRpcRuntimeStatsIgnoresEmptyNetworkAndInvalidHeight(t *testing.T) {
|
||||
ResetRpcRuntimeStatsForTest()
|
||||
t.Cleanup(ResetRpcRuntimeStatsForTest)
|
||||
|
||||
RecordRpcSuccess("")
|
||||
RecordRpcFailure(" ")
|
||||
RecordRpcBlockHeight("tron", 0)
|
||||
|
||||
stats := SnapshotRpcRuntimeStats()
|
||||
if len(stats) != 0 {
|
||||
t.Fatalf("stats = %#v, want empty", stats)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRpcRuntimeStatsBlockHeightUpdatesLastSyncAt(t *testing.T) {
|
||||
ResetRpcRuntimeStatsForTest()
|
||||
t.Cleanup(ResetRpcRuntimeStatsForTest)
|
||||
|
||||
start := time.Now()
|
||||
RecordRpcBlockHeight("ethereum", 123)
|
||||
|
||||
stats := SnapshotRpcRuntimeStats()
|
||||
got := stats["ethereum"]
|
||||
if got.SuccessCount != 0 {
|
||||
t.Fatalf("success count = %d, want 0", got.SuccessCount)
|
||||
}
|
||||
if got.LastSyncAt.Before(start) {
|
||||
t.Fatalf("last sync at = %s, want after %s", got.LastSyncAt, start)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetRpcRuntimeStatsForTest(t *testing.T) {
|
||||
ResetRpcRuntimeStatsForTest()
|
||||
RecordRpcSuccess("tron")
|
||||
ResetRpcRuntimeStatsForTest()
|
||||
|
||||
stats := SnapshotRpcRuntimeStats()
|
||||
if len(stats) != 0 {
|
||||
t.Fatalf("stats = %#v, want empty after reset", stats)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user