Skip to main content

TenantForestPool

Struct TenantForestPool 

Source
pub struct TenantForestPool<K, const D: usize>
where K: Hash + Eq + Clone,
{ /* 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>
where K: Hash + Eq + Clone,

Source

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>
where K: Hash + Eq + Clone,

Source

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>
where K: Hash + Eq + Clone,

Source

pub fn new<F>(capacity: usize, factory: F) -> RcfResult<Self>
where F: Fn() -> RcfResult<ThresholdedForest<D>> + 'static,

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.

Source

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.

Source

pub fn metrics_sink(&self) -> &Arc<dyn MetricsSink>

Read-only handle to the installed pool-level sink.

Source

pub fn capacity(&self) -> usize

Maximum number of tenants the pool holds simultaneously.

Source

pub fn len(&self) -> usize

Current number of live tenants.

Source

pub fn is_empty(&self) -> bool

Whether the pool is empty.

Source

pub fn contains(&self, key: &K) -> bool

Whether the tenant currently has a detector in the pool.

Source

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.

Source

pub fn get(&mut self, key: &K) -> Option<&ThresholdedForest<D>>

Read-only handle with an LRU touch — the tenant is treated as freshly accessed.

Source

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.

Source

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.).

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn score_across_tenants( &self, point: &[f64; D], ) -> RcfResult<Vec<(K, AnomalyGrade)>>
where K: Send + Sync,

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.

Source

pub fn similarity_matrix(&self, min_observations: u64) -> Vec<(K, K, f64)>
where K: Send + Sync,

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn bootstrap<I>(&mut self, key: &K, points: I) -> RcfResult<BootstrapReport>
where I: IntoIterator<Item = [f64; D]>,

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.

Source

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.

Source

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.

Source

pub fn clear(&mut self)

Drop every tenant’s detector.

Source

pub fn iter(&self) -> impl Iterator<Item = (&K, &ThresholdedForest<D>)> + '_

Iterate (key, detector) pairs in an unspecified order — use this for snapshot / migration.

Source

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).

Source

pub fn tenants(&self) -> Vec<K>

Snapshot every tenant key currently held.

Source

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.

Source

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.

Source

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§

Source§

impl<K, const D: usize> Debug for TenantForestPool<K, D>
where K: Hash + Eq + Clone + Debug,

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

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> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V