QMonReceiver.cs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. using System.Collections.Concurrent;
  2. using System.IO.Abstractions;
  3. using System.Net.Sockets;
  4. using System.Text;
  5. using System.Text.Json;
  6. using System.Text.Json.Serialization;
  7. using Quadarax.Foundation.Core.Logging;
  8. using Quadarax.Foundation.Core.QMonitor.Interfaces;
  9. using Quadarax.Foundation.Core.Thread;
  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 readonly GeneralDataReceivedDelegate? _generalReceivedCallback;
  25. private readonly DataReceivedDelegate? _dataReceivedCallback;
  26. private readonly IDictionary<string, MonReceiverGeneralCahedItem> _generalCache = new ConcurrentDictionary<string, MonReceiverGeneralCahedItem>();
  27. private readonly IList<MonData> _dataCache = new List<MonData>();
  28. private readonly JsonSerializerOptions _jsonSerializerOptions;
  29. private DateTime _lastSavedTimestamp;
  30. private readonly IFileSystem _fileSystem;
  31. private ContiguousWorker? _dataReceiverWorker;
  32. #endregion
  33. #region *** Constructors ***
  34. public QMonReceiver(IFileSystem fileSystem, QMonReceiverConfiguration configuration, ILogger logger, GeneralDataReceivedDelegate? generalDataReceivedCallback = null, DataReceivedDelegate? liveDataReceivedCallback = null) : base(configuration, logger)
  35. {
  36. _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
  37. _lastSavedTimestamp = DateTime.Now;
  38. _generalReceivedCallback = generalDataReceivedCallback;
  39. _dataReceivedCallback = liveDataReceivedCallback;
  40. _jsonSerializerOptions = new JsonSerializerOptions
  41. {
  42. IgnoreNullValues = true,
  43. WriteIndented = false,
  44. PropertyNameCaseInsensitive = true,
  45. PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
  46. MaxDepth = 10
  47. };
  48. _jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
  49. Configuration.DataCache.Path = Configuration.DataCache.Path.Replace("%AppData%",
  50. Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData));
  51. if (!_fileSystem.Directory.Exists(Configuration.DataCache.Path))
  52. {
  53. _fileSystem.Directory.CreateDirectory(Configuration.DataCache.Path);
  54. Log(LogSeverityEnum.Debug, $"Data receiver created data cache directory '{Configuration.DataCache.Path}'");
  55. }
  56. }
  57. #endregion
  58. #region *** Public Operations ***
  59. public IReceiverHandler CreateHandler(string instanceIdentifier, string name)
  60. {
  61. return null;
  62. }
  63. #endregion
  64. #region *** Private overrides ***
  65. protected override void OnOpen()
  66. {
  67. if (WorkerData == null) throw new InvalidOperationException("WorkerData is null");
  68. var cancel = new CancellationTokenSource();
  69. var dataReceiverContext = new QMonWorkerContext(((QMonWorkerContext)WorkerData.Context).Client);
  70. dataReceiverContext.Cancel = cancel.Token;
  71. _dataReceiverWorker = new ContiguousWorker(async ()=>await OnReceiveData(dataReceiverContext));
  72. _dataReceiverWorker.Start();
  73. Log(LogSeverityEnum.Info, $"Data receiver thread on {UriData} started");
  74. }
  75. protected override void OnClose()
  76. {
  77. _dataReceiverWorker?.Stop();
  78. _dataReceiverWorker?.Dispose();
  79. _dataReceiverWorker = null;
  80. Log(LogSeverityEnum.Info, $"Data receiver thread on {UriData} stopped");
  81. }
  82. protected override async Task OnTimerData(QMonWorkerContext context)
  83. {
  84. _lastSavedTimestamp = DateTime.Now;
  85. var itemsToSave = _dataCache.Where(x => x.Timestamp < _lastSavedTimestamp).ToArray();
  86. var instances = itemsToSave.Select(x => x.InstanceIdentifier).Distinct();
  87. foreach (var instance in instances)
  88. {
  89. var dirName = _fileSystem.Path.Combine(Configuration.DataCache.Path, instance);
  90. if (!_fileSystem.Directory.Exists(dirName))
  91. {
  92. _fileSystem.Directory.CreateDirectory(dirName);
  93. Log(LogSeverityEnum.Debug, $"Data receiver created data cache directory '{dirName}'");
  94. }
  95. var batchData = itemsToSave.Where(x => x.InstanceIdentifier == instance).OrderBy(x => x.Timestamp).ToArray();
  96. _dataReceivedCallback?.Invoke(batchData);
  97. var dtFrom = batchData.Min(x => x.Timestamp).ToFileTimeUtc();
  98. var dtTo = batchData.Max(x => x.Timestamp).ToFileTimeUtc();
  99. var sbFileName = new StringBuilder();
  100. sbFileName.Append("mondata_").Append(dtFrom).Append("-").Append(dtTo).Append(".json");
  101. var fileName = _fileSystem.Path.Combine(dirName, sbFileName.ToString());
  102. await using (var streamWriter = File.CreateText(fileName))
  103. {
  104. await JsonSerializer.SerializeAsync(streamWriter.BaseStream, batchData, _jsonSerializerOptions,context.Cancel);
  105. await streamWriter.FlushAsync();
  106. }
  107. Log(LogSeverityEnum.Trace, $"Data receiver created dump data file '{fileName}'");
  108. }
  109. foreach (var item in itemsToSave)
  110. _dataCache.Remove(item);
  111. }
  112. protected override async Task OnTimerGeneral(QMonWorkerContext context)
  113. {
  114. try
  115. {
  116. //var remoteEndPoint =
  117. // new IPEndPoint(IPAddress.Parse(UriGeneral.DnsSafeHost),UriGeneral.Port);
  118. var data = await context.Client.ReceiveAsync(context.Cancel);
  119. var general = (MonGeneral)Binder.LoadFromString(Encoding.UTF8.GetString(data.Buffer), typeof(MonGeneral));
  120. Log(LogSeverityEnum.Debug,
  121. $"[ReceiverGeneral] receive general data from '{data.RemoteEndPoint}' [{general.InstanceIdentifier}] size={data.Buffer.Length} bytes.");
  122. _generalCache.TryAdd(general.InstanceIdentifier,
  123. new MonReceiverGeneralCahedItem(DateTime.Now, data.RemoteEndPoint, general));
  124. _generalReceivedCallback?.Invoke(general);
  125. }
  126. catch (SocketException e)
  127. {
  128. if (e.ErrorCode!= 10004) Log(LogSeverityEnum.Error, $"[ReceiverGeneral] Error receiving general data", e);
  129. }
  130. catch (Exception e)
  131. {
  132. Log(LogSeverityEnum.Error, $"[ReceiverGeneral] Error receiving general data", e);
  133. }
  134. }
  135. private async Task OnReceiveData(QMonWorkerContext context)
  136. {
  137. while (!context.Cancel.IsCancellationRequested)
  138. {
  139. try
  140. {
  141. System.Threading.Thread.CurrentThread.Join(100);
  142. Log(LogSeverityEnum.Debug, $"[ReceiverData] Waiting for data on '{UriData}'...");
  143. var data = await context.Client.ReceiveAsync(context.Cancel);
  144. var monData = JsonSerializer.Deserialize<MonData>(data.Buffer, _jsonSerializerOptions);
  145. if (monData == null)
  146. {
  147. Log(LogSeverityEnum.Warn, $"[ReceiverData] cannot deserialize monitor data from '{data.RemoteEndPoint}' size={data.Buffer.Length} bytes. Skipped.");
  148. continue;
  149. }
  150. _dataCache.Add(monData);
  151. Log(LogSeverityEnum.Debug, $"[ReceiverData] receive monitor data from '{data.RemoteEndPoint}' size={data.Buffer.Length} bytes.");
  152. }
  153. catch (Exception e)
  154. {
  155. Log(LogSeverityEnum.Error, $"[ReceiverData] Error receiving monitor data", e);
  156. }
  157. }
  158. }
  159. protected override UdpClient CreateUdpClient(Uri targetUri)
  160. {
  161. var udpClient = new UdpClient(targetUri.Port);
  162. return udpClient;
  163. }
  164. protected override void OnDisposing()
  165. {
  166. if (_dataReceiverWorker is { IsRunning: true }) _dataReceiverWorker?.Stop();;
  167. _dataReceiverWorker?.Dispose();
  168. _dataReceiverWorker = null;
  169. base.OnDisposing();
  170. }
  171. #endregion
  172. }
  173. }