From c9e70fcf07631d6b3316001c78dbab631eec6bdf Mon Sep 17 00:00:00 2001 From: Ignacio Duart Date: Tue, 7 Nov 2023 11:45:34 +0100 Subject: [PATCH] Fix test issues --- crates/core/src/node.rs | 2 +- crates/core/src/operations/connect.rs | 8 +- crates/core/src/operations/get.rs | 23 +- crates/core/src/operations/put.rs | 7 +- crates/core/src/operations/subscribe.rs | 8 +- crates/core/src/ring.rs | 66 ++-- crates/core/src/router.rs | 28 +- crates/core/src/router/isotonic_estimator.rs | 6 +- crates/core/src/topology.rs | 17 +- .../src/topology/request_density_tracker.rs | 345 ++++++++++++++++-- .../cached_density_map.rs | 28 -- .../topology/request_density_tracker/tests.rs | 269 -------------- 12 files changed, 403 insertions(+), 404 deletions(-) delete mode 100644 crates/core/src/topology/request_density_tracker/cached_density_map.rs delete mode 100644 crates/core/src/topology/request_density_tracker/tests.rs diff --git a/crates/core/src/node.rs b/crates/core/src/node.rs index f9d8b468c..3954e0124 100644 --- a/crates/core/src/node.rs +++ b/crates/core/src/node.rs @@ -544,7 +544,7 @@ async fn report_result( }) .unwrap_or_default(); tracing::error!(%tx, ?state, "Wrong state"); - eprintln!("{trace}"); + eprintln!("Operation error trace:\n{trace}"); } #[cfg(not(debug_assertions))] { diff --git a/crates/core/src/operations/connect.rs b/crates/core/src/operations/connect.rs index 5338f6183..3067cf5e3 100644 --- a/crates/core/src/operations/connect.rs +++ b/crates/core/src/operations/connect.rs @@ -917,8 +917,12 @@ where "Selecting close peer to forward request", ); // FIXME: target the `desired_location` - ring.routing(&joiner.location.unwrap(), Some(&req_peer.peer), &skip_list) - .and_then(|pkl| (pkl.peer != joiner.peer).then_some(pkl)) + ring.routing( + joiner.location.unwrap(), + Some(&req_peer.peer), + skip_list.as_slice(), + ) + .and_then(|pkl| (pkl.peer != joiner.peer).then_some(pkl)) }; if let Some(forward_to) = forward_to { diff --git a/crates/core/src/operations/get.rs b/crates/core/src/operations/get.rs index 8bad7c9a6..e412bc7cd 100644 --- a/crates/core/src/operations/get.rs +++ b/crates/core/src/operations/get.rs @@ -251,7 +251,7 @@ impl Operation for GetOp { if htl == 0 { tracing::warn!( tx = %id, - "The maximum HOPS number has been exceeded, sending the error \ + "The maximum hops has been exceeded, sending the error \ back to the node @ {}", sender.peer ); @@ -275,7 +275,10 @@ impl Operation for GetOp { } let new_htl = htl - 1; - let Some(new_target) = op_storage.ring.closest_caching(&key, &[]) else { + let Some(new_target) = op_storage + .ring + .closest_caching(&key, [&sender.peer].as_slice()) + else { tracing::warn!(tx = %id, "No other peers found while trying getting contract {key} @ {}", target.peer); return Err(OpError::RingError(RingError::NoCachingPeers(key))); }; @@ -367,9 +370,10 @@ impl Operation for GetOp { sender, target, } => { - let this_loc = target; + let this_peer = target; tracing::warn!( tx = %id, + %this_peer, "Neither contract or contract value for contract `{}` found at peer {}, \ retrying with other peers", key, @@ -397,7 +401,7 @@ impl Operation for GetOp { id: *id, key: key.clone(), target, - sender: *this_loc, + sender: *this_peer, fetch_contract, htl: MAX_GET_RETRY_HOPS, }); @@ -697,13 +701,14 @@ pub(crate) async fn request_get( client_id: Option, ) -> Result<(), OpError> { let (target, id) = if let Some(GetState::PrepareRequest { key, id, .. }) = &get_op.state { + const EMPTY: &[PeerKey] = &[]; // the initial request must provide: // - a location in the network where the contract resides // - and the key of the contract value to get ( op_storage .ring - .closest_caching(key, &[]) + .closest_caching(key, EMPTY) .into_iter() .next() .ok_or(RingError::EmptyRing)?, @@ -901,7 +906,7 @@ mod test { // trigger get @ node-0, which does not own the contract sim_nw - .trigger_event("node-0", 1, Some(Duration::from_millis(50))) + .trigger_event("node-0", 1, Some(Duration::from_secs(1))) .await?; assert!(sim_nw.has_got_contract("node-0", &key)); Ok(()) @@ -939,7 +944,7 @@ mod test { // trigger get @ node-1, which does not own the contract sim_nw - .trigger_event("node-1", 1, Some(Duration::from_millis(50))) + .trigger_event("node-1", 1, Some(Duration::from_secs(1))) .await?; assert!(!sim_nw.has_got_contract("node-1", &key)); Ok(()) @@ -947,6 +952,7 @@ mod test { #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn contract_found_after_retry() -> Result<(), anyhow::Error> { + crate::config::set_logger(); const NUM_NODES: usize = 2usize; const NUM_GW: usize = 1usize; @@ -1005,9 +1011,8 @@ mod test { .await; sim_nw.start_with_spec(get_specs).await; sim_nw.check_connectivity(Duration::from_secs(3)).await?; - sim_nw - .trigger_event("node-0", 1, Some(Duration::from_millis(200))) + .trigger_event("node-0", 1, Some(Duration::from_secs(1))) .await?; assert!(sim_nw.has_got_contract("node-0", &key)); Ok(()) diff --git a/crates/core/src/operations/put.rs b/crates/core/src/operations/put.rs index 0ffef8ed2..5c9b6f49d 100644 --- a/crates/core/src/operations/put.rs +++ b/crates/core/src/operations/put.rs @@ -676,7 +676,7 @@ pub(crate) async fn request_put( // - and the value to put let target = op_storage .ring - .closest_caching(&key, &[sender.peer]) + .closest_caching(&key, [&sender.peer].as_slice()) .into_iter() .next() .ok_or(RingError::EmptyRing)?; @@ -771,7 +771,8 @@ async fn forward_changes( { let key = contract.key(); let contract_loc = Location::from(&key); - let forward_to = op_storage.ring.closest_caching(&key, &[]); + const EMPTY: &[PeerKey] = &[]; + let forward_to = op_storage.ring.closest_caching(&key, EMPTY); let own_loc = op_storage.ring.own_location().location.expect("infallible"); if let Some(peer) = forward_to { let other_loc = peer.location.as_ref().expect("infallible"); @@ -1018,7 +1019,7 @@ mod test { // trigger the put op @ gw-0 sim_nw - .trigger_event("gateway-0", 1, Some(Duration::from_millis(200))) + .trigger_event("gateway-0", 1, Some(Duration::from_secs(1))) .await?; assert!(sim_nw.has_put_contract("gateway-0", &key, &new_value)); assert!(sim_nw.event_listener.contract_broadcasted(&key)); diff --git a/crates/core/src/operations/subscribe.rs b/crates/core/src/operations/subscribe.rs index 65f6496aa..bafa58233 100644 --- a/crates/core/src/operations/subscribe.rs +++ b/crates/core/src/operations/subscribe.rs @@ -151,7 +151,8 @@ impl Operation for SubscribeOp { if !op_storage.ring.is_contract_cached(key) { tracing::debug!(tx = %id, "Contract {} not found at {}, trying other peer", key, target.peer); - let Some(new_target) = op_storage.ring.closest_caching(key, &[sender.peer]) + let Some(new_target) = + op_storage.ring.closest_caching(key, [&sender.peer].as_slice()) else { tracing::warn!(tx = %id, "No peer found while trying getting contract {key}"); return Err(OpError::RingError(RingError::NoCachingPeers(key.clone()))); @@ -345,10 +346,11 @@ pub(crate) async fn request_subscribe( key.clone(), ))); } + const EMPTY: &[PeerKey] = &[]; ( op_storage .ring - .closest_caching(key, &[]) + .closest_caching(key, EMPTY) .into_iter() .next() .ok_or_else(|| RingError::NoCachingPeers(key.clone()))?, @@ -526,7 +528,7 @@ mod test { sim_nw.start_with_spec(subscribe_specs).await; sim_nw.check_connectivity(Duration::from_secs(3)).await?; sim_nw - .trigger_event("node-1", 1, Some(Duration::from_secs(2))) + .trigger_event("node-1", 1, Some(Duration::from_secs(1))) .await?; assert!(sim_nw.has_got_contract("node-1", &contract_key)); tokio::time::sleep(Duration::from_secs(3)).await; diff --git a/crates/core/src/ring.rs b/crates/core/src/ring.rs index 73e72188b..35cc3e0e5 100644 --- a/crates/core/src/ring.rs +++ b/crates/core/src/ring.rs @@ -397,7 +397,6 @@ impl Ring { } else if open_conn < self.min_connections { true } else if open_conn >= self.max_connections { - tracing::debug!(peer = %self.peer_key, "max open connections reached"); false } else { let strategy = if self @@ -411,7 +410,7 @@ impl Ring { self.topology_manager .write() .evaluate_new_connection(location, strategy) - .expect("already have > min connections, so neighbors shouldn't be empty") + .unwrap_or(false) }; if !accepted { self.open_connections @@ -436,8 +435,8 @@ impl Ring { open_at: Instant::now(), }); self.location_for_peer.write().insert(peer, loc); - let topology_manager = &mut *self.topology_manager.write(); let current_neighbors = &Self::current_neighbors(&cbl); + let topology_manager = &mut *self.topology_manager.write(); topology_manager .refresh_cache(current_neighbors) .expect("current neightbors shouldn't be empty here ever, just added at least one") @@ -448,7 +447,7 @@ impl Ring { pub fn closest_caching( &self, contract_key: &ContractKey, - skip_list: &[PeerKey], + skip_list: impl Contains, ) -> Option { self.routing(Location::from(contract_key), None, skip_list) } @@ -620,15 +619,12 @@ impl Ring { const REMOVAL_TICK_DURATION: Duration = Duration::from_secs(60 * 5); #[cfg(test)] const REMOVAL_TICK_DURATION: Duration = Duration::from_secs(1); - #[cfg(test)] - const ACQUIRE_CONNS_TICK_DURATION: Duration = Duration::from_millis(100); - #[cfg(not(test))] const ACQUIRE_CONNS_TICK_DURATION: Duration = Duration::from_secs(1); let mut check_interval = tokio::time::interval(REMOVAL_TICK_DURATION); - check_interval.tick().await; + check_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); let mut acquire_max_connections = tokio::time::interval(ACQUIRE_CONNS_TICK_DURATION); - acquire_max_connections.tick().await; + acquire_max_connections.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); let mut missing = BTreeMap::new(); @@ -645,7 +641,7 @@ impl Ring { } Err(sync::mpsc::error::TryRecvError::Empty) => break, Err(sync::mpsc::error::TryRecvError::Disconnected) => { - tracing::debug!("shutting down connection maintenance"); + tracing::debug!("Shutting down connection maintenance"); break 'outer Err("finished".into()); } } @@ -656,16 +652,22 @@ impl Ring { let open_connections = self .open_connections - .load(std::sync::atomic::Ordering::Acquire); - if open_connections < self.max_connections && open_connections > self.min_connections { + .load(std::sync::atomic::Ordering::SeqCst); + + if open_connections < self.max_connections { self.fast_acquisition .store(true, std::sync::atomic::Ordering::Release); // requires more connections let ideal_location = { - self.topology_manager - .write() - .get_best_candidate_location() - .expect("we only acquire connections when we have at least the minimum established neighbors already") + let loc = { self.topology_manager.read().get_best_candidate_location() }; + match loc { + Ok(loc) => loc, + Err(_) => { + tracing::debug!(peer = %self.own_location(), "Insufficient data gathered by the topology manager"); + acquire_max_connections.tick().await; + continue; + } + } }; self.acquire_new( ideal_location, @@ -674,20 +676,11 @@ impl Ring { ) .await .map_err(|error| { - tracing::debug!(?error, "shutting down connection maintenance task"); + tracing::debug!(?error, "Shutting down connection maintenance task"); error })?; - if self - .open_connections - .load(std::sync::atomic::Ordering::Acquire) - < self.max_connections - { - acquire_max_connections.tick().await; - continue; - } else { - check_interval.tick().await; - continue; - } + acquire_max_connections.tick().await; + continue; } let mut should_swap = { @@ -707,10 +700,15 @@ impl Ring { self.fast_acquisition .store(false, std::sync::atomic::Ordering::Release); let ideal_location = { - self.topology_manager - .write() - .get_best_candidate_location() - .expect("we only swap when we have some established neighbors already") + let loc = { self.topology_manager.read().get_best_candidate_location() }; + match loc { + Ok(loc) => loc, + Err(_) => { + tracing::debug!(peer = %self.own_location(), "Insufficient data gathered by the topology manager"); + check_interval.tick().await; + continue; + } + } }; self.acquire_new( ideal_location, @@ -719,7 +717,7 @@ impl Ring { ) .await .map_err(|error| { - tracing::warn!(?error, "shutting down connection maintenance task"); + tracing::warn!(?error, "Shutting down connection maintenance task"); error })?; for peer in should_swap.drain(..) { @@ -729,7 +727,7 @@ impl Ring { ))) .await .map_err(|error| { - tracing::debug!(?error, "shutting down connection maintenance task"); + tracing::debug!(?error, "Shutting down connection maintenance task"); error })?; } diff --git a/crates/core/src/router.rs b/crates/core/src/router.rs index 2a10e1117..7ea381042 100644 --- a/crates/core/src/router.rs +++ b/crates/core/src/router.rs @@ -149,7 +149,7 @@ impl Router { pub fn select_peer<'a>( &self, peers: impl IntoIterator, - contract_location: &Location, + target_location: Location, ) -> Option<&'a PeerKeyLocation> { if !self.has_sufficient_historical_data() { // Find the peer with the minimum distance to the contract location, @@ -158,7 +158,7 @@ impl Router { .into_iter() .filter_map(|peer| { peer.location - .map(|loc| (peer, contract_location.distance(loc))) + .map(|loc| (peer, target_location.distance(loc))) }) .min_by_key(|&(_, distance)| distance) .map(|(peer, _)| peer) @@ -167,11 +167,9 @@ impl Router { peers .into_iter() .map(|peer: &PeerKeyLocation| { - let t = self - .predict_routing_outcome(peer, contract_location) - .expect( - "Should always be Ok when has_sufficient_historical_data() is true", - ); + let t = self.predict_routing_outcome(peer, target_location).expect( + "Should always be Ok when has_sufficient_historical_data() is true", + ); (peer, t.time_to_response_start) }) // Required because f64 doesn't implement Ord @@ -187,7 +185,7 @@ impl Router { fn predict_routing_outcome( &self, peer: &PeerKeyLocation, - contract_location: &Location, + target_location: Location, ) -> Result { if !self.has_sufficient_historical_data() { return Err(RoutingError::InsufficientDataError); @@ -195,21 +193,21 @@ impl Router { let time_to_response_start_estimate = self .response_start_time_estimator - .estimate_retrieval_time(peer, contract_location) + .estimate_retrieval_time(peer, target_location) .map_err(|source| RoutingError::EstimationError { estimation: "start time", source, })?; let failure_estimate = self .failure_estimator - .estimate_retrieval_time(peer, contract_location) + .estimate_retrieval_time(peer, target_location) .map_err(|source| RoutingError::EstimationError { estimation: "failure", source, })?; let transfer_rate_estimate = self .transfer_rate_estimator - .estimate_retrieval_time(peer, contract_location) + .estimate_retrieval_time(peer, target_location) .map_err(|source| RoutingError::EstimationError { estimation: "transfer rate", source, @@ -299,7 +297,7 @@ mod tests { for _ in 0..10 { let contract_location = Location::random(); // Pass a reference to the `peers` vector - let best = router.select_peer(&peers, &contract_location).unwrap(); + let best = router.select_peer(&peers, contract_location).unwrap(); let best_distance = best.location.unwrap().distance(contract_location); for peer in &peers { // Dereference `best` when making the comparison @@ -359,7 +357,7 @@ mod tests { let truth = simulate_prediction(&mut rng, event.peer, event.contract_location); let prediction = router - .predict_routing_outcome(&event.peer, &event.contract_location) + .predict_routing_outcome(&event.peer, event.contract_location) .unwrap(); // Verify that the prediction is within 0.01 of the truth @@ -399,9 +397,9 @@ mod tests { fn simulate_prediction( random: &mut rand::rngs::ThreadRng, peer: PeerKeyLocation, - contract_location: Location, + target_location: Location, ) -> RoutingPrediction { - let distance = peer.location.unwrap().distance(contract_location); + let distance = peer.location.unwrap().distance(target_location); let time_to_response_start = 2.0 * distance.as_f64(); let failure_prob = distance.as_f64(); let transfer_speed = 100.0 - (100.0 * distance.as_f64()); diff --git a/crates/core/src/router/isotonic_estimator.rs b/crates/core/src/router/isotonic_estimator.rs index d987917e1..24ca45bc9 100644 --- a/crates/core/src/router/isotonic_estimator.rs +++ b/crates/core/src/router/isotonic_estimator.rs @@ -111,7 +111,7 @@ impl IsotonicEstimator { pub fn estimate_retrieval_time( &self, peer: &PeerKeyLocation, - contract_location: &Location, + contract_location: Location, ) -> Result { // Check if there are enough data points that the model won't produce // garbage output, but users of this class must implement their own checks @@ -251,7 +251,7 @@ mod tests { let mut errors = Vec::new(); for event in testing_events { let estimated_time = estimator - .estimate_retrieval_time(&event.peer, &event.contract_location) + .estimate_retrieval_time(&event.peer, event.contract_location) .unwrap(); let actual_time = event.result; let error = (estimated_time - actual_time).abs(); @@ -288,7 +288,7 @@ mod tests { let mut errors = Vec::new(); for event in testing_events { let estimated_time = estimator - .estimate_retrieval_time(&event.peer, &event.contract_location) + .estimate_retrieval_time(&event.peer, event.contract_location) .unwrap(); let actual_time = event.result; let error = (estimated_time - actual_time).abs(); diff --git a/crates/core/src/topology.rs b/crates/core/src/topology.rs index 3b6240763..eec855e89 100644 --- a/crates/core/src/topology.rs +++ b/crates/core/src/topology.rs @@ -2,12 +2,11 @@ use crate::{ message::TransactionType, ring::{Distance, Location}, }; -use request_density_tracker::cached_density_map::CachedDensityMap; +use request_density_tracker::CachedDensityMap; use std::{ collections::BTreeMap, time::{Duration, Instant}, }; -use tracing::{debug, error}; mod connection_evaluator; mod request_density_tracker; @@ -79,9 +78,9 @@ impl TopologyManager { pub(crate) fn record_request( &mut self, requested_location: Location, - _request_type: TransactionType, + request_type: TransactionType, ) { - debug!("Recording request for location: {:?}", requested_location); + tracing::debug!(%request_type, %requested_location, "Recording request for location"); self.request_density_tracker.sample(requested_location); } @@ -106,7 +105,7 @@ impl TopologyManager { acquisition_strategy: AcquisitionStrategy, current_time: Instant, ) -> Result { - debug!( + tracing::debug!( "Evaluating new connection for candidate location: {:?}", candidate_location ); @@ -136,7 +135,6 @@ impl TopologyManager { /// Get the ideal location for a new connection based on current neighbors and request density pub(crate) fn get_best_candidate_location(&self) -> Result { - debug!("Retrieving best candidate location"); let density_map = self .cached_density_map .get() @@ -144,24 +142,23 @@ impl TopologyManager { let best_location = match density_map.get_max_density() { Ok(location) => { - debug!("Max density found at location: {:?}", location); + tracing::debug!("Max density found at location: {:?}", location); location } Err(_) => { - error!( + tracing::error!( "An error occurred while getting max density, falling back to random location" ); self.random_location() } }; - Ok(best_location) } /// Generates a random location that is close to the current peer location with a small /// world distribution. fn random_location(&self) -> Location { - debug!("Generating random location"); + tracing::debug!("Generating random location"); let distance = random_link_distance(Distance::new(RANDOM_CLOSEST_DISTANCE)); let location_f64 = if rand::random() { self.this_peer_location.as_f64() - distance.as_f64() diff --git a/crates/core/src/topology/request_density_tracker.rs b/crates/core/src/topology/request_density_tracker.rs index 298ce22c0..87c2237ab 100644 --- a/crates/core/src/topology/request_density_tracker.rs +++ b/crates/core/src/topology/request_density_tracker.rs @@ -1,18 +1,15 @@ -pub mod cached_density_map; - -#[cfg(test)] -mod tests; - use crate::ring::Location; -use std::collections::{BTreeMap, LinkedList}; +use std::collections::{BTreeMap, VecDeque}; use thiserror::Error; /// Tracks requests sent by a node to its neighbors and creates a density map, which /// is useful for determining which new neighbors to connect to based on their /// location. pub(super) struct RequestDensityTracker { - ordered_map: BTreeMap, - list: LinkedList, + /// Amount of requests done to an specific location. + request_locations: BTreeMap, + /// Request locations sorted by order of execution. + request_list: VecDeque, window_size: usize, samples: usize, } @@ -20,8 +17,8 @@ pub(super) struct RequestDensityTracker { impl RequestDensityTracker { pub fn new(window_size: usize) -> Self { Self { - ordered_map: BTreeMap::new(), - list: LinkedList::new(), + request_locations: BTreeMap::new(), + request_list: VecDeque::new(), window_size, samples: 0, } @@ -30,15 +27,15 @@ impl RequestDensityTracker { pub fn sample(&mut self, value: Location) { self.samples += 1; - self.list.push_back(value); - *self.ordered_map.entry(value).or_insert(0) += 1; + self.request_list.push_back(value); + *self.request_locations.entry(value).or_insert(0) += 1; - if self.list.len() > self.window_size { - if let Some(oldest) = self.list.pop_front() { - if let Some(count) = self.ordered_map.get_mut(&oldest) { + if self.request_list.len() > self.window_size { + if let Some(oldest) = self.request_list.pop_front() { + if let Some(count) = self.request_locations.get_mut(&oldest) { *count -= 1; if *count == 0 { - self.ordered_map.remove(&oldest); + self.request_locations.remove(&oldest); } } } @@ -54,11 +51,9 @@ impl RequestDensityTracker { return Err(DensityMapError::EmptyNeighbors); } - let mut density_map = DensityMap { - neighbor_request_counts: BTreeMap::new(), - }; + let mut neighbor_request_counts = BTreeMap::new(); - for (sample_location, sample_count) in self.ordered_map.iter() { + for (sample_location, sample_count) in self.request_locations.iter() { let previous_neighbor = neighbors .range(..*sample_location) .next_back() @@ -73,13 +68,11 @@ impl RequestDensityTracker { if sample_location.distance(*previous_neighbor_location) < sample_location.distance(*next_neighbor_location) { - *density_map - .neighbor_request_counts + *neighbor_request_counts .entry(*previous_neighbor_location) .or_insert(0) += sample_count; } else { - *density_map - .neighbor_request_counts + *neighbor_request_counts .entry(*next_neighbor_location) .or_insert(0) += sample_count; } @@ -91,9 +84,9 @@ impl RequestDensityTracker { } } - debug_assert!(!density_map.neighbor_request_counts.is_empty()); - - Ok(density_map) + Ok(DensityMap { + neighbor_request_counts, + }) } } @@ -195,8 +188,306 @@ impl DensityMap { } } +/// Struct to handle caching of DensityMap +pub(in crate::topology) struct CachedDensityMap { + density_map: Option, +} + +impl CachedDensityMap { + pub fn new() -> Self { + CachedDensityMap { density_map: None } + } + + pub fn set( + &mut self, + tracker: &RequestDensityTracker, + current_neighbors: &BTreeMap, + ) -> Result<(), DensityMapError> { + let density_map = tracker.create_density_map(current_neighbors)?; + self.density_map = Some(density_map); + Ok(()) + } + + pub fn get(&self) -> Option<&DensityMap> { + self.density_map.as_ref() + } +} + #[derive(Error, Debug)] pub(crate) enum DensityMapError { #[error("The neighbors BTreeMap is empty.")] EmptyNeighbors, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_create_density_map() { + let mut sw = RequestDensityTracker::new(5); + sw.sample(Location::new(0.21)); + sw.sample(Location::new(0.22)); + sw.sample(Location::new(0.23)); + sw.sample(Location::new(0.61)); + sw.sample(Location::new(0.62)); + + let mut neighbors = BTreeMap::new(); + neighbors.insert(Location::new(0.2), 1); + neighbors.insert(Location::new(0.6), 1); + + let result = sw.create_density_map(&neighbors); + assert!(result.is_ok()); + let result = result.unwrap(); + assert_eq!( + result.neighbor_request_counts.get(&Location::new(0.2)), + Some(&3) + ); + assert_eq!( + result.neighbor_request_counts.get(&Location::new(0.6)), + Some(&2) + ); + } + + #[test] + fn test_wrap_around() { + let mut sw = RequestDensityTracker::new(5); + sw.sample(Location::new(0.21)); + sw.sample(Location::new(0.22)); + sw.sample(Location::new(0.23)); + sw.sample(Location::new(0.61)); + sw.sample(Location::new(0.62)); + + let mut neighbors = BTreeMap::new(); + neighbors.insert(Location::new(0.6), 1); + neighbors.insert(Location::new(0.9), 1); + + let result = sw.create_density_map(&neighbors); + assert!(result.is_ok()); + let result = result.unwrap(); + assert_eq!( + result.neighbor_request_counts.get(&Location::new(0.9)), + Some(&3) + ); + assert_eq!( + result.neighbor_request_counts.get(&Location::new(0.6)), + Some(&2) + ); + } + + #[test] + fn test_interpolate() { + let mut sw = RequestDensityTracker::new(10); + sw.sample(Location::new(0.19)); + sw.sample(Location::new(0.20)); + sw.sample(Location::new(0.21)); + sw.sample(Location::new(0.59)); + sw.sample(Location::new(0.60)); + + let mut neighbors = BTreeMap::new(); + neighbors.insert(Location::new(0.2), 1); + neighbors.insert(Location::new(0.6), 1); + + let result = sw.create_density_map(&neighbors); + assert!(result.is_ok()); + let result = result.unwrap(); + + // Scan and dumb densities 0.0 to 1.0 at 0.01 intervals + println!("Location\tDensity"); + for i in 0..100 { + let location = Location::new(i as f64 / 100.0); + let density = result.get_density_at(location).unwrap(); + // Print and round density to 2 decimals + println!( + "{}\t{}", + location.as_f64(), + (density * 100.0).round() / 100.0 + ); + } + + assert_eq!(result.get_density_at(Location::new(0.2)).unwrap(), 3.0); + assert_eq!(result.get_density_at(Location::new(0.6)).unwrap(), 2.0); + assert_eq!(result.get_density_at(Location::new(0.4)).unwrap(), 2.5); + assert_eq!(result.get_density_at(Location::new(0.5)).unwrap(), 2.25); + } + + #[test] + fn test_drop() { + let mut sw = RequestDensityTracker::new(4); + sw.sample(Location::new(0.21)); + sw.sample(Location::new(0.22)); + sw.sample(Location::new(0.23)); + sw.sample(Location::new(0.61)); + sw.sample(Location::new(0.62)); + + let mut neighbors = BTreeMap::new(); + neighbors.insert(Location::new(0.2), 1); + neighbors.insert(Location::new(0.6), 1); + + let result = sw.create_density_map(&neighbors); + assert!(result.is_ok()); + let result = result.unwrap(); + assert_eq!( + result.neighbor_request_counts.get(&Location::new(0.2)), + Some(&2) + ); + assert_eq!( + result.neighbor_request_counts.get(&Location::new(0.6)), + Some(&2) + ); + } + + #[test] + #[should_panic(expected = "assertion failed: !neighbors.is_empty()")] + fn test_empty_neighbors_error() { + let sw = RequestDensityTracker::new(10); + let empty_neighbors = BTreeMap::new(); + matches!( + sw.create_density_map(&empty_neighbors), + Err(DensityMapError::EmptyNeighbors) + ); + } + + #[test] + fn test_get_max_density() { + let mut density_map = DensityMap { + neighbor_request_counts: BTreeMap::new(), + }; + + density_map + .neighbor_request_counts + .insert(Location::new(0.2), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.6), 2); + density_map + .neighbor_request_counts + .insert(Location::new(0.8), 2); + + let result = density_map.get_max_density(); + assert!(result.is_ok()); + let result = result.unwrap(); + assert_eq!(result, Location::new(0.7)); + } + + #[test] + fn test_get_max_density_2() { + let mut density_map = DensityMap { + neighbor_request_counts: BTreeMap::new(), + }; + + density_map + .neighbor_request_counts + .insert(Location::new(0.2), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.6), 2); + density_map + .neighbor_request_counts + .insert(Location::new(0.8), 2); + density_map + .neighbor_request_counts + .insert(Location::new(0.9), 1); + + let result = density_map.get_max_density(); + assert!(result.is_ok()); + let result = result.unwrap(); + assert_eq!(result, Location::new(0.7)); + } + + #[test] + fn test_get_max_density_first_last() { + let mut density_map = DensityMap { + neighbor_request_counts: BTreeMap::new(), + }; + + density_map + .neighbor_request_counts + .insert(Location::new(0.0), 2); + density_map + .neighbor_request_counts + .insert(Location::new(0.2), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.6), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.8), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.9), 2); + + let result = density_map.get_max_density(); + assert!(result.is_ok()); + let result = result.unwrap(); + assert_eq!(result, Location::new(0.95)); + } + + #[test] + fn test_get_max_density_first_last_2() { + // Verify the other case in max_density_location calculation + let mut density_map = DensityMap { + neighbor_request_counts: BTreeMap::new(), + }; + + density_map + .neighbor_request_counts + .insert(Location::new(0.3), 2); + density_map + .neighbor_request_counts + .insert(Location::new(0.4), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.6), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.8), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.9), 2); + + let result = density_map.get_max_density(); + assert!(result.is_ok()); + let result = result.unwrap(); + assert_eq!(result, Location::new(0.1)); + } + + #[test] + fn test_get_max_density_first_last_3() { + // Verify the other case in max_density_location calculation + let mut density_map = DensityMap { + neighbor_request_counts: BTreeMap::new(), + }; + + density_map + .neighbor_request_counts + .insert(Location::new(0.1), 2); + density_map + .neighbor_request_counts + .insert(Location::new(0.2), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.3), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.4), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.7), 2); + + let result = density_map.get_max_density(); + assert!(result.is_ok()); + let result = result.unwrap(); + assert_eq!(result, Location::new(0.9)); + } + + #[test] + fn test_get_max_density_empty_neighbors_error() { + let density_map = DensityMap { + neighbor_request_counts: BTreeMap::new(), + }; + + let result = density_map.get_max_density(); + assert!(matches!(result, Err(DensityMapError::EmptyNeighbors))); + } +} diff --git a/crates/core/src/topology/request_density_tracker/cached_density_map.rs b/crates/core/src/topology/request_density_tracker/cached_density_map.rs deleted file mode 100644 index 0dd5962df..000000000 --- a/crates/core/src/topology/request_density_tracker/cached_density_map.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::ring::Location; -use crate::topology::request_density_tracker::{self, DensityMapError}; -use std::collections::BTreeMap; - -/// Struct to handle caching of DensityMap -pub(in crate::topology) struct CachedDensityMap { - pub density_map: Option, -} - -impl CachedDensityMap { - pub fn new() -> Self { - CachedDensityMap { density_map: None } - } - - pub fn set( - &mut self, - tracker: &request_density_tracker::RequestDensityTracker, - current_neighbors: &BTreeMap, - ) -> Result<(), DensityMapError> { - let density_map = tracker.create_density_map(current_neighbors)?; - self.density_map = Some(density_map); - Ok(()) - } - - pub fn get(&self) -> Option<&request_density_tracker::DensityMap> { - self.density_map.as_ref() - } -} diff --git a/crates/core/src/topology/request_density_tracker/tests.rs b/crates/core/src/topology/request_density_tracker/tests.rs deleted file mode 100644 index fa54d6dfe..000000000 --- a/crates/core/src/topology/request_density_tracker/tests.rs +++ /dev/null @@ -1,269 +0,0 @@ -use super::*; - -#[test] -fn test_create_density_map() { - let mut sw = RequestDensityTracker::new(5); - sw.sample(Location::new(0.21)); - sw.sample(Location::new(0.22)); - sw.sample(Location::new(0.23)); - sw.sample(Location::new(0.61)); - sw.sample(Location::new(0.62)); - - let mut neighbors = BTreeMap::new(); - neighbors.insert(Location::new(0.2), 1); - neighbors.insert(Location::new(0.6), 1); - - let result = sw.create_density_map(&neighbors); - assert!(result.is_ok()); - let result = result.unwrap(); - assert_eq!( - result.neighbor_request_counts.get(&Location::new(0.2)), - Some(&3) - ); - assert_eq!( - result.neighbor_request_counts.get(&Location::new(0.6)), - Some(&2) - ); -} - -#[test] -fn test_wrap_around() { - let mut sw = RequestDensityTracker::new(5); - sw.sample(Location::new(0.21)); - sw.sample(Location::new(0.22)); - sw.sample(Location::new(0.23)); - sw.sample(Location::new(0.61)); - sw.sample(Location::new(0.62)); - - let mut neighbors = BTreeMap::new(); - neighbors.insert(Location::new(0.6), 1); - neighbors.insert(Location::new(0.9), 1); - - let result = sw.create_density_map(&neighbors); - assert!(result.is_ok()); - let result = result.unwrap(); - assert_eq!( - result.neighbor_request_counts.get(&Location::new(0.9)), - Some(&3) - ); - assert_eq!( - result.neighbor_request_counts.get(&Location::new(0.6)), - Some(&2) - ); -} - -#[test] -fn test_interpolate() { - let mut sw = RequestDensityTracker::new(10); - sw.sample(Location::new(0.19)); - sw.sample(Location::new(0.20)); - sw.sample(Location::new(0.21)); - sw.sample(Location::new(0.59)); - sw.sample(Location::new(0.60)); - - let mut neighbors = BTreeMap::new(); - neighbors.insert(Location::new(0.2), 1); - neighbors.insert(Location::new(0.6), 1); - - let result = sw.create_density_map(&neighbors); - assert!(result.is_ok()); - let result = result.unwrap(); - - // Scan and dumb densities 0.0 to 1.0 at 0.01 intervals - println!("Location\tDensity"); - for i in 0..100 { - let location = Location::new(i as f64 / 100.0); - let density = result.get_density_at(location).unwrap(); - // Print and round density to 2 decimals - println!( - "{}\t{}", - location.as_f64(), - (density * 100.0).round() / 100.0 - ); - } - - assert_eq!(result.get_density_at(Location::new(0.2)).unwrap(), 3.0); - assert_eq!(result.get_density_at(Location::new(0.6)).unwrap(), 2.0); - assert_eq!(result.get_density_at(Location::new(0.4)).unwrap(), 2.5); - assert_eq!(result.get_density_at(Location::new(0.5)).unwrap(), 2.25); -} - -#[test] -fn test_drop() { - let mut sw = RequestDensityTracker::new(4); - sw.sample(Location::new(0.21)); - sw.sample(Location::new(0.22)); - sw.sample(Location::new(0.23)); - sw.sample(Location::new(0.61)); - sw.sample(Location::new(0.62)); - - let mut neighbors = BTreeMap::new(); - neighbors.insert(Location::new(0.2), 1); - neighbors.insert(Location::new(0.6), 1); - - let result = sw.create_density_map(&neighbors); - assert!(result.is_ok()); - let result = result.unwrap(); - assert_eq!( - result.neighbor_request_counts.get(&Location::new(0.2)), - Some(&2) - ); - assert_eq!( - result.neighbor_request_counts.get(&Location::new(0.6)), - Some(&2) - ); -} - -#[test] -#[should_panic(expected = "assertion failed: !neighbors.is_empty()")] -fn test_empty_neighbors_error() { - let sw = RequestDensityTracker::new(10); - let empty_neighbors = BTreeMap::new(); - matches!( - sw.create_density_map(&empty_neighbors), - Err(DensityMapError::EmptyNeighbors) - ); -} - -#[test] -fn test_get_max_density() { - let mut density_map = DensityMap { - neighbor_request_counts: BTreeMap::new(), - }; - - density_map - .neighbor_request_counts - .insert(Location::new(0.2), 1); - density_map - .neighbor_request_counts - .insert(Location::new(0.6), 2); - density_map - .neighbor_request_counts - .insert(Location::new(0.8), 2); - - let result = density_map.get_max_density(); - assert!(result.is_ok()); - let result = result.unwrap(); - assert_eq!(result, Location::new(0.7)); -} - -#[test] -fn test_get_max_density_2() { - let mut density_map = DensityMap { - neighbor_request_counts: BTreeMap::new(), - }; - - density_map - .neighbor_request_counts - .insert(Location::new(0.2), 1); - density_map - .neighbor_request_counts - .insert(Location::new(0.6), 2); - density_map - .neighbor_request_counts - .insert(Location::new(0.8), 2); - density_map - .neighbor_request_counts - .insert(Location::new(0.9), 1); - - let result = density_map.get_max_density(); - assert!(result.is_ok()); - let result = result.unwrap(); - assert_eq!(result, Location::new(0.7)); -} - -#[test] -fn test_get_max_density_first_last() { - let mut density_map = DensityMap { - neighbor_request_counts: BTreeMap::new(), - }; - - density_map - .neighbor_request_counts - .insert(Location::new(0.0), 2); - density_map - .neighbor_request_counts - .insert(Location::new(0.2), 1); - density_map - .neighbor_request_counts - .insert(Location::new(0.6), 1); - density_map - .neighbor_request_counts - .insert(Location::new(0.8), 1); - density_map - .neighbor_request_counts - .insert(Location::new(0.9), 2); - - let result = density_map.get_max_density(); - assert!(result.is_ok()); - let result = result.unwrap(); - assert_eq!(result, Location::new(0.95)); -} - -#[test] -fn test_get_max_density_first_last_2() { - // Verify the other case in max_density_location calculation - let mut density_map = DensityMap { - neighbor_request_counts: BTreeMap::new(), - }; - - density_map - .neighbor_request_counts - .insert(Location::new(0.3), 2); - density_map - .neighbor_request_counts - .insert(Location::new(0.4), 1); - density_map - .neighbor_request_counts - .insert(Location::new(0.6), 1); - density_map - .neighbor_request_counts - .insert(Location::new(0.8), 1); - density_map - .neighbor_request_counts - .insert(Location::new(0.9), 2); - - let result = density_map.get_max_density(); - assert!(result.is_ok()); - let result = result.unwrap(); - assert_eq!(result, Location::new(0.1)); -} - -#[test] -fn test_get_max_density_first_last_3() { - // Verify the other case in max_density_location calculation - let mut density_map = DensityMap { - neighbor_request_counts: BTreeMap::new(), - }; - - density_map - .neighbor_request_counts - .insert(Location::new(0.1), 2); - density_map - .neighbor_request_counts - .insert(Location::new(0.2), 1); - density_map - .neighbor_request_counts - .insert(Location::new(0.3), 1); - density_map - .neighbor_request_counts - .insert(Location::new(0.4), 1); - density_map - .neighbor_request_counts - .insert(Location::new(0.7), 2); - - let result = density_map.get_max_density(); - assert!(result.is_ok()); - let result = result.unwrap(); - assert_eq!(result, Location::new(0.9)); -} - -#[test] -fn test_get_max_density_empty_neighbors_error() { - let density_map = DensityMap { - neighbor_request_counts: BTreeMap::new(), - }; - - let result = density_map.get_max_density(); - assert!(matches!(result, Err(DensityMapError::EmptyNeighbors))); -}