Skip to content

Commit 6856ef9

Browse files
committed
Make Metrics configurable & address fixups
1 parent 70dceb2 commit 6856ef9

8 files changed

Lines changed: 329 additions & 84 deletions

File tree

e2e-tests/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ max_client_to_self_delay = 1024
140140
min_payment_size_msat = 0
141141
max_payment_size_msat = 1000000000
142142
client_trusts_lsp = true
143+
144+
[metrics]
145+
enabled = true
143146
"#,
144147
storage_dir = storage_dir.display(),
145148
);

e2e-tests/tests/e2e.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,3 +778,88 @@ async fn test_hodl_invoice_fail() {
778778
events_a.iter().map(|e| &e.event).collect::<Vec<_>>()
779779
);
780780
}
781+
782+
#[tokio::test]
783+
async fn test_metrics_endpoint() {
784+
let bitcoind = TestBitcoind::new();
785+
786+
// Test with metrics enabled
787+
let server_a = LdkServerHandle::start(&bitcoind).await;
788+
let server_b = LdkServerHandle::start(&bitcoind).await;
789+
790+
let client = server_a.client();
791+
let metrics_result = client.get_metrics().await;
792+
793+
assert!(metrics_result.is_ok(), "Expected metrics to succeed when enabled");
794+
let metrics = metrics_result.unwrap();
795+
796+
// Verify initial state
797+
assert!(metrics.contains("ldk_server_total_peers_count 0"));
798+
assert!(metrics.contains("ldk_server_total_payments_count 0"));
799+
assert!(metrics.contains("ldk_server_total_successful_payments_count 0"));
800+
assert!(metrics.contains("ldk_server_total_pending_payments_count 0"));
801+
assert!(metrics.contains("ldk_server_total_failed_payments_count 0"));
802+
assert!(metrics.contains("ldk_server_total_channels_count 0"));
803+
assert!(metrics.contains("ldk_server_total_public_channels_count 0"));
804+
assert!(metrics.contains("ldk_server_total_private_channels_count 0"));
805+
assert!(metrics.contains("ldk_server_total_onchain_balance_sats 0"));
806+
assert!(metrics.contains("ldk_server_spendable_onchain_balance_sats 0"));
807+
assert!(metrics.contains("ldk_server_total_anchor_channels_reserve_sats 0"));
808+
assert!(metrics.contains("ldk_server_total_lightning_balance_sats 0"));
809+
810+
// Set up channel and make a payment to trigger metric update
811+
setup_funded_channel(&bitcoind, &server_a, &server_b, 100_000).await;
812+
813+
// We need to poll here because the metrics below are poll-based and
814+
// updates every 60 seconds.
815+
let timeout = Duration::from_secs(65);
816+
let start = std::time::Instant::now();
817+
loop {
818+
let metrics = client.get_metrics().await.unwrap();
819+
if metrics.contains("ldk_server_total_peers_count 1")
820+
&& metrics.contains("ldk_server_total_channels_count 1")
821+
&& metrics.contains("ldk_server_total_public_channels_count 1")
822+
&& metrics.contains("ldk_server_total_payments_count 2")
823+
&& !metrics.contains("ldk_server_total_lightning_balance_sats 0")
824+
&& !metrics.contains("ldk_server_total_onchain_balance_sats 0")
825+
&& !metrics.contains("ldk_server_spendable_onchain_balance_sats 0")
826+
&& !metrics.contains("ldk_server_total_anchor_channels_reserve_sats 0")
827+
{
828+
break;
829+
}
830+
831+
if start.elapsed() > timeout {
832+
panic!("Timed out waiting for channel/peer metrics to update",);
833+
}
834+
tokio::time::sleep(Duration::from_secs(1)).await;
835+
}
836+
837+
// Make a payment to trigger payment metric updates.
838+
let invoice_resp = server_b
839+
.client()
840+
.bolt11_receive(Bolt11ReceiveRequest {
841+
amount_msat: Some(10_000_000),
842+
description: Some(Bolt11InvoiceDescription {
843+
kind: Some(bolt11_invoice_description::Kind::Direct("metrics test".to_string())),
844+
}),
845+
expiry_secs: 3600,
846+
})
847+
.await
848+
.unwrap();
849+
850+
run_cli(&server_a, &["bolt11-send", &invoice_resp.invoice]);
851+
852+
// Wait to receive the PaymentSuccessful event and update metrics
853+
let timeout = Duration::from_secs(30);
854+
let start = std::time::Instant::now();
855+
loop {
856+
let metrics = client.get_metrics().await.unwrap();
857+
if metrics.contains("ldk_server_total_successful_payments_count 1") {
858+
break;
859+
}
860+
if start.elapsed() > timeout {
861+
panic!("Timed out waiting for payment metrics to update");
862+
}
863+
tokio::time::sleep(Duration::from_millis(500)).await;
864+
}
865+
}

