Skip to content

Commit 531acf4

Browse files
chore: refactor to RoutesStats
1 parent 0f9ff48 commit 531acf4

File tree

5 files changed

+72
-53
lines changed

5 files changed

+72
-53
lines changed

Diff for: ic-agent/src/agent/route_provider.rs

+27-15
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,24 @@ const ICP0_SUB_DOMAIN: &str = ".icp0.io";
3636
const ICP_API_SUB_DOMAIN: &str = ".icp-api.io";
3737
const LOCALHOST_SUB_DOMAIN: &str = ".localhost";
3838

39+
/// Statistical info about routing urls.
40+
#[derive(Debug, PartialEq)]
41+
pub struct RoutesStats {
42+
/// Total number of existing routes (both healthy and unhealthy).
43+
pub total: usize,
44+
45+
/// Number of currently healthy routes, or None if health status information is unavailable.
46+
/// A healthy route is one that is available and ready to receive traffic.
47+
/// The specific criteria for what constitutes a "healthy" route is implementation dependent.
48+
pub healthy: Option<usize>,
49+
}
50+
51+
impl RoutesStats {
52+
fn new(total: usize, healthy: Option<usize>) -> Self {
53+
Self { total, healthy }
54+
}
55+
}
56+
3957
/// A [`RouteProvider`] for dynamic generation of routing urls.
4058
pub trait RouteProvider: std::fmt::Debug + Send + Sync {
4159
/// Generates the next routing URL based on the internal routing logic.
@@ -52,14 +70,8 @@ pub trait RouteProvider: std::fmt::Debug + Send + Sync {
5270
/// fewer are available.
5371
fn n_ordered_routes(&self, n: usize) -> Result<Vec<Url>, AgentError>;
5472

55-
/// Returns the total number of routes and healthy routes as a tuple.
56-
///
57-
/// - First element is the total number of routes available (both healthy and unhealthy)
58-
/// - Second element is the number of currently healthy routes, or None if health status information is unavailable
59-
///
60-
/// A healthy route is one that is available and ready to receive traffic.
61-
/// The specific criteria for what constitutes a "healthy" route is implementation dependent.
62-
fn routes_stats(&self) -> (usize, Option<usize>);
73+
/// Returns statistics about the total number of existing routes and the number of healthy routes.
74+
fn routes_stats(&self) -> RoutesStats;
6375
}
6476

6577
/// A simple implementation of the [`RouteProvider`] which produces an even distribution of the urls from the input ones.
@@ -104,8 +116,8 @@ impl RouteProvider for RoundRobinRouteProvider {
104116
Ok(urls)
105117
}
106118

107-
fn routes_stats(&self) -> (usize, Option<usize>) {
108-
(self.routes.len(), None)
119+
fn routes_stats(&self) -> RoutesStats {
120+
RoutesStats::new(self.routes.len(), None)
109121
}
110122
}
111123

@@ -146,8 +158,8 @@ impl RouteProvider for Url {
146158
fn n_ordered_routes(&self, _: usize) -> Result<Vec<Url>, AgentError> {
147159
Ok(vec![self.route()?])
148160
}
149-
fn routes_stats(&self) -> (usize, Option<usize>) {
150-
(1, None)
161+
fn routes_stats(&self) -> RoutesStats {
162+
RoutesStats::new(1, None)
151163
}
152164
}
153165

@@ -231,7 +243,7 @@ impl RouteProvider for DynamicRouteProvider {
231243
fn n_ordered_routes(&self, n: usize) -> Result<Vec<Url>, AgentError> {
232244
self.inner.n_ordered_routes(n)
233245
}
234-
fn routes_stats(&self) -> (usize, Option<usize>) {
246+
fn routes_stats(&self) -> RoutesStats {
235247
self.inner.routes_stats()
236248
}
237249
}
@@ -289,8 +301,8 @@ impl<R: RouteProvider> RouteProvider for UrlUntilReady<R> {
289301
self.url.route()
290302
}
291303
}
292-
fn routes_stats(&self) -> (usize, Option<usize>) {
293-
(1, None)
304+
fn routes_stats(&self) -> RoutesStats {
305+
RoutesStats::new(1, None)
294306
}
295307
}
296308

Diff for: ic-agent/src/agent/route_provider/dynamic_routing/dynamic_route_provider.rs

+10-10
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use crate::{
2323
snapshot::routing_snapshot::RoutingSnapshot,
2424
type_aliases::AtomicSwap,
2525
},
26-
RouteProvider,
26+
RouteProvider, RoutesStats,
2727
},
2828
HttpService,
2929
},
@@ -187,9 +187,9 @@ where
187187
Ok(urls)
188188
}
189189

