SystemMonitor.cs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. using dodohold.core;
  2. using Microsoft.Extensions.Logging;
  3. using System.Diagnostics;
  4. using System.Text;
  5. namespace molilian.core
  6. {
  7. public static class SystemMonitor
  8. {
  9. private static Timer _monitorTimer;
  10. private static int _isCollecting;
  11. private static readonly AlertState MemoryAlertState = new();
  12. private static readonly int MemoryAlertThresholdMb = GetIntEnv("SystemMonitorMemoryAlertMB", 2048);
  13. private static readonly int MemoryAlertRecoveryMb = GetIntEnv("SystemMonitorMemoryRecoveryMB", (int)(MemoryAlertThresholdMb * 0.9));
  14. private static readonly int MemoryAlertConsecutiveHits = GetIntEnv("SystemMonitorMemoryAlertConsecutiveHits", 3);
  15. private static readonly int MemoryAlertCooldownMinutes = GetIntEnv("SystemMonitorMemoryAlertCooldownMinutes", 30);
  16. public static void StartMonitoring(int intervalSeconds = 30)
  17. {
  18. _monitorTimer = new Timer(
  19. CollectMetrics,
  20. null,
  21. TimeSpan.Zero,
  22. TimeSpan.FromSeconds(intervalSeconds)
  23. );
  24. }
  25. private static void CollectMetrics(object state)
  26. {
  27. if (Interlocked.Exchange(ref _isCollecting, 1) == 1) return;
  28. try
  29. {
  30. var metrics = new StringBuilder();
  31. // 线程池信息
  32. ThreadPool.GetMaxThreads(out int maxWorkerThreads, out int maxIoThreads);
  33. ThreadPool.GetAvailableThreads(out int availWorkerThreads, out int availIoThreads);
  34. ThreadPool.GetMinThreads(out int minWorkerThreads, out int minIoThreads);
  35. var usedWorkerThreads = maxWorkerThreads - availWorkerThreads;
  36. var usedIoThreads = maxIoThreads - availIoThreads;
  37. metrics.AppendLine("=== Thread Pool Stats ===");
  38. metrics.AppendLine($"Worker Threads: {usedWorkerThreads}/{maxWorkerThreads} (used/max)");
  39. metrics.AppendLine($"IO Threads: {usedIoThreads}/{maxIoThreads} (used/max)");
  40. metrics.AppendLine($"Min Worker Threads: {minWorkerThreads}");
  41. metrics.AppendLine($"Min IO Threads: {minIoThreads}");
  42. metrics.AppendLine($"Thread Pool Queue Length: {ThreadPool.PendingWorkItemCount}");
  43. metrics.AppendLine();
  44. // 内存信息
  45. var process = Process.GetCurrentProcess();
  46. var gcInfo = GC.GetGCMemoryInfo();
  47. metrics.AppendLine("=== Memory Stats ===");
  48. metrics.AppendLine($"Working Set: {process.WorkingSet64 / 1024 / 1024} MB");
  49. metrics.AppendLine($"Private Memory: {process.PrivateMemorySize64 / 1024 / 1024} MB");
  50. metrics.AppendLine($"GC Heap Size: {GC.GetTotalMemory(false) / 1024 / 1024} MB");
  51. metrics.AppendLine($"Gen 0 Collections: {GC.CollectionCount(0)}");
  52. metrics.AppendLine($"Gen 1 Collections: {GC.CollectionCount(1)}");
  53. metrics.AppendLine($"Gen 2 Collections: {GC.CollectionCount(2)}");
  54. metrics.AppendLine($"Memory Load: {gcInfo.MemoryLoadBytes / 1024 / 1024} MB");
  55. // 记录日志
  56. _ = new LoggerLibrary("debug", "metrics").Info(metrics.ToString()).SaveAsync();
  57. // 检查是否需要报警
  58. CheckAlertConditions(
  59. usedWorkerThreads, maxWorkerThreads,
  60. process.WorkingSet64,
  61. ThreadPool.PendingWorkItemCount
  62. );
  63. }
  64. catch (Exception ex)
  65. {
  66. _ = new LoggerLibrary("debug", "metrics_error")
  67. .Info($"Error collecting system metrics: {ex}").SaveAsync();
  68. }
  69. finally
  70. {
  71. Volatile.Write(ref _isCollecting, 0);
  72. }
  73. }
  74. private static void CheckAlertConditions(
  75. int usedWorkerThreads,
  76. int maxWorkerThreads,
  77. long workingSet,
  78. long pendingWork)
  79. {
  80. var alerts = new List<string>();
  81. // 线程池使用率超过80%
  82. if ((double)usedWorkerThreads / maxWorkerThreads > 0.8)
  83. {
  84. alerts.Add($"High thread pool usage: {usedWorkerThreads}/{maxWorkerThreads}");
  85. }
  86. HandleMemoryAlert(workingSet);
  87. // 线程池队列堆积
  88. if (pendingWork > 100)
  89. {
  90. alerts.Add($"High thread pool queue length: {pendingWork}");
  91. }
  92. if (alerts.Any())
  93. {
  94. NotifyCore.Notify($"System alerts:\n{string.Join("\n", alerts)}");
  95. }
  96. }
  97. public static void StopMonitoring()
  98. {
  99. _monitorTimer?.Dispose();
  100. }
  101. private static void HandleMemoryAlert(long workingSet)
  102. {
  103. int workingSetMb = (int)(workingSet / 1024 / 1024);
  104. bool isTriggered = workingSetMb >= MemoryAlertThresholdMb;
  105. bool isRecovered = workingSetMb <= MemoryAlertRecoveryMb;
  106. var now = DateTime.UtcNow;
  107. lock (MemoryAlertState)
  108. {
  109. if (isTriggered)
  110. {
  111. MemoryAlertState.ConsecutiveHits++;
  112. bool shouldSend = MemoryAlertState.ConsecutiveHits >= MemoryAlertConsecutiveHits &&
  113. (!MemoryAlertState.IsActive || now - MemoryAlertState.LastSentAtUtc >= TimeSpan.FromMinutes(MemoryAlertCooldownMinutes));
  114. if (shouldSend)
  115. {
  116. NotifyCore.Notify(
  117. $"System alerts:\nHigh memory usage: {workingSetMb} MB\n" +
  118. $"threshold={MemoryAlertThresholdMb} MB, consecutiveHits={MemoryAlertState.ConsecutiveHits}");
  119. MemoryAlertState.IsActive = true;
  120. MemoryAlertState.LastSentAtUtc = now;
  121. }
  122. return;
  123. }
  124. MemoryAlertState.ConsecutiveHits = 0;
  125. if (MemoryAlertState.IsActive && isRecovered)
  126. {
  127. NotifyCore.Notify(
  128. $"System recovery:\nMemory usage recovered to {workingSetMb} MB\n" +
  129. $"recoveryThreshold={MemoryAlertRecoveryMb} MB");
  130. MemoryAlertState.IsActive = false;
  131. MemoryAlertState.LastSentAtUtc = now;
  132. }
  133. }
  134. }
  135. private static int GetIntEnv(string key, int defaultValue)
  136. {
  137. var raw = Environment.GetEnvironmentVariable(key);
  138. return int.TryParse(raw, out var value) && value > 0 ? value : defaultValue;
  139. }
  140. private sealed class AlertState
  141. {
  142. public int ConsecutiveHits { get; set; }
  143. public bool IsActive { get; set; }
  144. public DateTime LastSentAtUtc { get; set; } = DateTime.MinValue;
  145. }
  146. }
  147. }