ldk-server-client/src/client.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ use ldk_server_protos::endpoints::{
3535
BOLT11_CLAIM_FOR_HASH_PATH, BOLT11_FAIL_FOR_HASH_PATH, BOLT11_RECEIVE_FOR_HASH_PATH,
3636
BOLT11_RECEIVE_PATH, BOLT11_SEND_PATH, BOLT12_RECEIVE_PATH, BOLT12_SEND_PATH,
3737
CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, DISCONNECT_PEER_PATH, EXPORT_PATHFINDING_SCORES_PATH,
38-
FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_NODE_INFO_PATH, GET_PAYMENT_DETAILS_PATH,
39-
GRAPH_GET_CHANNEL_PATH, GRAPH_GET_NODE_PATH, GRAPH_LIST_CHANNELS_PATH, GRAPH_LIST_NODES_PATH,
40-
LIST_CHANNELS_PATH, LIST_FORWARDED_PAYMENTS_PATH, LIST_PAYMENTS_PATH,
41-
LIST_PEERS_PATH, ONCHAIN_RECEIVE_PATH,
38+
FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_METRICS_PATH, GET_NODE_INFO_PATH,
39+
GET_PAYMENT_DETAILS_PATH, GRAPH_GET_CHANNEL_PATH, GRAPH_GET_NODE_PATH,
40+
GRAPH_LIST_CHANNELS_PATH, GRAPH_LIST_NODES_PATH, LIST_CHANNELS_PATH,
41+
LIST_FORWARDED_PAYMENTS_PATH, LIST_PAYMENTS_PATH, LIST_PEERS_PATH, ONCHAIN_RECEIVE_PATH,
4242
ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, SIGN_MESSAGE_PATH, SPLICE_IN_PATH, SPLICE_OUT_PATH,
4343
SPONTANEOUS_SEND_PATH, UPDATE_CHANNEL_CONFIG_PATH, VERIFY_SIGNATURE_PATH,
4444
};
@@ -427,9 +427,8 @@ impl LdkServerClient {
427427
RequestType::Post => self.client.post(url),
428428
};
429429

