use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Default)]
pub(crate) struct PlacementMigrationMetrics {
sent: AtomicU64,
received: AtomicU64,
acted: AtomicU64,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct PlacementMigrationSnapshot {
pub sent: u64,
pub received: u64,
pub acted: u64,
}
impl PlacementMigrationMetrics {
#[inline]
pub(crate) fn record_sent(&self) {
self.sent.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub(crate) fn record_received(&self) {
self.received.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub(crate) fn record_acted(&self) {
self.acted.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn snapshot(&self) -> PlacementMigrationSnapshot {
PlacementMigrationSnapshot {
sent: self.sent.load(Ordering::Relaxed),
received: self.received.load(Ordering::Relaxed),
acted: self.acted.load(Ordering::Relaxed),
}
}
}
pub(crate) const CLOSE_RING_DISTANCE: f64 = 0.1;
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct HostedKeyDistanceStats {
pub count: u64,
pub median: f64,
pub p90: f64,
pub min: f64,
pub mean: f64,
pub frac_within_0_1: f64,
}
pub(crate) fn placement_quality(
node_location: f64,
contract_locations: &[f64],
) -> Option<HostedKeyDistanceStats> {
if contract_locations.is_empty() {
return None;
}
let node = crate::ring::Location::new_rounded(node_location);
let mut distances: Vec<f64> = contract_locations
.iter()
.map(|&loc| {
node.distance(crate::ring::Location::new_rounded(loc))
.as_f64()
})
.collect();
distances.sort_by(|a, b| a.total_cmp(b));
let count = distances.len();
let sum: f64 = distances.iter().sum();
let mean = sum / count as f64;
let min = distances[0];
let within = distances
.iter()
.filter(|&&d| d <= CLOSE_RING_DISTANCE)
.count();
let frac_within_0_1 = within as f64 / count as f64;
Some(HostedKeyDistanceStats {
count: count as u64,
median: percentile(&distances, 0.5),
p90: percentile(&distances, 0.9),
min,
mean,
frac_within_0_1,
})
}
fn percentile(sorted: &[f64], q: f64) -> f64 {
debug_assert!(!sorted.is_empty(), "percentile of empty slice");
debug_assert!((0.0..=1.0).contains(&q), "percentile q out of range");
let n = sorted.len();
let rank = (q * n as f64).ceil() as usize;
let idx = rank.clamp(1, n) - 1;
sorted[idx]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn placement_quality_empty_is_none() {
assert!(placement_quality(0.5, &[]).is_none());
}
#[test]
fn placement_quality_single_contract_at_node() {
let stats = placement_quality(0.5, &[0.5]).expect("non-empty");
assert_eq!(stats.count, 1);
assert!((stats.median - 0.0).abs() < 1e-12);
assert!((stats.p90 - 0.0).abs() < 1e-12);
assert!((stats.min - 0.0).abs() < 1e-12);
assert!((stats.mean - 0.0).abs() < 1e-12);
assert!((stats.frac_within_0_1 - 1.0).abs() < 1e-12);
}
#[test]
fn placement_quality_known_distribution() {
let node = 0.5;
let contracts = [0.5, 0.55, 0.6, 0.7, 0.9];
let stats = placement_quality(node, &contracts).expect("non-empty");
assert_eq!(stats.count, 5);
assert!((stats.median - 0.1).abs() < 1e-9, "median={}", stats.median);
assert!((stats.p90 - 0.4).abs() < 1e-9, "p90={}", stats.p90);
assert!((stats.min - 0.0).abs() < 1e-12, "min={}", stats.min);
assert!((stats.mean - 0.15).abs() < 1e-9, "mean={}", stats.mean);
assert!(
(stats.frac_within_0_1 - 0.6).abs() < 1e-9,
"frac_within_0_1={}",
stats.frac_within_0_1
);
}
#[test]
fn placement_quality_uses_wrap_around_distance() {
let stats = placement_quality(0.95, &[0.02]).expect("non-empty");
assert!(
(stats.median - 0.07).abs() < 1e-9,
"wrap distance median={}",
stats.median
);
assert!(stats.median <= 0.5, "ring distance must be <= 0.5");
}
#[test]
fn placement_quality_far_contracts_low_close_fraction() {
let stats = placement_quality(0.0, &[0.4, 0.45, 0.5]).expect("non-empty");
assert_eq!(stats.count, 3);
assert!(
(stats.frac_within_0_1 - 0.0).abs() < 1e-12,
"no contract should be within 0.1, frac={}",
stats.frac_within_0_1
);
assert!(stats.min >= CLOSE_RING_DISTANCE);
}
#[test]
fn percentile_nearest_rank_endpoints() {
let sorted = [0.0, 0.1, 0.2, 0.3, 0.4];
assert_eq!(percentile(&sorted, 0.0), 0.0);
assert_eq!(percentile(&sorted, 1.0), 0.4);
}
#[test]
fn metrics_counters_increment_independently() {
let m = PlacementMigrationMetrics::default();
let s0 = m.snapshot();
assert_eq!((s0.sent, s0.received, s0.acted), (0, 0, 0));
m.record_sent();
m.record_sent();
m.record_received();
m.record_acted();
m.record_received();
m.record_received();
let s1 = m.snapshot();
assert_eq!(s1.sent, 2, "sent");
assert_eq!(s1.received, 3, "received");
assert_eq!(s1.acted, 1, "acted");
}
#[test]
fn migration_counter_sites_present() {
let migration_src = include_str!("../node/network_bridge/p2p_protoc/migration.rs");
assert_eq!(
migration_src
.matches(".placement_migration_metrics()")
.count(),
1,
"the SubscribeHint SEND site must reach the placement-migration metrics exactly once"
);
assert_eq!(
migration_src.matches(".record_sent()").count(),
1,
"the SubscribeHint SEND site must increment `sent` exactly once"
);
let node_src = include_str!("../node.rs");
let arm_start = node_src
.find("NetMessageV1::SubscribeHint(hint) =>")
.expect("SubscribeHint arm present in node.rs");
let after = &node_src[arm_start..];
let arm_end = after
.find("NetMessageV1::Aborted(tx) =>")
.expect("Aborted arm follows SubscribeHint arm");
let arm = &after[..arm_end];
assert_eq!(
arm.matches(".record_received()").count(),
1,
"the SubscribeHint RECEIVE site must increment `received` exactly once \
(counted before the admission gates)"
);
assert_eq!(
arm.matches(".record_acted()").count(),
1,
"the SubscribeHint ACTED site must increment `acted` exactly once \
(only on the branch that starts the directed subscribe)"
);
let acted_at = arm.find(".record_acted()").expect("acted present");
let act_log_at = arm
.find("Received SubscribeHint — starting directed subscribe to holder")
.expect("act-branch log present");
assert!(
acted_at > act_log_at,
"`record_acted()` must sit on the act branch (after the directed-subscribe \
log), so gated/dropped hints are not counted as acted"
);
}
}