01fd90c178
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
65 lines
2.5 KiB
C#
65 lines
2.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Stability;
|
|
|
|
/// <summary>
|
|
/// Galaxy-specific RSS watchdog per <c>driver-stability.md §"Memory Watchdog Thresholds"</c>.
|
|
/// Baseline-relative + absolute caps. Sustained-slope detection uses a rolling 30-min window.
|
|
/// Pluggable RSS source keeps it unit-testable.
|
|
/// </summary>
|
|
public sealed class MemoryWatchdog
|
|
{
|
|
/// <summary>Absolute hard ceiling — process is force-killed above this.</summary>
|
|
public long HardCeilingBytes { get; init; } = 1_500L * 1024 * 1024;
|
|
|
|
/// <summary>Sustained slope (bytes/min) above which soft recycle is scheduled.</summary>
|
|
public long SustainedSlopeBytesPerMinute { get; init; } = 5L * 1024 * 1024;
|
|
|
|
public TimeSpan SlopeWindow { get; init; } = TimeSpan.FromMinutes(30);
|
|
|
|
private readonly long _baselineBytes;
|
|
private readonly Queue<RssSample> _samples = new();
|
|
|
|
public MemoryWatchdog(long baselineBytes)
|
|
{
|
|
_baselineBytes = baselineBytes;
|
|
}
|
|
|
|
/// <summary>Called every 30s with the current RSS. Returns the action the supervisor should take.</summary>
|
|
public WatchdogAction Sample(long rssBytes, DateTime utcNow)
|
|
{
|
|
_samples.Enqueue(new RssSample(utcNow, rssBytes));
|
|
while (_samples.Count > 0 && utcNow - _samples.Peek().TimestampUtc > SlopeWindow)
|
|
_samples.Dequeue();
|
|
|
|
if (rssBytes >= HardCeilingBytes)
|
|
return WatchdogAction.HardKill;
|
|
|
|
var softThreshold = Math.Max(_baselineBytes * 2, _baselineBytes + 200L * 1024 * 1024);
|
|
var warnThreshold = Math.Max((long)(_baselineBytes * 1.5), _baselineBytes + 200L * 1024 * 1024);
|
|
|
|
if (rssBytes >= softThreshold) return WatchdogAction.SoftRecycle;
|
|
if (rssBytes >= warnThreshold) return WatchdogAction.Warn;
|
|
|
|
if (_samples.Count >= 2)
|
|
{
|
|
var oldest = _samples.Peek();
|
|
var span = (utcNow - oldest.TimestampUtc).TotalMinutes;
|
|
if (span >= SlopeWindow.TotalMinutes * 0.9) // need ~full window to trust the slope
|
|
{
|
|
var delta = rssBytes - oldest.RssBytes;
|
|
var bytesPerMin = delta / span;
|
|
if (bytesPerMin >= SustainedSlopeBytesPerMinute)
|
|
return WatchdogAction.SoftRecycle;
|
|
}
|
|
}
|
|
|
|
return WatchdogAction.None;
|
|
}
|
|
|
|
private readonly record struct RssSample(DateTime TimestampUtc, long RssBytes);
|
|
}
|
|
|
|
public enum WatchdogAction { None, Warn, SoftRecycle, HardKill }
|