QMonReceiver.cs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. using System.Collections.Concurrent;
  2. using System.IO.Abstractions;
  3. using System.Net;
  4. using System.Net.Sockets;
  5. using System.Text;
  6. using System.Text.Json;
  7. using System.Text.Json.Serialization;
  8. using Quadarax.Foundation.Core.Logging;
  9. using Quadarax.Foundation.Core.QMonitor.Interfaces;
  10. namespace Quadarax.Foundation.Core.QMonitor
  11. {
  12. public delegate void GeneralDataReceivedDelegate(MonGeneral general);
  13. public delegate void DataReceivedDelegate(MonData[] data);
  14. public class QMonReceiver : QMonHostBase<QMonReceiverConfiguration>
  15. {
  16. #region *** Properties ***
  17. protected override Uri UriGeneral => Configuration.SourceUriGeneral;
  18. protected override Uri UriData => Configuration.SourceUriData;
  19. protected override TimeSpan IntervalGeneral => Configuration.GeneralReceiverInterval;
  20. protected override TimeSpan? IntervalData => Configuration.DataCache.FlushCacheInterval;
  21. public MonGeneral[] AvailableGeneral => _generalCache.Values.Select(x => x.Data).ToArray();
  22. #endregion
  23. #region *** Fields ***
  24. private GeneralDataReceivedDelegate _generalReceivedCallback;
  25. private DataReceivedDelegate _dataReceivedCallback;
  26. private bool _isReceivingGeneral;
  27. private CancellationTokenSource _dataReceiverCancel;
  28. private IDictionary<string, MonReceiverGeneralCahedItem> _generalCache = new ConcurrentDictionary<string, MonReceiverGeneralCahedItem>();
  29. private IList<MonData> _dataCache = new List<MonData>();
  30. private JsonSerializerOptions _jsonSerializerOptions;
  31. private DateTime _lastSavedTimestamp;
  32. private IFileSystem _fileSystem;
  33. private bool _isDataClosing;
  34. #endregion
  35. #region *** Constructors ***
  36. public QMonReceiver(IFileSystem fileSystem, QMonReceiverConfiguration configuration, ILogger logger, GeneralDataReceivedDelegate generalDataReceivedCallback = null, DataReceivedDelegate liveDataReceivedCallback = null) : base(configuration, logger)
  37. {
  38. _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
  39. _lastSavedTimestamp = DateTime.Now;
  40. _generalReceivedCallback = generalDataReceivedCallback;
  41. _dataReceivedCallback = liveDataReceivedCallback;
  42. _jsonSerializerOptions = new JsonSerializerOptions
  43. {
  44. IgnoreNullValues = true,
  45. WriteIndented = false,
  46. PropertyNameCaseInsensitive = true,
  47. PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
  48. MaxDepth = 10
  49. };
  50. _jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
  51. Configuration.DataCache.Path = Configuration.DataCache.Path.Replace("%AppData%",
  52. Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData));
  53. if (!_fileSystem.Directory.Exists(Configuration.DataCache.Path))
  54. {
  55. _fileSystem.Directory.CreateDirectory(Configuration.DataCache.Path);
  56. Log(LogSeverityEnum.Debug, $"Data receiver created data cache directory '{Configuration.DataCache.Path}'");
  57. }
  58. }
  59. #endregion
  60. #region *** Public Operations ***
  61. public IReceiverHandler CreateHandler(string instanceIdentifier, string name)
  62. {
  63. return null;
  64. }
  65. #endregion
  66. #region *** Private overrides ***
  67. protected override void OnOpen()
  68. {
  69. _isDataClosing = false;
  70. var _dataReceiverCancel = new CancellationTokenSource();
  71. new System.Threading.Thread(() =>
  72. {
  73. try
  74. {
  75. Log(LogSeverityEnum.Debug, "Data receiver thread started.");
  76. while (!_dataReceiverCancel.Token.IsCancellationRequested)
  77. OnThreadData(_dataReceiverCancel.Token);
  78. }
  79. catch (OperationCanceledException)
  80. {
  81. Log(LogSeverityEnum.Info, $"Data receiver thread on {UriData} unexpected stopped");
  82. }
  83. Log(LogSeverityEnum.Debug, "Data receiver thread stoped.");
  84. }).Start();
  85. Log(LogSeverityEnum.Info, $"Data receiver thread on {UriData} started");
  86. }
  87. protected override void OnClose()
  88. {
  89. _isDataClosing = true;
  90. _dataReceiverCancel?.Cancel();
  91. Log(LogSeverityEnum.Info, $"Data receiver thread on {UriData} stopped");
  92. }
  93. protected override void OnTimerData(object? state)
  94. {
  95. _lastSavedTimestamp = DateTime.Now;
  96. var itemsToSave = _dataCache.Where(x => x.Timestamp < _lastSavedTimestamp).ToArray();
  97. var instances = itemsToSave.Select(x => x.InstanceIdentifier).Distinct();
  98. foreach (var instance in instances)
  99. {
  100. var dirName = _fileSystem.Path.Combine(Configuration.DataCache.Path, instance);
  101. if (!_fileSystem.Directory.Exists(dirName))
  102. {
  103. _fileSystem.Directory.CreateDirectory(dirName);
  104. Log(LogSeverityEnum.Debug, $"Data receiver created data cache directory '{dirName}'");
  105. }
  106. var batchData = itemsToSave.Where(x => x.InstanceIdentifier == instance).OrderBy(x => x.Timestamp).ToArray();
  107. _dataReceivedCallback?.Invoke(batchData);
  108. var dfrom = batchData.Min(x => x.Timestamp).ToFileTimeUtc();
  109. var dto = batchData.Max(x => x.Timestamp).ToFileTimeUtc();
  110. var sbFileName = new StringBuilder();
  111. sbFileName.Append("mondata_").Append(dfrom).Append("-").Append(dto).Append(".json");
  112. var fileName = _fileSystem.Path.Combine(dirName, sbFileName.ToString());
  113. using (var streamWriter = File.CreateText(fileName))
  114. {
  115. JsonSerializer.Serialize(streamWriter.BaseStream, batchData, _jsonSerializerOptions);
  116. streamWriter.Flush();
  117. }
  118. Log(LogSeverityEnum.Trace, $"Data receiver created dump data file '{fileName}'");
  119. }
  120. foreach (var item in itemsToSave)
  121. _dataCache.Remove(item);
  122. }
  123. protected override void OnTimerGeneral(object? state)
  124. {
  125. if (state == null) throw new ArgumentNullException(nameof(state));
  126. var owner = (QMonReceiver)state;
  127. if (owner._isReceivingGeneral) return;
  128. owner._isReceivingGeneral = true;
  129. try
  130. {
  131. var remoteEndPoint =
  132. new IPEndPoint(IPAddress.Parse(owner.UriGeneral.DnsSafeHost), owner.UriGeneral.Port);
  133. var data = UdpClientGeneral.Receive(ref remoteEndPoint);
  134. var general = (MonGeneral)Binder.LoadFromString(Encoding.UTF8.GetString(data), typeof(MonGeneral));
  135. Log(LogSeverityEnum.Debug,
  136. $"[ReceiverGeneral] receive general data from '{remoteEndPoint}' [{general.InstanceIdentifier}] size={data.Length} bytes.");
  137. _generalCache.TryAdd(general.InstanceIdentifier,
  138. new MonReceiverGeneralCahedItem(DateTime.Now, remoteEndPoint, general));
  139. _generalReceivedCallback?.Invoke(general);
  140. }
  141. catch (SocketException e)
  142. {
  143. if (e.ErrorCode!= 10004) Log(LogSeverityEnum.Error, $"[ReceiverGeneral] Error receiving general data", e);
  144. }
  145. catch (Exception e)
  146. {
  147. Log(LogSeverityEnum.Error, $"[ReceiverGeneral] Error receiving general data", e);
  148. }
  149. finally
  150. {
  151. owner._isReceivingGeneral = false;
  152. }
  153. }
  154. private void OnThreadData(CancellationToken cancellation)
  155. {
  156. if (cancellation == null) throw new ArgumentNullException(nameof(cancellation));
  157. if (cancellation.IsCancellationRequested)
  158. {
  159. Log(LogSeverityEnum.Debug, $"[ReceiverData] Cancellation token detected on listener '{UriData}'.");
  160. return;
  161. }
  162. try
  163. {
  164. System.Threading.Thread.CurrentThread.Join(100);
  165. if (_isDataClosing) return;
  166. if (UdpClientData.Available == 0) return;
  167. var remoteEndPoint = new IPEndPoint(IPAddress.Parse(UriData.DnsSafeHost), UriData.Port);
  168. Log(LogSeverityEnum.Debug, $"[ReceiverData] Waiting for data on '{UriData}'...");
  169. var data = UdpClientData.Receive(ref remoteEndPoint);
  170. var monData = JsonSerializer.Deserialize<MonData>(data, _jsonSerializerOptions);
  171. if (monData == null)
  172. {
  173. Log(LogSeverityEnum.Warn, $"[ReceiverData] cannot deserialize monitor data from '{remoteEndPoint}' size={data.Length} bytes. Skipped.");
  174. return;
  175. }
  176. _dataCache.Add(monData);
  177. Log(LogSeverityEnum.Debug, $"[ReceiverData] receive monitor data from '{remoteEndPoint}' size={data.Length} bytes.");
  178. }
  179. catch (Exception e)
  180. {
  181. Log(LogSeverityEnum.Error, $"[ReceiverData] Error receiving monitor data", e);
  182. }
  183. }
  184. protected override UdpClient CreateUdpClient(Uri targetUri)
  185. {
  186. var udpClient = new UdpClient(targetUri.Port);
  187. return udpClient;
  188. }
  189. protected override void OnDisposing()
  190. {
  191. _dataReceiverCancel?.Cancel();
  192. _dataReceiverCancel?.Dispose();
  193. _dataReceiverCancel = null;
  194. base.OnDisposing();
  195. }
  196. #endregion
  197. }
  198. }