pub struct TenantForestPool<K, const D: usize>{ /* private fields */ }Expand description
Per-tenant pool of ThresholdedForest detectors.
K is the tenant key type. Typical choices: String,
u64, a small enum, or a newtype wrapping a UUID.
D is the per-point dimensionality, identical across every
tenant in the pool — mixed-dimension pools are not supported
because each tenant’s const D: usize is baked into the forest’s
type.
§Examples
use anomstream_core::{TenantForestPool, ThresholdedForestBuilder};
let mut pool: TenantForestPool<String, 2> = TenantForestPool::new(
4,
|| ThresholdedForestBuilder::<2>::new()
.num_trees(50)
.sample_size(16)
.min_observations(4)
.seed(42)
.build(),
).unwrap();
let verdict_a = pool.process(&"tenant-a".to_string(), [0.1, 0.2]).unwrap();
let verdict_b = pool.process(&"tenant-b".to_string(), [5.0, 5.0]).unwrap();
assert_eq!(pool.len(), 2);Implementations§
Source§impl<K, const D: usize> TenantForestPool<K, D>
impl<K, const D: usize> TenantForestPool<K, D>
Sourcepub fn attribution_stability(
&mut self,
key: &K,
point: &[f64; D],
) -> RcfResult<AttributionStability>
pub fn attribution_stability( &mut self, key: &K, point: &[f64; D], ) -> RcfResult<AttributionStability>
Per-tenant attribution stability. Lazily instantiates the
tenant (like Self::process).
§Errors
Same as ThresholdedForest::attribution_stability plus
factory errors.
§Panics
Never under normal use — the fall-through branch forces a
slot via Self::score_only before re-borrowing through
Self::get_mut; the assertion only fires on an impossible
concurrent eviction through &mut self.
Source§impl<K, const D: usize> TenantForestPool<K, D>
impl<K, const D: usize> TenantForestPool<K, D>
Sourcepub fn group_scores(
&mut self,
key: &K,
point: &[f64; D],
groups: &FeatureGroups,
) -> RcfResult<GroupScores>
pub fn group_scores( &mut self, key: &K, point: &[f64; D], groups: &FeatureGroups, ) -> RcfResult<GroupScores>
Per-tenant decomposition. Lazily instantiates the tenant’s
detector (like Self::process) then delegates to
crate::ThresholdedForest::group_scores.
§Errors
Same as crate::ThresholdedForest::group_scores plus
factory errors.
§Panics
Never under normal use. The fall-through branch uses
Self::score_only to force an entry for the tenant, then
asserts the tenant is resident — the assertion is only
defensive and cannot fire unless a concurrent mutation
evicts the tenant between the two calls, which cannot happen
through &mut self.
Source§impl<K, const D: usize> TenantForestPool<K, D>
impl<K, const D: usize> TenantForestPool<K, D>
Sourcepub fn new<F>(capacity: usize, factory: F) -> RcfResult<Self>
pub fn new<F>(capacity: usize, factory: F) -> RcfResult<Self>
Build a pool bounded at capacity tenants.
The factory is stored and invoked on every first-seen tenant — it must be able to build a detector repeatedly, not just once. Seed the factory’s builder deterministically (or from a per-tenant seed inside the closure) when reproducibility matters.
§Errors
Returns RcfError::InvalidConfig when capacity == 0.
Sourcepub fn with_metrics_sink(self, sink: Arc<dyn MetricsSink>) -> Self
pub fn with_metrics_sink(self, sink: Arc<dyn MetricsSink>) -> Self
Install a crate::MetricsSink for pool-level events.
Emits rcf_tenants_resident gauge updates on every public
mutation and rcf_tenant_evictions_total on LRU evictions.
Per-tenant detector metrics are the factory’s responsibility.
Sourcepub fn metrics_sink(&self) -> &Arc<dyn MetricsSink>
pub fn metrics_sink(&self) -> &Arc<dyn MetricsSink>
Read-only handle to the installed pool-level sink.
Sourcepub fn contains(&self, key: &K) -> bool
pub fn contains(&self, key: &K) -> bool
Whether the tenant currently has a detector in the pool.
Sourcepub fn peek(&self, key: &K) -> Option<&ThresholdedForest<D>>
pub fn peek(&self, key: &K) -> Option<&ThresholdedForest<D>>
Read-only handle to a tenant’s detector. Returns None when
the tenant has never processed a point or has been evicted.
Does not bump the LRU access counter so diagnostic tools
can inspect state without disturbing eviction order.
Sourcepub fn get(&mut self, key: &K) -> Option<&ThresholdedForest<D>>
pub fn get(&mut self, key: &K) -> Option<&ThresholdedForest<D>>
Read-only handle with an LRU touch — the tenant is treated as freshly accessed.
Sourcepub fn get_mut(&mut self, key: &K) -> Option<&mut ThresholdedForest<D>>
pub fn get_mut(&mut self, key: &K) -> Option<&mut ThresholdedForest<D>>
Mutable handle with an LRU touch. Prefer
Self::process / Self::score_only unless you need
direct access to ThresholdedForest methods not exposed by
the pool.
Sourcepub fn process(&mut self, key: &K, point: [f64; D]) -> RcfResult<AnomalyGrade>
pub fn process(&mut self, key: &K, point: [f64; D]) -> RcfResult<AnomalyGrade>
Score a point through the tenant’s detector and graduate it through the adaptive threshold, creating the detector with the factory if this is the first point for the tenant.
If inserting would push the pool past capacity the
least-recently-used tenant is evicted first.
§Errors
Propagates factory errors (when a new detector must be built)
and ThresholdedForest::process errors (malformed point,
etc.).
Sourcepub fn score_only(
&mut self,
key: &K,
point: &[f64; D],
) -> RcfResult<AnomalyGrade>
pub fn score_only( &mut self, key: &K, point: &[f64; D], ) -> RcfResult<AnomalyGrade>
Score a point against the tenant’s detector without mutating
the underlying forest or its statistics. Creates the detector
on first use just like Self::process so the very first
call for a tenant is not surprising — the detector exists
but returns a warming-up verdict (no observations yet).
§Errors
Propagates factory errors and ThresholdedForest::score_only
errors.
Sourcepub fn attribution(&mut self, key: &K, point: &[f64; D]) -> RcfResult<DiVector>
pub fn attribution(&mut self, key: &K, point: &[f64; D]) -> RcfResult<DiVector>
Per-feature attribution for a tenant’s view of a point.
§Errors
Propagates factory errors and
ThresholdedForest::attribution errors.
Sourcepub fn score_only_many(
&mut self,
key: &K,
points: &[[f64; D]],
) -> RcfResult<Option<Vec<AnomalyGrade>>>
pub fn score_only_many( &mut self, key: &K, points: &[[f64; D]], ) -> RcfResult<Option<Vec<AnomalyGrade>>>
Bulk-score a batch of points through the tenant’s detector
without creating the tenant on absence — retention-aware
read path. Returns None when the tenant is absent, or the
batch of graded verdicts otherwise.
§Errors
Propagates ThresholdedForest::score_only_many errors.
Sourcepub fn score_many_early_term(
&mut self,
key: &K,
points: &[[f64; D]],
config: EarlyTermConfig,
) -> RcfResult<Vec<EarlyTermScore>>
pub fn score_many_early_term( &mut self, key: &K, points: &[[f64; D]], config: EarlyTermConfig, ) -> RcfResult<Vec<EarlyTermScore>>
Bulk early-termination scoring on a tenant’s detector.
Auto-creates the tenant (consistent with process).
§Errors
Propagates ThresholdedForest::score_many_early_term errors.
Sourcepub fn score_across_tenants(
&self,
point: &[f64; D],
) -> RcfResult<Vec<(K, AnomalyGrade)>>
pub fn score_across_tenants( &self, point: &[f64; D], ) -> RcfResult<Vec<(K, AnomalyGrade)>>
Cross-tenant what-if scoring — pipe the same point
through every resident tenant’s detector and collect
(key, grade) pairs sorted by descending grade.
Primary use case: MSSP / threat-intel lateral scan. Analyst investigates an anomaly on tenant A, wants to see which other tenants’ baselines flag the same observation — common pattern for supply-chain / shared-infra compromises.
Tenants currently in the warming-up window
(crate::AnomalyGrade::ready returns false) are
skipped so callers only see confidence-bearing grades.
Does not auto-create any tenant. Does not mutate
detector state (read-only path).
§Errors
Propagates crate::ThresholdedForest::score_only errors.
Sourcepub fn similarity_matrix(&self, min_observations: u64) -> Vec<(K, K, f64)>
pub fn similarity_matrix(&self, min_observations: u64) -> Vec<(K, K, f64)>
Pairwise similarity between every tenant in the pool,
computed on each tenant’s anomaly-score EMA stats
(mean, stddev). Tenants with fewer than
min_observations samples are skipped — their stats are
too noisy to compare.
Similarity is exp(-sqrt(Δmean² + Δstddev²)) ∈ (0, 1]:
identical distributions → 1.0, unrelated → near 0.
Returns (key_a, key_b, similarity) triples with
key_a < key_b ordering not guaranteed — callers that care
about a canonical order should sort their own slice.
Sourcepub fn most_similar(
&self,
key: &K,
top_n: usize,
min_observations: u64,
) -> Vec<(K, f64)>
pub fn most_similar( &self, key: &K, top_n: usize, min_observations: u64, ) -> Vec<(K, f64)>
Top-n tenants most similar to key, sorted by descending
similarity. Excludes key itself and tenants below
min_observations. Returns an empty vec when key is
absent or the pool is otherwise empty.
See Self::similarity_matrix for the similarity metric.
Sourcepub fn forensic_baseline(
&mut self,
key: &K,
point: &[f64; D],
) -> RcfResult<Option<ForensicBaseline<D>>>
pub fn forensic_baseline( &mut self, key: &K, point: &[f64; D], ) -> RcfResult<Option<ForensicBaseline<D>>>
Per-tenant imputation-like forensic baseline. Returns None
when the tenant is absent — does not auto-create (forensic
is a read path).
§Errors
Propagates ThresholdedForest::forensic_baseline errors.
Sourcepub fn attribution_many(
&mut self,
key: &K,
points: &[[f64; D]],
) -> RcfResult<Vec<DiVector>>
pub fn attribution_many( &mut self, key: &K, points: &[[f64; D]], ) -> RcfResult<Vec<DiVector>>
Bulk per-feature attribution on a tenant’s detector. Auto-creates the tenant.
§Errors
Propagates ThresholdedForest::attribution_many errors.
Sourcepub fn process_at(
&mut self,
key: &K,
point: [f64; D],
timestamp: u64,
) -> RcfResult<AnomalyGrade>
pub fn process_at( &mut self, key: &K, point: [f64; D], timestamp: u64, ) -> RcfResult<AnomalyGrade>
Timestamped variant of Self::process — tags the freshly
inserted point with timestamp on the tenant’s forest, so
Self::delete_before can retract history by age.
§Errors
Propagates ThresholdedForest::process_at errors.
Sourcepub fn delete_before(&mut self, key: &K, cutoff: u64) -> RcfResult<usize>
pub fn delete_before(&mut self, key: &K, cutoff: u64) -> RcfResult<usize>
Retract every point older than cutoff from a tenant’s
detector. Returns Ok(0) (without creating the tenant) when
the tenant is absent — retention paths must never spin up a
fresh detector.
§Errors
Propagates ThresholdedForest::delete_before errors.
Sourcepub fn score_early_term(
&mut self,
key: &K,
point: &[f64; D],
config: EarlyTermConfig,
) -> RcfResult<EarlyTermScore>
pub fn score_early_term( &mut self, key: &K, point: &[f64; D], config: EarlyTermConfig, ) -> RcfResult<EarlyTermScore>
Early-termination scoring on a tenant’s detector. Auto-
creates the tenant (like Self::process) — cold-start
returns EmptyForest, just like
ThresholdedForest::score_early_term.
§Errors
Propagates factory errors and
ThresholdedForest::score_early_term errors.
Sourcepub fn delete(&mut self, key: &K, point_idx: usize) -> RcfResult<bool>
pub fn delete(&mut self, key: &K, point_idx: usize) -> RcfResult<bool>
Retract a previously-observed point from a tenant’s forest by
its point_idx. Returns Ok(false) (and does not create the
tenant) when the tenant is absent — SOC retraction paths must
not silently spin up fresh detectors.
§Errors
Propagates ThresholdedForest::delete errors.
Sourcepub fn delete_by_value(&mut self, key: &K, point: &[f64; D]) -> RcfResult<usize>
pub fn delete_by_value(&mut self, key: &K, point: &[f64; D]) -> RcfResult<usize>
Retract every point whose stored value bit-matches point
for a given tenant. Returns Ok(0) (and does not create the
tenant) when the tenant is absent.
§Errors
Propagates ThresholdedForest::delete_by_value errors.
Sourcepub fn bootstrap<I>(&mut self, key: &K, points: I) -> RcfResult<BootstrapReport>
pub fn bootstrap<I>(&mut self, key: &K, points: I) -> RcfResult<BootstrapReport>
Replay historical points into the tenant’s detector before
any live traffic. Lazily instantiates the tenant (like
Self::process), then delegates to
ThresholdedForest::bootstrap. Returns a report summarising
ingestion.
Use this when restarting a long-running agent: pull recent
per-tenant history from the upstream TSDB (Prometheus,
Loki, InfluxDB, parquet dump…), hand it to this method per
tenant, and the pool is hot before the live streaming pipeline
is switched back on — avoiding the per-tenant warmup coverage
hole.
§Errors
Propagates factory and
ThresholdedForest::bootstrap errors.
Sourcepub fn insert(
&mut self,
key: K,
forest: ThresholdedForest<D>,
) -> Option<ThresholdedForest<D>>
pub fn insert( &mut self, key: K, forest: ThresholdedForest<D>, ) -> Option<ThresholdedForest<D>>
Install a pre-built detector for key, replacing any
existing entry. Useful for warm reload — iterate a directory
of per-tenant snapshots and pump them back into a fresh pool.
Returns the displaced detector if one was already resident.
When inserting a brand-new tenant would push the pool past
capacity, the least-recently-used tenant is evicted first.
Sourcepub fn remove(&mut self, key: &K) -> Option<ThresholdedForest<D>>
pub fn remove(&mut self, key: &K) -> Option<ThresholdedForest<D>>
Drop the tenant’s detector. Returns the detector so callers can hand it back to the factory or persist it before release.
Sourcepub fn iter(&self) -> impl Iterator<Item = (&K, &ThresholdedForest<D>)> + '_
pub fn iter(&self) -> impl Iterator<Item = (&K, &ThresholdedForest<D>)> + '_
Iterate (key, detector) pairs in an unspecified order —
use this for snapshot / migration.
Sourcepub fn iter_mut(
&mut self,
) -> impl Iterator<Item = (&K, &mut ThresholdedForest<D>)> + '_
pub fn iter_mut( &mut self, ) -> impl Iterator<Item = (&K, &mut ThresholdedForest<D>)> + '_
Mutable iteration over (key, detector) pairs. Does not bump
any tenant’s LRU tick — callers are assumed to be scanning
for bulk operations (save to disk, migrate, reset stats).
Sourcepub fn readiness_summary(&self) -> ReadinessSummary
pub fn readiness_summary(&self) -> ReadinessSummary
Snapshot aggregate readiness of the pool — how many tenants
are warm vs warming, capacity headroom, and lifetime
create/evict counters. Cheap O(resident) scan.
Use for /healthz / /readyz endpoints: a pool with
readiness_ratio() < 0.5 on a mature deployment typically
indicates either bootstrap lag or aggressive eviction
thrashing.
Sourcepub fn evict_lru(&mut self) -> Option<(K, ThresholdedForest<D>)>
pub fn evict_lru(&mut self) -> Option<(K, ThresholdedForest<D>)>
Evict the least-recently-used tenant explicitly. Returns the
evicted (key, detector) pair so callers can persist it
before release.
Public so custom eviction strategies (time-based, manual shedding, etc.) can drive the pool without going through the auto-eviction path.
Sourcepub fn evict_idle(&mut self, ttl: Duration) -> Vec<(K, ThresholdedForest<D>)>
pub fn evict_idle(&mut self, ttl: Duration) -> Vec<(K, ThresholdedForest<D>)>
Evict every tenant whose wall-clock last-access is older than
ttl. Returns the evicted (key, detector) pairs in
unspecified order so callers can persist them before release
(SOC retention windows, GDPR purge, cold-path archival).
Orthogonal to Self::evict_lru: LRU sheds on capacity
pressure, evict_idle sheds on wall-clock staleness.
Call on a schedule (e.g. every minute from a background
task) — the pool mutation takes &mut self so the caller
owns the cadence.
Bumps both the aggregate rcf_tenant_evictions_total counter
and the dedicated rcf_tenant_idle_evictions_total counter
so dashboards can distinguish pressure-driven churn from
retention-driven shedding.
Trait Implementations§
Auto Trait Implementations§
impl<K, const D: usize> Freeze for TenantForestPool<K, D>
impl<K, const D: usize> !RefUnwindSafe for TenantForestPool<K, D>
impl<K, const D: usize> !Send for TenantForestPool<K, D>
impl<K, const D: usize> !Sync for TenantForestPool<K, D>
impl<K, const D: usize> Unpin for TenantForestPool<K, D>where
K: Unpin,
impl<K, const D: usize> UnsafeUnpin for TenantForestPool<K, D>
impl<K, const D: usize> !UnwindSafe for TenantForestPool<K, D>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more