190-
fn routes_stats(&self) -> (usize, Option<usize>) {
190+
fn routes_stats(&self) -> RoutesStats {
191191
let snapshot = self.routing_snapshot.load();
192-
snapshot.nodes_stats()
192+
snapshot.routes_stats()
193193
}
194194
}
195195

@@ -296,7 +296,7 @@ mod tests {
296296
assert_routed_domains, route_n_times, NodeHealthCheckerMock, NodesFetcherMock,
297297
},
298298
},
299-
RouteProvider,
299+
RouteProvider, RoutesStats,
300300
},
301301
Agent, AgentError,
302302
};
@@ -422,7 +422,7 @@ mod tests {
422422
tokio::time::sleep(snapshot_update_duration).await;
423423
let routed_domains = route_n_times(6, Arc::clone(&route_provider));
424424
assert_routed_domains(routed_domains, vec![node_1.domain()], 6);
425-
assert_eq!(route_provider.routes_stats(), (1, Some(1)));
425+
assert_eq!(route_provider.routes_stats(), RoutesStats::new(1, Some(1)));
426426

427427
// Test 2: multiple route() calls return 3 different domains with equal fairness (repetition).
428428
// Two healthy nodes are added to the topology.
@@ -437,15 +437,15 @@ mod tests {
437437
vec![node_1.domain(), node_2.domain(), node_3.domain()],
438438
2,
439439
);
440-
assert_eq!(route_provider.routes_stats(), (3, Some(3)));
440+
assert_eq!(route_provider.routes_stats(), RoutesStats::new(3, Some(3)));
441441

442442
// Test 3: multiple route() calls return 2 different domains with equal fairness (repetition).
443443
// One node is set to unhealthy.
444444
checker.overwrite_healthy_nodes(vec![node_1.clone(), node_3.clone()]);
445445
tokio::time::sleep(snapshot_update_duration).await;
446446
let routed_domains = route_n_times(6, Arc::clone(&route_provider));
447447
assert_routed_domains(routed_domains, vec![node_1.domain(), node_3.domain()], 3);
448-
assert_eq!(route_provider.routes_stats(), (3, Some(2)));
448+
assert_eq!(route_provider.routes_stats(), RoutesStats::new(3, Some(2)));
449449

450450
// Test 4: multiple route() calls return 3 different domains with equal fairness (repetition).
451451
// Unhealthy node is set back to healthy.
@@ -457,7 +457,7 @@ mod tests {
457457
vec![node_1.domain(), node_2.domain(), node_3.domain()],
458458
2,
459459
);
460-
assert_eq!(route_provider.routes_stats(), (3, Some(3)));
460+
assert_eq!(route_provider.routes_stats(), RoutesStats::new(3, Some(3)));
461461

462462
// Test 5: multiple route() calls return 3 different domains with equal fairness (repetition).
463463
// One healthy node is added, but another one goes unhealthy.
@@ -476,7 +476,7 @@ mod tests {
476476
vec![node_2.domain(), node_3.domain(), node_4.domain()],
477477
2,
478478
);
479-
assert_eq!(route_provider.routes_stats(), (4, Some(3)));
479+
assert_eq!(route_provider.routes_stats(), RoutesStats::new(4, Some(3)));
480480

481481
// Test 6: multiple route() calls return a single domain=api1.com.
482482
// One node is set to unhealthy and one is removed from the topology.
@@ -485,7 +485,7 @@ mod tests {
485485
tokio::time::sleep(snapshot_update_duration).await;
486486
let routed_domains = route_n_times(3, Arc::clone(&route_provider));
487487
assert_routed_domains(routed_domains, vec![node_2.domain()], 3);
488-
assert_eq!(route_provider.routes_stats(), (3, Some(1)));
488+
assert_eq!(route_provider.routes_stats(), RoutesStats::new(3, Some(1)));
489489
}
490490

491491
#[tokio::test]

Diff for: ic-agent/src/agent/route_provider/dynamic_routing/snapshot/latency_based_routing.rs

+22-16
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ use std::{
55

66
use rand::Rng;
77

8-
use crate::agent::route_provider::dynamic_routing::{
9-
health_check::HealthCheckStatus, node::Node, snapshot::routing_snapshot::RoutingSnapshot,
8+
use crate::agent::route_provider::{
9+
dynamic_routing::{
10+
health_check::HealthCheckStatus, node::Node, snapshot::routing_snapshot::RoutingSnapshot,
11+
},
12+
RoutesStats,
1013
};
1114

1215
// Determines the size of the sliding window used for storing latencies and availabilities of nodes.
@@ -323,8 +326,8 @@ impl RoutingSnapshot for LatencyRoutingSnapshot {
323326
true
324327
}
325328

326-
fn nodes_stats(&self) -> (usize, Option<usize>) {
327-
(self.existing_nodes.len(), Some(self.healthy_nodes.len()))
329+
fn routes_stats(&self) -> RoutesStats {
330+
RoutesStats::new(self.existing_nodes.len(), Some(self.healthy_nodes.len()))
328331
}
329332
}
330333

@@ -335,15 +338,18 @@ mod tests {
335338
time::Duration,
336339
};
337340

338-
use crate::agent::route_provider::dynamic_routing::{
339-
health_check::HealthCheckStatus,
340-
node::Node,
341-
snapshot::{
342-
latency_based_routing::{
343-
compute_score, weighted_sample, LatencyRoutingSnapshot, NodeWithMetrics,
341+
use crate::agent::route_provider::{
342+
dynamic_routing::{
343+
health_check::HealthCheckStatus,
344+
node::Node,
345+
snapshot::{
346+
latency_based_routing::{
347+
compute_score, weighted_sample, LatencyRoutingSnapshot, NodeWithMetrics,
348+
},
349+
routing_snapshot::RoutingSnapshot,
344350
},
345-
routing_snapshot::RoutingSnapshot,
346351
},
352+
RoutesStats,
347353
};
348354

349355
#[test]
@@ -356,7 +362,7 @@ mod tests {
356362
assert!(!snapshot.has_nodes());
357363
assert!(snapshot.next_node().is_none());
358364
assert!(snapshot.next_n_nodes(1).is_empty());
359-
assert_eq!(snapshot.nodes_stats(), (0, Some(0)));
365+
assert_eq!(snapshot.routes_stats(), RoutesStats::new(0, Some(0)));
360366
}
361367

362368
#[test]
@@ -372,7 +378,7 @@ mod tests {
372378
assert!(snapshot.nodes_with_metrics.is_empty());
373379
assert!(!snapshot.has_nodes());
374380
assert!(snapshot.next_node().is_none());
375-
assert_eq!(snapshot.nodes_stats(), (0, Some(0)));
381+
assert_eq!(snapshot.routes_stats(), RoutesStats::new(0, Some(0)));
376382
}
377383

378384
#[test]
@@ -384,15 +390,15 @@ mod tests {
384390
let node = Node::new("api1.com").unwrap();
385391
let health = HealthCheckStatus::new(Some(Duration::from_secs(1)));
386392
snapshot.existing_nodes.insert(node.clone());
387-
assert_eq!(snapshot.nodes_stats(), (1, Some(0)));
393+
assert_eq!(snapshot.routes_stats(), RoutesStats::new(1, Some(0)));
388394
// Check first update
389395
let is_updated = snapshot.update_node(&node, health);
390396
assert!(is_updated);
391397
assert!(snapshot.has_nodes());
392398
let node_with_metrics = snapshot.nodes_with_metrics.first().unwrap();
393399
assert_eq!(node_with_metrics.score, (2.0 / 1.0) / 2.0);
394400
assert_eq!(snapshot.next_node().unwrap(), node);
395-
assert_eq!(snapshot.nodes_stats(), (1, Some(1)));
401+
assert_eq!(snapshot.routes_stats(), RoutesStats::new(1, Some(1)));
396402
// Check second update
397403
let health = HealthCheckStatus::new(Some(Duration::from_secs(2)));
398404
let is_updated = snapshot.update_node(&node, health);
@@ -415,7 +421,7 @@ mod tests {
415421
assert_eq!(snapshot.nodes_with_metrics.len(), 1);
416422
assert_eq!(snapshot.existing_nodes.len(), 1);
417423
assert!(snapshot.next_node().is_none());
418-
assert_eq!(snapshot.nodes_stats(), (1, Some(0)));
424+
assert_eq!(snapshot.routes_stats(), RoutesStats::new(1, Some(0)));
419425
}
420426

421427
#[test]

Diff for: ic-agent/src/agent/route_provider/dynamic_routing/snapshot/round_robin_routing.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ use std::{
66
},
77
};
88

9-
use crate::agent::route_provider::dynamic_routing::{
10-
health_check::HealthCheckStatus, node::Node, snapshot::routing_snapshot::RoutingSnapshot,
9+
use crate::agent::route_provider::{
10+
dynamic_routing::{
11+
health_check::HealthCheckStatus, node::Node, snapshot::routing_snapshot::RoutingSnapshot,
12+
},
13+
RoutesStats,
1114
};
1215

1316
/// Routing snapshot, which samples nodes in a round-robin fashion.
@@ -107,8 +110,8 @@ impl RoutingSnapshot for RoundRobinRoutingSnapshot {
107110
}
108111
}
109112

110-
fn nodes_stats(&self) -> (usize, Option<usize>) {
111-
(self.existing_nodes.len(), Some(self.healthy_nodes.len()))
113+
fn routes_stats(&self) -> RoutesStats {
114+
RoutesStats::new(self.existing_nodes.len(), Some(self.healthy_nodes.len()))
112115
}
113116
}
114117

Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use std::fmt::Debug;
22

3-
use crate::agent::route_provider::dynamic_routing::{health_check::HealthCheckStatus, node::Node};
3+
use crate::agent::route_provider::{
4+
dynamic_routing::{health_check::HealthCheckStatus, node::Node},
5+
RoutesStats,
6+
};
47

58
/// A trait for interacting with the snapshot of nodes (routing table).
69
pub trait RoutingSnapshot: Send + Sync + Clone + Debug {
@@ -15,11 +18,6 @@ pub trait RoutingSnapshot: Send + Sync + Clone + Debug {
1518
fn sync_nodes(&mut self, nodes: &[Node]) -> bool;
1619
/// Updates the health status of a specific node, returning `true` if the node was found and updated.
1720
fn update_node(&mut self, node: &Node, health: HealthCheckStatus) -> bool;
18-
/// Returns the total number of nodes and healthy nodes as a tuple.
19-
///
20-
/// - First element is the total number of nodes available (both healthy and unhealthy)
21-
/// - Second element is the number of currently healthy nodes, or None if health status information is unavailable
22-
///
23-
/// The specific criteria for what constitutes a "healthy" node is implementation dependent.
24-
fn nodes_stats(&self) -> (usize, Option<usize>);
21+
/// Returns statistics about the routes (nodes).
22+
fn routes_stats(&self) -> RoutesStats;
2523
}

0 commit comments

Comments
 (0)