430-
let body_for_auth = body.as_deref().unwrap_or(&[]);
431-
432430
let builder = if authenticated {
431+
let body_for_auth = body.as_deref().unwrap_or(&[]);
433432
let auth_header = self.compute_auth_header(body_for_auth);
434433
builder.header("X-Auth", auth_header)
435434
} else {

ldk-server/ldk-server-config.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,7 @@ client_trusts_lsp = false
7979
## A token we may require to be sent by the clients.
8080
## If set, only requests matching this token will be accepted. (uncomment and set if required)
8181
# require_token = ""
82+
83+
# Metrics settings
84+
[metrics]
85+
enabled = false

ldk-server/src/main.rs

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -267,18 +267,25 @@ fn main() {
267267
};
268268
let event_node = Arc::clone(&node);
269269

270-
let metrics_node = Arc::clone(&node);
271-
let mut interval = tokio::time::interval(BUILD_METRICS_INTERVAL);
272-
let metrics = Arc::new(Metrics::new());
273-
let metrics_bg = Arc::clone(&metrics);
274-
let event_metrics = Arc::clone(&metrics);
275-
276-
runtime.spawn(async move {
277-
loop {
278-
interval.tick().await;
279-
metrics_bg.update_all_pollable_metrics(&metrics_node);
280-
}
281-
});
270+
let metrics: Option<Arc<Metrics>> = if config_file.metrics_enabled {
271+
let metrics_node = Arc::clone(&node);
272+
let mut interval = tokio::time::interval(BUILD_METRICS_INTERVAL);
273+
let metrics = Arc::new(Metrics::new());
274+
let metrics_bg = Arc::clone(&metrics);
275+
276+
// Initialize metrics that are event-driven to ensure they start with correct values from persistence
277+
metrics.initialize_payment_metrics(&metrics_node);
278+
279+
runtime.spawn(async move {
280+
loop {
281+
interval.tick().await;
282+
metrics_bg.update_all_pollable_metrics(&metrics_node);
283+
}
284+
});
285+
Some(metrics)
286+
} else {
287+
None
288+
};
282289

283290
let rest_svc_listener = TcpListener::bind(config_file.rest_service_addr)
284291
.await
@@ -347,7 +354,9 @@ fn main() {
347354
Arc::clone(&event_publisher),
348355
Arc::clone(&paginated_store)).await;
349356

350-
event_metrics.update_total_successful_payments_count(&event_node);
357+
if let Some(metrics) = &metrics {
358+
metrics.update_payments_count(true);
359+
}
351360
},
352361
Event::PaymentFailed {payment_id, ..} => {
353362
let payment_id = payment_id.expect("PaymentId expected for ldk-server >=0.1");
@@ -360,7 +369,9 @@ fn main() {
360369
Arc::clone(&event_publisher),
361370
Arc::clone(&paginated_store)).await;
362371

363-
event_metrics.update_total_failed_payments_count(&event_node);
372+
if let Some(metrics) = &metrics {
373+
metrics.update_payments_count(false);
374+
}
364375
},
365376
Event::PaymentClaimable {payment_id, ..} => {
366377
publish_event_and_upsert_payment(&payment_id,
@@ -446,7 +457,7 @@ fn main() {
446457
res = rest_svc_listener.accept() => {
447458
match res {
448459
Ok((stream, _)) => {
449-
let node_service = NodeService::new(Arc::clone(&node), Arc::clone(&paginated_store), api_key.clone(), Arc::clone(&metrics));
460+
let node_service = NodeService::new(Arc::clone(&node), Arc::clone(&paginated_store), api_key.clone(), metrics.clone());
450461
let acceptor = tls_acceptor.clone();
451462
runtime.spawn(async move {
452463
match acceptor.accept(stream).await {

ldk-server/src/service.rs

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ use ldk_server_protos::endpoints::{
2222
BOLT11_CLAIM_FOR_HASH_PATH, BOLT11_FAIL_FOR_HASH_PATH, BOLT11_RECEIVE_FOR_HASH_PATH,
2323
BOLT11_RECEIVE_PATH, BOLT11_SEND_PATH, BOLT12_RECEIVE_PATH, BOLT12_SEND_PATH,
2424
CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, DISCONNECT_PEER_PATH, EXPORT_PATHFINDING_SCORES_PATH,
25-
FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_NODE_INFO_PATH, GET_PAYMENT_DETAILS_PATH,
26-
GRAPH_GET_CHANNEL_PATH, GRAPH_GET_NODE_PATH, GRAPH_LIST_CHANNELS_PATH, GRAPH_LIST_NODES_PATH,
27-
LIST_CHANNELS_PATH, LIST_FORWARDED_PAYMENTS_PATH, LIST_PAYMENTS_PATH,
28-
LIST_PEERS_PATH, ONCHAIN_RECEIVE_PATH,
25+
FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_METRICS_PATH, GET_NODE_INFO_PATH,
26+
GET_PAYMENT_DETAILS_PATH, GRAPH_GET_CHANNEL_PATH, GRAPH_GET_NODE_PATH,
27+
GRAPH_LIST_CHANNELS_PATH, GRAPH_LIST_NODES_PATH, LIST_CHANNELS_PATH,
28+
LIST_FORWARDED_PAYMENTS_PATH, LIST_PAYMENTS_PATH, LIST_PEERS_PATH, ONCHAIN_RECEIVE_PATH,
2929
ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, SIGN_MESSAGE_PATH, SPLICE_IN_PATH, SPLICE_OUT_PATH,
3030
SPONTANEOUS_SEND_PATH, UPDATE_CHANNEL_CONFIG_PATH, VERIFY_SIGNATURE_PATH,
3131
};
@@ -76,13 +76,13 @@ pub struct NodeService {
7676
node: Arc<Node>,
7777
paginated_kv_store: Arc<dyn PaginatedKVStore>,
7878
api_key: String,
79-
metrics: Arc<Metrics>,
79+
metrics: Option<Arc<Metrics>>,
8080
}
8181

8282
impl NodeService {
8383
pub(crate) fn new(
8484
node: Arc<Node>, paginated_kv_store: Arc<dyn PaginatedKVStore>, api_key: String,
85-
metrics: Arc<Metrics>,
85+
metrics: Option<Arc<Metrics>>,
8686
) -> Self {
8787
Self { node, paginated_kv_store, api_key, metrics }
8888
}
@@ -169,14 +169,26 @@ impl Service<Request<Incoming>> for NodeService {
169169

170170
fn call(&self, req: Request<Incoming>) -> Self::Future {
171171
// Handle metrics endpoint separately to bypass auth and return plain text
172-
if req.uri().path().len() > 1 && &req.uri().path()[1..] == GET_METRICS_PATH {
173-
let metrics = Arc::clone(&self.metrics);
174-
return Box::pin(async move {
175-
Ok(Response::builder()
176-
.header("Content-Type", "text/plain")
177-
.body(Full::new(Bytes::from(metrics.gather_metrics())))
178-
.unwrap())
179-
});
172+
if req.method() == hyper::Method::GET
173+
&& req.uri().path().len() > 1
174+
&& &req.uri().path()[1..] == GET_METRICS_PATH
175+
{
176+
if let Some(metrics) = &self.metrics {
177+
let metrics = Arc::clone(metrics);
178+
return Box::pin(async move {
179+
Ok(Response::builder()
180+
.header("Content-Type", "text/plain")
181+
.body(Full::new(Bytes::from(metrics.gather_metrics())))
182+
.unwrap())
183+
});
184+
} else {
185+
return Box::pin(async move {
186+
Ok(Response::builder()
187+
.status(StatusCode::NOT_FOUND)
188+
.body(Full::new(Bytes::from("Not Found")))
189+
.unwrap())
190+
});
191+
}
180192
}
181193

182194
// Extract auth params from headers (validation happens after body is read)

0 commit comments

Comments
 (0)