Files
Ayay/SHH.ProcessLaunchers/IProcessManager.cs

176 lines
6.5 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
namespace SHH.ProcessLaunchers
{
/// <summary>
/// 进程管理器核心接口
/// </summary>
public interface IProcessManager
{
/// <summary>
/// 注册一个要管理的进程配置
/// </summary>
/// <param name="config">进程配置对象</param>
void Register(ProcessConfig config);
/// <summary>
/// 启动指定名称的进程
/// </summary>
/// <param name="name">配置中定义的 Name</param>
void Start(string name);
/// <summary>
/// 启动所有已注册的进程
/// </summary>
Task StartAllAsync();
/// <summary>
/// 停止指定进程 (优雅或强制)
/// </summary>
/// <param name="name">进程名称</param>
void Stop(string name);
/// <summary>
/// 停止所有进程
/// </summary>
void StopAll();
/// <summary>
/// 获取指定进程的资源监控复位接口
/// </summary>
/// <param name="processName">进程名称</param>
void ResetGuard(string processName);
/// <summary>
/// 获取所有进程的实时状态快照
/// </summary>
List<ProcessInfoSnapshot> GetSnapshot();
// --- 事件定义 ---
/// <summary>
/// 当接收到子进程的标准输出或错误流时触发
/// </summary>
event EventHandler<ProcessOutputEventArgs> OnOutputReceived;
/// <summary>
/// 当进程生命周期状态发生变化时触发
/// </summary>
event EventHandler<ProcessStateEventArgs> OnStateChanged;
}
public class MemoryGuard : IResourceGuard
{
public string Name => "MemoryGuard";
private readonly long _warningBytes;
private readonly long _criticalBytes;
private readonly TimeSpan _alertDuration; // 持续时间阈值 (如 3分钟)
// --- 内部状态 ---
private DateTime? _firstOverLimitTime; // 第一次检测到超限的时间
private bool _isAlertLatched = false; // 是否已经报过警 (自锁)
/// <summary>
/// 智能内存哨兵
/// </summary>
/// <param name="warningMb">警告阈值</param>
/// <param name="criticalMb">熔断阈值</param>
/// <param name="durationMinutes">必须持续超限多少分钟才报警</param>
public MemoryGuard(int warningMb, int criticalMb, int durationMinutes = 3)
{
_warningBytes = (long)warningMb * 1024 * 1024;
_criticalBytes = (long)criticalMb * 1024 * 1024;
_alertDuration = TimeSpan.FromMinutes(durationMinutes);
}
public GuardResult Check(Process process, out string reason)
{
reason = null;
try
{
process.Refresh();
long currentUsage = process.WorkingSet64;
// 1. 优先检查 Critical (熔断线)
// 逻辑:熔断涉及生死,不需要防抖,也不受“已报警”锁定的限制。
// 哪怕用户标记了已处置,只要内存爆了,必须重启。
if (currentUsage > _criticalBytes)
{
reason = $"[严重] 内存 {FormatSize(currentUsage)} > 熔断线 {FormatSize(_criticalBytes)} (立即执行管控)";
// 重启后,物理进程会变,下一次 Check 会是新进程,状态建议在重启时由外部重置,
// 或者这里不重置,依靠 ProcessManager 重启后重新创建 Guard 实例。
return GuardResult.Critical;
}
// 2. 检查 Warning (警告线) - 包含防抖和自锁逻辑
if (currentUsage > _warningBytes)
{
// A. 如果已经报过警 (已锁定),则不再报,保持沉默
if (_isAlertLatched)
{
return GuardResult.Normal;
}
// B. 如果是刚发现超限,记录时间
if (_firstOverLimitTime == null)
{
_firstOverLimitTime = DateTime.Now;
// 这里可以选做:记录一条 Info 日志,告诉用户"正在观察中"
// reason = $"内存超限 {FormatSize(currentUsage)},开始计时观察...";
return GuardResult.Normal; // 暂时不报 Warning
}
// C. 检查持续时间
var duration = DateTime.Now - _firstOverLimitTime.Value;
if (duration >= _alertDuration)
{
// 满足持续时间 -> 触发报警并锁定
_isAlertLatched = true;
_firstOverLimitTime = null; // 计时归零
reason = $"[报警] 内存 {FormatSize(currentUsage)} > 阈值 {FormatSize(_warningBytes)} 且持续超过 {_alertDuration.TotalMinutes}分钟";
return GuardResult.Warning; // 抛出信号,发邮件!
}
else
{
// 还没到时间
return GuardResult.Normal;
}
}
else
{
// 3. 内存正常
// 逻辑如果之前在计时比如超了1分钟现在降下来了则计时器清零。
// 但如果已经报过警 (_isAlertLatched=true),则保持锁定,不自动复位。
// 除非用户手动点 Reset。
if (_firstOverLimitTime != null)
{
_firstOverLimitTime = null; // 波动防抖:由于降下来了,重置观察计时
}
return GuardResult.Normal;
}
}
catch
{
return GuardResult.Normal;
}
}
/// <summary>
/// 用户点击“已处置”时调用
/// </summary>
public void Reset()
{
_isAlertLatched = false;
_firstOverLimitTime = null;
}
private string FormatSize(long bytes) => $"{bytes / 1024 / 1024}MB";
}
}