Skip to content

Commit 0efd538

Browse files
committed
Make Metrics configurable & address fixups
1 parent 579ec79 commit 0efd538

8 files changed

Lines changed: 304 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: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,3 +634,63 @@ async fn test_forwarded_payment_event() {
634634

635635
node_c.stop().unwrap();
636636
}
637+
638+
#[tokio::test]
639+
async fn test_metrics_endpoint() {
640+
let bitcoind = TestBitcoind::new();
641+
642+
// Test with metrics enabled
643+
let server_a = LdkServerHandle::start(&bitcoind).await;
644+
let server_b = LdkServerHandle::start(&bitcoind).await;
645+
646+
let client = server_a.client();
647+
let metrics_result = client.get_metrics().await;
648+
649+
assert!(metrics_result.is_ok(), "Expected metrics to succeed when enabled");
650+
let metrics = metrics_result.unwrap();
651+
652+
// Verify initial state
653+
assert!(metrics.contains("ldk_server_total_peers_count 0"));
654+
assert!(metrics.contains("ldk_server_total_payments_count 0"));
655+
assert!(metrics.contains("ldk_server_total_successful_payments_count 0"));
656+
assert!(metrics.contains("ldk_server_total_pending_payments_count 0"));
657+
assert!(metrics.contains("ldk_server_total_failed_payments_count 0"));
658+
assert!(metrics.contains("ldk_server_total_channels_count 0"));
659+
assert!(metrics.contains("ldk_server_total_public_channels_count 0"));
660+
assert!(metrics.contains("ldk_server_total_private_channels_count 0"));
661+
assert!(metrics.contains("ldk_server_total_onchain_balance_sats 0"));
662+
assert!(metrics.contains("ldk_server_spendable_onchain_balance_sats 0"));
663+
assert!(metrics.contains("ldk_server_total_anchor_channels_reserve_sats 0"));
664+
assert!(metrics.contains("ldk_server_total_lightning_balance_sats 0"));
665+
666+
// Set up channel and make a payment to trigger metric update
667+
setup_funded_channel(&bitcoind, &server_a, &server_b, 100_000).await;
668+
669+
let invoice_resp = server_b
670+
.client()
671+
.bolt11_receive(Bolt11ReceiveRequest {
672+
amount_msat: Some(10_000_000),
673+
description: Some(Bolt11InvoiceDescription {
674+
kind: Some(bolt11_invoice_description::Kind::Direct("metrics test".to_string())),
675+
}),
676+
expiry_secs: 3600,
677+
})
678+
.await
679+
.unwrap();
680+
681+
run_cli(&server_a, &["bolt11-send", &invoice_resp.invoice]);
682+
683+
// Wait to receive the PaymentSuccessful event and update metrics
684+
let timeout = Duration::from_secs(30);
685+
let start = std::time::Instant::now();
686+
loop {
687+
let metrics = client.get_metrics().await.unwrap();
688+
if metrics.contains("ldk_server_total_successful_payments_count 1") {
689+
break;
690+
}
691+
if start.elapsed() > timeout {
692+
panic!("Timed out waiting for payment metrics to update");
693+
}
694+
tokio::time::sleep(Duration::from_millis(500)).await;
695+
}
696+
}

