using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Core.Stability;
///
/// Tier-agnostic memory-footprint tracker. Captures the post-initialize baseline
/// from the first samples after IDriver.InitializeAsync, then classifies each
/// subsequent sample against a hybrid soft/hard threshold per
/// docs/v2/plan.md decision #146 — soft = max(multiplier × baseline, baseline + floor),
/// hard = 2 × soft.
///
///
/// Per decision #145, this tracker never kills a process. Soft and hard breaches
/// log + surface to the Admin UI via DriverInstanceResilienceStatus. The matching
/// process-level recycle protection lives in a separate MemoryRecycle that activates
/// for Tier C drivers only (where the driver runs out-of-process behind a supervisor that
/// can safely restart it without tearing down the OPC UA session or co-hosted in-proc
/// drivers).
///
/// Baseline capture: the tracker starts in for
/// (default 5 min). During that window samples are collected;
/// the baseline is computed as the median once the window elapses. Before that point every
/// classification returns .
///
public sealed class MemoryTracking
{
private readonly DriverTier _tier;
private readonly TimeSpan _baselineWindow;
private readonly List _warmupSamples = [];
private long _baselineBytes;
private TrackingPhase _phase = TrackingPhase.WarmingUp;
private DateTime? _warmupStartUtc;
/// Tier-default multiplier/floor constants per decision #146.
public static (int Multiplier, long FloorBytes) GetTierConstants(DriverTier tier) => tier switch
{
DriverTier.A => (Multiplier: 3, FloorBytes: 50L * 1024 * 1024),
DriverTier.B => (Multiplier: 3, FloorBytes: 100L * 1024 * 1024),
DriverTier.C => (Multiplier: 2, FloorBytes: 500L * 1024 * 1024),
_ => throw new ArgumentOutOfRangeException(nameof(tier), tier, $"No memory-tracking constants defined for tier {tier}."),
};
/// Window over which post-init samples are collected to compute the baseline.
public TimeSpan BaselineWindow => _baselineWindow;
/// Current phase: or .
public TrackingPhase Phase => _phase;
/// Captured baseline; 0 until warmup completes.
public long BaselineBytes => _baselineBytes;
/// Effective soft threshold (zero while warming up).
public long SoftThresholdBytes => _baselineBytes == 0 ? 0 : ComputeSoft(_tier, _baselineBytes);
/// Effective hard threshold = 2 × soft (zero while warming up).
public long HardThresholdBytes => _baselineBytes == 0 ? 0 : ComputeSoft(_tier, _baselineBytes) * 2;
public MemoryTracking(DriverTier tier, TimeSpan? baselineWindow = null)
{
_tier = tier;
_baselineWindow = baselineWindow ?? TimeSpan.FromMinutes(5);
}
///
/// Submit a memory-footprint sample. Returns the action the caller should surface.
/// During warmup, always returns and accumulates
/// samples; once the window elapses the first steady-phase sample triggers baseline capture
/// (median of warmup samples).
///
public MemoryTrackingAction Sample(long footprintBytes, DateTime utcNow)
{
if (_phase == TrackingPhase.WarmingUp)
{
_warmupStartUtc ??= utcNow;
_warmupSamples.Add(footprintBytes);
if (utcNow - _warmupStartUtc.Value >= _baselineWindow && _warmupSamples.Count > 0)
{
_baselineBytes = ComputeMedian(_warmupSamples);
_phase = TrackingPhase.Steady;
}
else
{
return MemoryTrackingAction.Warming;
}
}
if (footprintBytes >= HardThresholdBytes) return MemoryTrackingAction.HardBreach;
if (footprintBytes >= SoftThresholdBytes) return MemoryTrackingAction.SoftBreach;
return MemoryTrackingAction.None;
}
private static long ComputeSoft(DriverTier tier, long baseline)
{
var (multiplier, floor) = GetTierConstants(tier);
return Math.Max(multiplier * baseline, baseline + floor);
}
private static long ComputeMedian(List samples)
{
var sorted = samples.Order().ToArray();
var mid = sorted.Length / 2;
return sorted.Length % 2 == 1
? sorted[mid]
: (sorted[mid - 1] + sorted[mid]) / 2;
}
}
/// Phase of a lifecycle.
public enum TrackingPhase
{
/// Collecting post-init samples; baseline not yet computed.
WarmingUp,
/// Baseline captured; every sample classified against soft/hard thresholds.
Steady,
}
/// Classification the tracker returns per sample.
public enum MemoryTrackingAction
{
/// Baseline not yet captured; sample collected, no threshold check.
Warming,
/// Below soft threshold.
None,
/// Between soft and hard thresholds — log + surface, no action.
SoftBreach,
///
/// ≥ hard threshold. Log + surface + (Tier C only, via MemoryRecycle) request
/// process recycle via the driver supervisor. Tier A/B breach never invokes any
/// kill path per decisions #145 and #74.
///
HardBreach,
}