iroh_http_core/endpoint/
observe.rs1use std::sync::atomic::Ordering;
7
8use iroh::endpoint::TransportAddrUsage;
9
10use super::{
11 stats::{EndpointStats, NodeAddrInfo, PathInfo, PeerStats},
12 IrohEndpoint,
13};
14
15impl IrohEndpoint {
16 pub fn endpoint_stats(&self) -> EndpointStats {
20 let (active_readers, active_writers, active_sessions, total_handles) =
21 self.inner.ffi.handles.count_handles();
22 let pool_size = self.inner.http.pool.entry_count_approx() as usize;
23 let active_connections = self.inner.http.active_connections.load(Ordering::Relaxed);
24 let active_requests = self.inner.http.active_requests.load(Ordering::Relaxed);
25 EndpointStats {
26 active_readers,
27 active_writers,
28 active_sessions,
29 total_handles,
30 pool_size,
31 active_connections,
32 active_requests,
33 }
34 }
35
36 pub fn bound_sockets(&self) -> Vec<std::net::SocketAddr> {
38 self.inner.transport.ep.bound_sockets()
39 }
40
41 pub fn node_addr(&self) -> NodeAddrInfo {
43 let addr = self.inner.transport.ep.addr();
44 let mut addrs = Vec::new();
45 for relay in addr.relay_urls() {
46 addrs.push(relay.to_string());
47 }
48 for da in addr.ip_addrs() {
49 addrs.push(da.to_string());
50 }
51 NodeAddrInfo {
52 id: self.inner.transport.node_id_str.clone(),
53 addrs,
54 }
55 }
56
57 pub fn home_relay(&self) -> Option<String> {
59 self.inner
60 .transport
61 .ep
62 .addr()
63 .relay_urls()
64 .next()
65 .map(|u| u.to_string())
66 }
67
68 pub async fn peer_info(&self, node_id_b32: &str) -> Option<NodeAddrInfo> {
70 let bytes = crate::base32_decode(node_id_b32).ok()?;
71 let arr: [u8; 32] = bytes.try_into().ok()?;
72 let pk = iroh::PublicKey::from_bytes(&arr).ok()?;
73 let info = self.inner.transport.ep.remote_info(pk).await?;
74 let id = crate::base32_encode(info.id().as_bytes());
75 let mut addrs = Vec::new();
76 for a in info.addrs() {
77 match a.addr() {
78 iroh::TransportAddr::Ip(sock) => addrs.push(sock.to_string()),
79 iroh::TransportAddr::Relay(url) => addrs.push(url.to_string()),
80 other => addrs.push(format!("{:?}", other)),
81 }
82 }
83 Some(NodeAddrInfo { id, addrs })
84 }
85
86 pub async fn peer_stats(&self, node_id_b32: &str) -> Option<PeerStats> {
91 let bytes = crate::base32_decode(node_id_b32).ok()?;
92 let arr: [u8; 32] = bytes.try_into().ok()?;
93 let pk = iroh::PublicKey::from_bytes(&arr).ok()?;
94 let info = self.inner.transport.ep.remote_info(pk).await?;
95
96 let mut paths = Vec::new();
97 let mut has_active_relay = false;
98 let mut active_relay_url: Option<String> = None;
99
100 for a in info.addrs() {
101 let is_relay = a.addr().is_relay();
102 let is_active = matches!(a.usage(), TransportAddrUsage::Active);
103
104 let addr_str = match a.addr() {
105 iroh::TransportAddr::Ip(sock) => sock.to_string(),
106 iroh::TransportAddr::Relay(url) => {
107 if is_active {
108 has_active_relay = true;
109 active_relay_url = Some(url.to_string());
110 }
111 url.to_string()
112 }
113 other => format!("{:?}", other),
114 };
115
116 paths.push(PathInfo {
117 relay: is_relay,
118 addr: addr_str,
119 active: is_active,
120 });
121 }
122
123 let (rtt_ms, bytes_sent, bytes_received, lost_packets, sent_packets, congestion_window) =
125 if let Some(pooled) = self.inner.http.pool.get_existing(pk, crate::ALPN).await {
126 let s = pooled.conn.stats();
127 let rtt = pooled.conn.rtt(iroh::endpoint::PathId::ZERO);
128 (
129 rtt.map(|d| d.as_secs_f64() * 1000.0),
130 Some(s.udp_tx.bytes),
131 Some(s.udp_rx.bytes),
132 None,
133 None,
134 None,
135 )
136 } else {
137 (None, None, None, None, None, None)
138 };
139
140 Some(PeerStats {
141 relay: has_active_relay,
142 relay_url: active_relay_url,
143 paths,
144 rtt_ms,
145 bytes_sent,
146 bytes_received,
147 lost_packets,
148 sent_packets,
149 congestion_window,
150 })
151 }
152
153 pub fn subscribe_path_changes(
159 &self,
160 node_id_str: &str,
161 ) -> tokio::sync::mpsc::UnboundedReceiver<PathInfo> {
162 let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
163 self.inner
165 .session
166 .path_subs
167 .insert(node_id_str.to_string(), tx);
168
169 let ep = self.clone();
170 let nid = node_id_str.to_string();
171 let event_tx = self.inner.session.event_tx.clone();
172
173 tokio::spawn(async move {
174 let mut last_key: Option<String> = None;
175 let mut closed_rx = ep.inner.session.closed_rx.clone();
176 loop {
177 if *closed_rx.borrow() {
179 ep.inner.session.path_subs.remove(&nid);
180 break;
181 }
182 let is_closed = ep
183 .inner
184 .session
185 .path_subs
186 .get(&nid)
187 .map(|s| s.is_closed())
188 .unwrap_or(true);
189 if is_closed {
190 ep.inner.session.path_subs.remove(&nid);
191 break;
192 }
193
194 if let Some(stats) = ep.peer_stats(&nid).await {
195 if let Some(active) = stats.paths.iter().find(|p| p.active) {
196 let key = format!("{}:{}", active.relay, active.addr);
197 if Some(&key) != last_key.as_ref() {
198 last_key = Some(key);
199 if let Some(sender) = ep.inner.session.path_subs.get(&nid) {
200 let _ = sender.send(active.clone());
201 }
202 let _ = event_tx.try_send(
203 crate::http::events::TransportEvent::path_change(
204 &nid,
205 &active.addr,
206 active.relay,
207 ),
208 );
209 }
210 }
211 }
212
213 tokio::select! {
215 _ = tokio::time::sleep(std::time::Duration::from_millis(200)) => {}
216 result = closed_rx.wait_for(|v| *v) => {
217 let _ = result;
218 ep.inner.session.path_subs.remove(&nid);
219 break;
220 }
221 }
222 }
223 });
224
225 rx
226 }
227}