Skip to content

Commit dcf91d6

Browse files
bmuddhathlorenzDodecahedr0xtaco-paco
authored
Rpc/execution related metrics (#584)
This PR adds some metrics related to RPC methods processing and execution timings, which can provide useful insights into common usage patterns and areas of improvement. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added runtime metrics for RPC requests, WebSocket subscriptions, transaction processing, failed transactions, and preflight behavior; added lightweight subscription guards. * Added human-friendly string helpers for JSON-RPC methods. * **Refactor** * Consolidated and relabeled metrics for finer-grained observability; replaced many legacy per-metric helpers with centralized metric vectors/timers. * **Bug Fixes** * Lowered severity of certain account/ensure failures from error to warn to reduce noisy logs. * **Chores** * Added metrics crate to the workspace and enabled an additional logging feature. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Thorsten Lorenz <[email protected]> Co-authored-by: Dodecahedr0x <[email protected]> Co-authored-by: taco-paco <[email protected]>
1 parent 52580c7 commit dcf91d6

File tree

13 files changed

+199
-238
lines changed

13 files changed

+199
-238
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ jsonrpc-pubsub = "18.0.0"
9191
jsonrpc-ws-server = "18.0.0"
9292
lazy_static = "1.4.0"
9393
libc = "0.2.153"
94-
log = "0.4.20"
94+
log = { version = "0.4.20", features = ["release_max_level_info"] }
9595
lru = "0.16.0"
9696
macrotest = "1"
9797
magic-domain-program = { git = "https://github.com/magicblock-labs/magic-domain-program.git", rev = "ea04d46", default-features = false }

magicblock-aperture/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ magicblock-chainlink = { workspace = true }
3333
magicblock-config = { workspace = true }
3434
magicblock-core = { workspace = true }
3535
magicblock-ledger = { workspace = true }
36+
magicblock-metrics = { workspace = true }
3637
magicblock-version = { workspace = true }
3738

3839
# solana

magicblock-aperture/src/requests/http/mod.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use log::*;
1010
use magicblock_core::{
1111
link::transactions::SanitizeableTransaction, traits::AccountsBank,
1212
};
13+
use magicblock_metrics::metrics::ENSURE_ACCOUNTS_TIME;
1314
use prelude::JsonBody;
1415
use solana_account::AccountSharedData;
1516
use solana_pubkey::Pubkey;
@@ -101,15 +102,17 @@ impl HttpDispatcher {
101102
&self,
102103
pubkey: &Pubkey,
103104
) -> Option<AccountSharedData> {
104-
debug!("Ensuring account {pubkey}");
105+
let _timer = ENSURE_ACCOUNTS_TIME
106+
.with_label_values(&["account"])
107+
.start_timer();
105108
let _ = self
106109
.chainlink
107110
.ensure_accounts(&[*pubkey], None)
108111
.await
109112
.inspect_err(|e| {
110113
// There is nothing we can do if fetching the account fails
111114
// Log the error and return whatever is in the accounts db
112-
error!("Failed to ensure account {pubkey}: {e}");
115+
warn!("Failed to ensure account {pubkey}: {e}");
113116
});
114117
self.accountsdb.get_account(pubkey)
115118
}
@@ -120,22 +123,18 @@ impl HttpDispatcher {
120123
&self,
121124
pubkeys: &[Pubkey],
122125
) -> Vec<Option<AccountSharedData>> {
123-
if log::log_enabled!(log::Level::Debug) {
124-
let pubkeys = pubkeys
125-
.iter()
126-
.map(ToString::to_string)
127-
.collect::<Vec<_>>()
128-
.join(", ");
129-
debug!("Ensuring accounts {pubkeys}");
130-
}
126+
trace!("Ensuring accounts {pubkeys:?}");
127+
let _timer = ENSURE_ACCOUNTS_TIME
128+
.with_label_values(&["multi-account"])
129+
.start_timer();
131130
let _ = self
132131
.chainlink
133132
.ensure_accounts(pubkeys, None)
134133
.await
135134
.inspect_err(|e| {
136135
// There is nothing we can do if fetching the accounts fails
137136
// Log the error and return whatever is in the accounts db
138-
error!("Failed to ensure accounts: {e}");
137+
warn!("Failed to ensure accounts: {e}");
139138
});
140139
pubkeys
141140
.iter()
@@ -195,6 +194,9 @@ impl HttpDispatcher {
195194
&self,
196195
transaction: &SanitizedTransaction,
197196
) -> RpcResult<()> {
197+
let _timer = ENSURE_ACCOUNTS_TIME
198+
.with_label_values(&["transaction"])
199+
.start_timer();
198200
match self
199201
.chainlink
200202
.ensure_transaction_accounts(transaction)

magicblock-aperture/src/requests/http/send_transaction.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use log::*;
1+
use log::{trace, warn};
2+
use magicblock_metrics::metrics::{
3+
TRANSACTION_PROCESSING_TIME, TRANSACTION_SKIP_PREFLIGHT,
4+
};
25
use solana_rpc_client_api::config::RpcSendTransactionConfig;
36
use solana_transaction_error::TransactionError;
47
use solana_transaction_status::UiTransactionEncoding;
@@ -15,8 +18,10 @@ impl HttpDispatcher {
1518
&self,
1619
request: &mut JsonRequest,
1720
) -> HandlerResult {
21+
let _timer = TRANSACTION_PROCESSING_TIME.start_timer();
1822
let (transaction_str, config) =
1923
parse_params!(request.params()?, String, RpcSendTransactionConfig);
24+
2025
let transaction_str: String = some_or_err!(transaction_str);
2126
let config = config.unwrap_or_default();
2227
let encoding = config.encoding.unwrap_or(UiTransactionEncoding::Base58);
@@ -35,16 +40,16 @@ impl HttpDispatcher {
3540
{
3641
return Err(TransactionError::AlreadyProcessed.into());
3742
}
38-
debug!("Received transaction: {signature}, ensuring accounts");
3943
self.ensure_transaction_accounts(&transaction).await?;
4044

4145
// Based on the preflight flag, either execute and await the result,
4246
// or schedule (fire-and-forget) for background processing.
4347
if config.skip_preflight {
44-
trace!("Scheduling transaction: {signature}");
48+
TRANSACTION_SKIP_PREFLIGHT.inc();
4549
self.transactions_scheduler.schedule(transaction).await?;
50+
trace!("Scheduling transaction {signature}");
4651
} else {
47-
trace!("Executing transaction: {signature}");
52+
trace!("Executing transaction {signature}");
4853
self.transactions_scheduler.execute(transaction).await?;
4954
}
5055

magicblock-aperture/src/requests/mod.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,84 @@ pub(crate) enum JsonRpcWsMethod {
8888
SlotUnsubscribe,
8989
}
9090

91+
impl JsonRpcHttpMethod {
92+
pub(crate) fn as_str(&self) -> &'static str {
93+
match self {
94+
JsonRpcHttpMethod::GetAccountInfo => "getAccountInfo",
95+
JsonRpcHttpMethod::GetBalance => "getBalance",
96+
JsonRpcHttpMethod::GetBlock => "getBlock",
97+
JsonRpcHttpMethod::GetBlockCommitment => "getBlockCommitment",
98+
JsonRpcHttpMethod::GetBlockHeight => "getBlockHeight",
99+
JsonRpcHttpMethod::GetBlockTime => "getBlockTime",
100+
JsonRpcHttpMethod::GetBlocks => "getBlocks",
101+
JsonRpcHttpMethod::GetBlocksWithLimit => "getBlocksWithLimit",
102+
JsonRpcHttpMethod::GetClusterNodes => "getClusterNodes",
103+
JsonRpcHttpMethod::GetEpochInfo => "getEpochInfo",
104+
JsonRpcHttpMethod::GetEpochSchedule => "getEpochSchedule",
105+
JsonRpcHttpMethod::GetFeeForMessage => "getFeeForMessage",
106+
JsonRpcHttpMethod::GetFirstAvailableBlock => {
107+
"getFirstAvailableBlock"
108+
}
109+
JsonRpcHttpMethod::GetGenesisHash => "getGenesisHash",
110+
JsonRpcHttpMethod::GetHealth => "getHealth",
111+
JsonRpcHttpMethod::GetHighestSnapshotSlot => {
112+
"getHighestSnapshotSlot"
113+
}
114+
JsonRpcHttpMethod::GetIdentity => "getIdentity",
115+
JsonRpcHttpMethod::GetLargestAccounts => "getLargestAccounts",
116+
JsonRpcHttpMethod::GetLatestBlockhash => "getLatestBlockhash",
117+
JsonRpcHttpMethod::GetMultipleAccounts => "getMultipleAccounts",
118+
JsonRpcHttpMethod::GetProgramAccounts => "getProgramAccounts",
119+
JsonRpcHttpMethod::GetSignatureStatuses => "getSignatureStatuses",
120+
JsonRpcHttpMethod::GetSignaturesForAddress => {
121+
"getSignaturesForAddress"
122+
}
123+
JsonRpcHttpMethod::GetSlot => "getSlot",
124+
JsonRpcHttpMethod::GetSlotLeader => "getSlotLeader",
125+
JsonRpcHttpMethod::GetSlotLeaders => "getSlotLeaders",
126+
JsonRpcHttpMethod::GetSupply => "getSupply",
127+
JsonRpcHttpMethod::GetTokenAccountBalance => {
128+
"getTokenAccountBalance"
129+
}
130+
JsonRpcHttpMethod::GetTokenAccountsByDelegate => {
131+
"getTokenAccountsByDelegate"
132+
}
133+
JsonRpcHttpMethod::GetTokenAccountsByOwner => {
134+
"getTokenAccountsByOwner"
135+
}
136+
JsonRpcHttpMethod::GetTokenLargestAccounts => {
137+
"getTokenLargestAccounts"
138+
}
139+
JsonRpcHttpMethod::GetTokenSupply => "getTokenSupply",
140+
JsonRpcHttpMethod::GetTransaction => "getTransaction",
141+
JsonRpcHttpMethod::GetTransactionCount => "getTransactionCount",
142+
JsonRpcHttpMethod::GetVersion => "getVersion",
143+
JsonRpcHttpMethod::IsBlockhashValid => "isBlockhashValid",
144+
JsonRpcHttpMethod::MinimumLedgerSlot => "minimumLedgerSlot",
145+
JsonRpcHttpMethod::RequestAirdrop => "requestAirdrop",
146+
JsonRpcHttpMethod::SendTransaction => "sendTransaction",
147+
JsonRpcHttpMethod::SimulateTransaction => "simulateTransaction",
148+
}
149+
}
150+
}
151+
152+
impl JsonRpcWsMethod {
153+
pub(crate) fn as_str(&self) -> &'static str {
154+
match self {
155+
JsonRpcWsMethod::AccountSubscribe => "accountSubscribe",
156+
JsonRpcWsMethod::AccountUnsubscribe => "accountUnsubscribe",
157+
JsonRpcWsMethod::LogsSubscribe => "logsSubscribe",
158+
JsonRpcWsMethod::LogsUnsubscribe => "logsUnsubscribe",
159+
JsonRpcWsMethod::ProgramSubscribe => "programSubscribe",
160+
JsonRpcWsMethod::ProgramUnsubscribe => "programUnsubscribe",
161+
JsonRpcWsMethod::SignatureSubscribe => "signatureSubscribe",
162+
JsonRpcWsMethod::SignatureUnsubscribe => "signatureUnsubscribe",
163+
JsonRpcWsMethod::SlotSubscribe => "slotSubscribe",
164+
JsonRpcWsMethod::SlotUnsubscribe => "slotUnsubscribe",
165+
}
166+
}
167+
}
168+
91169
/// A helper macro for easily parsing positional parameters from a JSON-RPC request.
92170
///
93171
/// This macro simplifies the process of extracting and deserializing parameters

magicblock-aperture/src/server/http/dispatch.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ use magicblock_core::link::{
66
transactions::TransactionSchedulerHandle, DispatchEndpoints,
77
};
88
use magicblock_ledger::Ledger;
9+
use magicblock_metrics::metrics::{
10+
RPC_REQUESTS_COUNT, RPC_REQUEST_HANDLING_TIME,
11+
};
912

1013
use crate::{
1114
requests::{
@@ -111,6 +114,11 @@ impl HttpDispatcher {
111114
async fn process(&self, request: &mut JsonHttpRequest) -> HandlerResult {
112115
// Route the request to the correct handler based on the method name.
113116
use crate::requests::JsonRpcHttpMethod::*;
117+
let method = request.method.as_str();
118+
RPC_REQUESTS_COUNT.with_label_values(&[method]).inc();
119+
let _timer = RPC_REQUEST_HANDLING_TIME
120+
.with_label_values(&[method])
121+
.start_timer();
114122
match request.method {
115123
GetAccountInfo => self.get_account_info(request).await,
116124
GetBalance => self.get_balance(request).await,

magicblock-aperture/src/server/websocket/dispatch.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::collections::HashMap;
22

33
use hyper::body::Bytes;
44
use json::{Serialize, Value};
5+
use magicblock_metrics::metrics::RPC_REQUESTS_COUNT;
56
use tokio::sync::mpsc;
67

78
use super::connection::ConnectionID;
@@ -65,6 +66,9 @@ impl WsDispatcher {
6566
request: &mut JsonWsRequest,
6667
) -> RpcResult<WsDispatchResult> {
6768
use JsonRpcWsMethod::*;
69+
RPC_REQUESTS_COUNT
70+
.with_label_values(&[request.method.as_str()])
71+
.inc();
6872
let result = match request.method {
6973
AccountSubscribe => self.account_subscribe(request).await,
7074
ProgramSubscribe => self.program_subscribe(request).await,

magicblock-aperture/src/state/subscriptions.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use magicblock_core::{
1515
},
1616
Slot,
1717
};
18+
use magicblock_metrics::metrics::RPC_WS_SUBSCRIPTIONS_COUNT;
1819
use parking_lot::RwLock;
1920
use solana_account::ReadableAccount;
2021
use solana_pubkey::Pubkey;
@@ -105,7 +106,7 @@ impl SubscriptionsDb {
105106
.await
106107
.or_insert_with(|| UpdateSubscribers(vec![]))
107108
.add_subscriber(chan, encoder.clone());
108-
109+
let metric = SubMetricGuard::new("account-subscribe");
109110
// Create a cleanup future that will be executed when the handle is dropped.
110111
let accounts = self.accounts.clone();
111112
let callback = async move {
@@ -116,6 +117,7 @@ impl SubscriptionsDb {
116117
if entry.remove_subscriber(conid, &encoder) {
117118
let _ = entry.remove();
118119
}
120+
drop(metric)
119121
};
120122
let cleanup = CleanUp(Some(Box::pin(callback)));
121123
SubscriptionHandle { id, cleanup }
@@ -146,13 +148,15 @@ impl SubscriptionsDb {
146148
.add_subscriber(chan, encoder.clone());
147149

148150
let programs = self.programs.clone();
151+
let metric = SubMetricGuard::new("program-subscribe");
149152
let callback = async move {
150153
let Some(mut entry) = programs.get_async(&pubkey).await else {
151154
return;
152155
};
153156
if entry.remove_subscriber(conid, &encoder) {
154157
let _ = entry.remove();
155158
}
159+
drop(metric)
156160
};
157161
let cleanup = CleanUp(Some(Box::pin(callback)));
158162
SubscriptionHandle { id, cleanup }
@@ -212,8 +216,10 @@ impl SubscriptionsDb {
212216
let id = self.logs.write().add_subscriber(chan, encoder.clone());
213217

214218
let logs = self.logs.clone();
219+
let metric = SubMetricGuard::new("logs-subscribe");
215220
let callback = async move {
216221
logs.write().remove_subscriber(conid, &encoder);
222+
drop(metric)
217223
};
218224
let cleanup = CleanUp(Some(Box::pin(callback)));
219225
SubscriptionHandle { id, cleanup }
@@ -239,8 +245,10 @@ impl SubscriptionsDb {
239245
let id = subscriber.id;
240246

241247
let slot = self.slot.clone();
248+
let metric = SubMetricGuard::new("slot-subscribe");
242249
let callback = async move {
243250
slot.write().txs.remove(&conid);
251+
drop(metric)
244252
};
245253
let cleanup = CleanUp(Some(Box::pin(callback)));
246254
SubscriptionHandle { id, cleanup }
@@ -391,3 +399,20 @@ impl<E> Drop for UpdateSubscriber<E> {
391399
self.live.store(false, Ordering::Relaxed);
392400
}
393401
}
402+
403+
pub(crate) struct SubMetricGuard(&'static str);
404+
405+
impl SubMetricGuard {
406+
pub(crate) fn new(name: &'static str) -> Self {
407+
RPC_WS_SUBSCRIPTIONS_COUNT.with_label_values(&[name]).inc();
408+
Self(name)
409+
}
410+
}
411+
412+
impl Drop for SubMetricGuard {
413+
fn drop(&mut self) {
414+
RPC_WS_SUBSCRIPTIONS_COUNT
415+
.with_label_values(&[self.0])
416+
.dec();
417+
}
418+
}

0 commit comments

Comments
 (0)