ldk-server-client/src/client.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ use ldk_server_protos::api::{
3232
use ldk_server_protos::endpoints::{
3333
BOLT11_RECEIVE_PATH, BOLT11_SEND_PATH, BOLT12_RECEIVE_PATH, BOLT12_SEND_PATH,
3434
CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, DISCONNECT_PEER_PATH, EXPORT_PATHFINDING_SCORES_PATH,
35-
FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_NODE_INFO_PATH, GET_PAYMENT_DETAILS_PATH,
36-
GRAPH_GET_CHANNEL_PATH, GRAPH_GET_NODE_PATH, GRAPH_LIST_CHANNELS_PATH, GRAPH_LIST_NODES_PATH,
37-
LIST_CHANNELS_PATH, LIST_FORWARDED_PAYMENTS_PATH, LIST_PAYMENTS_PATH,
38-
LIST_PEERS_PATH, ONCHAIN_RECEIVE_PATH,
35+
FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_METRICS_PATH, GET_NODE_INFO_PATH,
36+
GET_PAYMENT_DETAILS_PATH, GRAPH_GET_CHANNEL_PATH, GRAPH_GET_NODE_PATH,
37+
GRAPH_LIST_CHANNELS_PATH, GRAPH_LIST_NODES_PATH, LIST_CHANNELS_PATH,
38+
LIST_FORWARDED_PAYMENTS_PATH, LIST_PAYMENTS_PATH, LIST_PEERS_PATH, ONCHAIN_RECEIVE_PATH,
3939
ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, SIGN_MESSAGE_PATH, SPLICE_IN_PATH, SPLICE_OUT_PATH,
4040
SPONTANEOUS_SEND_PATH, UPDATE_CHANNEL_CONFIG_PATH, VERIFY_SIGNATURE_PATH,
4141
};
@@ -396,9 +396,8 @@ impl LdkServerClient {
396396
RequestType::Post => self.client.post(url),
397397
};
398398

399-
let body_for_auth = body.as_deref().unwrap_or(&[]);
400-
401399
let builder = if authenticated {
400+
let body_for_auth = body.as_deref().unwrap_or(&[]);
402401
let auth_header = self.compute_auth_header(body_for_auth);
403402
builder.header("X-Auth", auth_header)
404403
} 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
if let Some(payment_details) = event_node.payment(&payment_id) {
@@ -445,7 +456,7 @@ fn main() {
445456
res = rest_svc_listener.accept() => {
446457
match res {
447458
Ok((stream, _)) => {
448-
let node_service = NodeService::new(Arc::clone(&node), Arc::clone(&paginated_store), api_key.clone(), Arc::clone(&metrics));
459+
let node_service = NodeService::new(Arc::clone(&node), Arc::clone(&paginated_store), api_key.clone(), metrics.clone());
449460
let acceptor = tls_acceptor.clone();
450461
runtime.spawn(async move {
451462
match acceptor.accept(stream).await {

ldk-server/src/service.rs

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ use ldk_node::Node;
2121
use ldk_server_protos::endpoints::{
2222
BOLT11_RECEIVE_PATH, BOLT11_SEND_PATH, BOLT12_RECEIVE_PATH, BOLT12_SEND_PATH,
2323
CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, DISCONNECT_PEER_PATH, EXPORT_PATHFINDING_SCORES_PATH,
24-
FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_NODE_INFO_PATH, GET_PAYMENT_DETAILS_PATH,
25-
GRAPH_GET_CHANNEL_PATH, GRAPH_GET_NODE_PATH, GRAPH_LIST_CHANNELS_PATH, GRAPH_LIST_NODES_PATH,
26-
LIST_CHANNELS_PATH, LIST_FORWARDED_PAYMENTS_PATH, LIST_PAYMENTS_PATH,
27-
LIST_PEERS_PATH, ONCHAIN_RECEIVE_PATH,
24+
FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_METRICS_PATH, GET_NODE_INFO_PATH,
25+
GET_PAYMENT_DETAILS_PATH, GRAPH_GET_CHANNEL_PATH, GRAPH_GET_NODE_PATH,
26+
GRAPH_LIST_CHANNELS_PATH, GRAPH_LIST_NODES_PATH, LIST_CHANNELS_PATH,
27+
LIST_FORWARDED_PAYMENTS_PATH, LIST_PAYMENTS_PATH, LIST_PEERS_PATH, ONCHAIN_RECEIVE_PATH,
2828
ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, SIGN_MESSAGE_PATH, SPLICE_IN_PATH, SPLICE_OUT_PATH,
2929
SPONTANEOUS_SEND_PATH, UPDATE_CHANNEL_CONFIG_PATH, VERIFY_SIGNATURE_PATH,
3030
};
@@ -72,13 +72,13 @@ pub struct NodeService {
7272
node: Arc<Node>,
7373
paginated_kv_store: Arc<dyn PaginatedKVStore>,
7474
api_key: String,
75-
metrics: Arc<Metrics>,
75+
metrics: Option<Arc<Metrics>>,
7676
}
7777

7878
impl NodeService {
7979
pub(crate) fn new(
8080
node: Arc<Node>, paginated_kv_store: Arc<dyn PaginatedKVStore>, api_key: String,
81-
metrics: Arc<Metrics>,
81+
metrics: Option<Arc<Metrics>>,
8282
) -> Self {
8383
Self { node, paginated_kv_store, api_key, metrics }
8484
}
@@ -165,14 +165,26 @@ impl Service<Request<Incoming>> for NodeService {
165165

166166
fn call(&self, req: Request<Incoming>) -> Self::Future {
167167
// Handle metrics endpoint separately to bypass auth and return plain text
168-
if req.uri().path().len() > 1 && &req.uri().path()[1..] == GET_METRICS_PATH {
169-
let metrics = Arc::clone(&self.metrics);
170-
return Box::pin(async move {
171-
Ok(Response::builder()
172-
.header("Content-Type", "text/plain")
173-
.body(Full::new(Bytes::from(metrics.gather_metrics())))
174-
.unwrap())
175-
});
168+
if req.method() == hyper::Method::GET
169+
&& req.uri().path().len() > 1
170+
&& &req.uri().path()[1..] == GET_METRICS_PATH
171+
{
172+
if let Some(metrics) = &self.metrics {
173+
let metrics = Arc::clone(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+
});
180+
} else {
181+
return Box::pin(async move {
182+
Ok(Response::builder()
183+
.status(StatusCode::NOT_FOUND)
184+
.body(Full::new(Bytes::from("Not Found")))
185+
.unwrap())
186+
});
187+
}
176188
}
177189

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

0 commit comments

Comments
 (0)