QMonReceiver.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. using System.Collections.Concurrent;
  2. using System.Collections.Immutable;
  3. using System.Diagnostics;
  4. using System.IO.Abstractions;
  5. using System.Net;
  6. using System.Net.Sockets;
  7. using System.Text;
  8. using System.Text.Json;
  9. using System.Text.Json.Serialization;
  10. using Quadarax.Foundation.Core.Collections;
  11. using Quadarax.Foundation.Core.Json;
  12. using Quadarax.Foundation.Core.Logging;
  13. using Quadarax.Foundation.Core.QMonitor.Interfaces;
  14. using Quadarax.Foundation.Core.Thread;
  15. namespace Quadarax.Foundation.Core.QMonitor
  16. {
  17. public delegate void GeneralDataReceivedDelegate(MonGeneral general);
  18. public delegate void DataReceivedDelegate(MonData[] data);
  19. public class QMonReceiver : QMonHostBase<QMonReceiverConfiguration>
  20. {
  21. #region *** Properties ***
  22. protected override Uri UriGeneral => Configuration.SourceUriGeneral;
  23. protected override Uri UriData => Configuration.SourceUriData;
  24. protected override TimeSpan IntervalGeneral => Configuration.GeneralReceiverInterval;
  25. protected override TimeSpan? IntervalData => Configuration.DataCache.FlushCacheInterval;
  26. public MonGeneral[] AvailableGeneral => _generalCache.Values.Select(x => x.Data).ToArray();
  27. #endregion
  28. #region *** Fields ***
  29. private readonly GeneralDataReceivedDelegate? _generalReceivedCallback;
  30. private readonly DataReceivedDelegate? _dataReceivedCallback;
  31. private readonly IDictionary<string, MonReceiverGeneralCahedItem> _generalCache = new ConcurrentDictionary<string, MonReceiverGeneralCahedItem>();
  32. private readonly IList<MonData> _dataCache = new LockedList<MonData>();
  33. private readonly JsonSerializerOptions _jsonSerializerOptions;
  34. private long _lastSavedTimestamp;
  35. private readonly IFileSystem _fileSystem;
  36. private ContiguousWorker? _dataReceiverWorker;
  37. private ILogger? _logger;
  38. #endregion
  39. #region *** Constructors ***
  40. public QMonReceiver(IFileSystem fileSystem, QMonReceiverConfiguration configuration,bool purgeOnInit, ILogger logger, GeneralDataReceivedDelegate? generalDataReceivedCallback = null, DataReceivedDelegate? liveDataReceivedCallback = null) : base(configuration, logger)
  41. {
  42. _logger = logger;
  43. _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
  44. _lastSavedTimestamp = DateTime.Now.ToFileTimeUtc();
  45. _generalReceivedCallback = generalDataReceivedCallback;
  46. _dataReceivedCallback = liveDataReceivedCallback;
  47. _jsonSerializerOptions = new JsonSerializerOptions
  48. {
  49. IgnoreNullValues = true,
  50. WriteIndented = false,
  51. PropertyNameCaseInsensitive = true,
  52. PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
  53. MaxDepth = 10
  54. };
  55. _jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
  56. Configuration.DataCache.Path = Configuration.DataCache.Path.Replace("%AppData%",
  57. Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData));
  58. if (!_fileSystem.Directory.Exists(Configuration.DataCache.Path))
  59. {
  60. _fileSystem.Directory.CreateDirectory(Configuration.DataCache.Path);
  61. Log(LogSeverityEnum.Debug, $"Data receiver created data cache directory '{Configuration.DataCache.Path}'");
  62. }
  63. if (purgeOnInit)
  64. {
  65. var files = _fileSystem.Directory.GetFiles(Configuration.DataCache.Path, "*.json", SearchOption.AllDirectories);
  66. var deletedCnt = 0;
  67. foreach (var file in files)
  68. {
  69. _fileSystem.File.Delete(file);
  70. deletedCnt++;
  71. }
  72. Log(LogSeverityEnum.Info, $"Data receiver purge data cache on init, removes {deletedCnt} files");
  73. }
  74. }
  75. #endregion
  76. #region *** Public Operations ***
  77. public IReceiverHandler CreateHandler(string instanceIdentifier, string name,TimeSpan refreshInterval,string? alternativeGeneralFile = null, bool waitForGeneralData = false, TimeSpan? timeout = null)
  78. {
  79. if (alternativeGeneralFile != null && !string.IsNullOrEmpty(alternativeGeneralFile))
  80. {
  81. if (_fileSystem.File.Exists(alternativeGeneralFile))
  82. {
  83. Log(LogSeverityEnum.Info, $"Using general file '{alternativeGeneralFile}' before initialization.");
  84. var binder = new Binder(_fileSystem, true, true, 5);
  85. var list = binder.Load<IEnumerable<MonItemGeneral>>(alternativeGeneralFile)?.ToList();
  86. if (list == null) throw new InvalidOperationException($"Cannot load general file '{alternativeGeneralFile}'");
  87. var ep = new IPEndPoint(IPAddress.Parse(UriGeneral.DnsSafeHost), UriGeneral.Port);
  88. var monGeneral = new MonGeneral() { InstanceIdentifier = instanceIdentifier, Items = list.ToList() };
  89. if (!_generalCache.ContainsKey(instanceIdentifier))
  90. _generalCache.Add(instanceIdentifier, new MonReceiverGeneralCahedItem(DateTime.Now,ep,monGeneral));
  91. else
  92. {
  93. foreach (var genitem in monGeneral.Items)
  94. {
  95. if (_generalCache[instanceIdentifier].Data.Items.All(x =>
  96. string.Equals(x.Name, genitem.Name,
  97. StringComparison.InvariantCultureIgnoreCase)))
  98. {
  99. _generalCache[instanceIdentifier].Data.Items.Add(genitem);
  100. }
  101. }
  102. }
  103. }
  104. }
  105. if (waitForGeneralData)
  106. WaitForGeneralData(instanceIdentifier, name, timeout.GetValueOrDefault(Configuration.GeneralReceiverInterval));
  107. if (_generalCache.TryGetValue(instanceIdentifier, out var item))
  108. {
  109. var generalItem = item.Data.Items.FirstOrDefault(x =>
  110. string.Equals(x.Name, name, StringComparison.InvariantCultureIgnoreCase));
  111. if (generalItem == null) throw new InvalidOperationException($"Cannot find descriptor '{name}' in general data cache for instance identifier '{instanceIdentifier}'. Specification is not available yet.");
  112. return new QMonReceiverHandler(_fileSystem, Configuration.DataCache.Path,instanceIdentifier, generalItem, refreshInterval, _logger);
  113. }
  114. throw new InvalidOperationException($"Instance identifier '{instanceIdentifier}' has not been available in general data cache yet.");
  115. }
  116. public bool WaitForGeneralData(string instanceIdentifier, string name, TimeSpan timeout)
  117. {
  118. Log(LogSeverityEnum.Debug, $"Waiting for general descriptor for instance identifier '{instanceIdentifier}' and channel '{name}' for {timeout.Seconds} sec...");
  119. var sw = Stopwatch.StartNew();
  120. while (sw.Elapsed < timeout)
  121. {
  122. if (_generalCache.TryGetValue(instanceIdentifier, out var item))
  123. {
  124. var generalItem = item.Data.Items.FirstOrDefault(x =>
  125. string.Equals(x.Name, name, StringComparison.InvariantCultureIgnoreCase));
  126. if (generalItem != null)
  127. {
  128. Log(LogSeverityEnum.Debug, "Descriptor received.");
  129. return true;
  130. }
  131. }
  132. System.Threading.Thread.CurrentThread.Join(100);
  133. }
  134. return false;
  135. }
  136. #endregion
  137. #region *** Private overrides ***
  138. protected override void OnOpen()
  139. {
  140. if (WorkerData == null) throw new InvalidOperationException("WorkerData is null");
  141. var cancel = new CancellationTokenSource();
  142. var dataReceiverContext = new QMonWorkerContext(((QMonWorkerContext)WorkerData.Context).Client);
  143. dataReceiverContext.Cancel = cancel.Token;
  144. _dataReceiverWorker = new ContiguousWorker(async ()=>await OnReceiveData(dataReceiverContext));
  145. _dataReceiverWorker.Start();
  146. Log(LogSeverityEnum.Info, $"Data receiver thread on {UriData} started");
  147. }
  148. protected override void OnClose()
  149. {
  150. _dataReceiverWorker?.Stop();
  151. _dataReceiverWorker?.Dispose();
  152. _dataReceiverWorker = null;
  153. Log(LogSeverityEnum.Info, $"Data receiver thread on {UriData} stopped");
  154. }
  155. protected override async Task OnTimerData(QMonWorkerContext context)
  156. {
  157. _lastSavedTimestamp = DateTime.Now.ToFileTimeUtc();
  158. IEnumerable<MonData> itemsToSave = _dataCache.Where(x => x.TimestampFtUtc < _lastSavedTimestamp).ToImmutableArray();
  159. IEnumerable<string> instances = itemsToSave.Select(x => x.InstanceIdentifier).Distinct();
  160. foreach (var instance in instances)
  161. {
  162. var dirName = _fileSystem.Path.Combine(Configuration.DataCache.Path, instance);
  163. if (!_fileSystem.Directory.Exists(dirName))
  164. {
  165. _fileSystem.Directory.CreateDirectory(dirName);
  166. Log(LogSeverityEnum.Debug, $"Data receiver created data cache directory '{dirName}'");
  167. }
  168. var batchData = itemsToSave.Where(x => x.InstanceIdentifier == instance).OrderBy(x => x.TimestampFtUtc).ToArray();
  169. _dataReceivedCallback?.Invoke(batchData);
  170. foreach (var data in batchData)
  171. {
  172. var sbFileName = new StringBuilder();
  173. sbFileName.Append(data.Name).Append("_").Append(data.TimestampFtUtc).Append(".json");
  174. var fileName = _fileSystem.Path.Combine(dirName, sbFileName.ToString());
  175. await using (var streamWriter = File.CreateText(fileName))
  176. {
  177. await JsonSerializer.SerializeAsync(streamWriter.BaseStream, data, _jsonSerializerOptions,context.Cancel);
  178. await streamWriter.FlushAsync();
  179. }
  180. Log(LogSeverityEnum.Trace, $"Data receiver created dump data file '{fileName}'");
  181. }
  182. }
  183. foreach (var item in itemsToSave)
  184. _dataCache.Remove(item);
  185. }
  186. protected override async Task OnTimerGeneral(QMonWorkerContext context)
  187. {
  188. try
  189. {
  190. //var remoteEndPoint =
  191. // new IPEndPoint(IPAddress.Parse(UriGeneral.DnsSafeHost),UriGeneral.Port);
  192. var data = await context.Client.ReceiveAsync(context.Cancel);
  193. var general = (MonGeneral)Binder.LoadFromString(Encoding.UTF8.GetString(data.Buffer), typeof(MonGeneral));
  194. Log(LogSeverityEnum.Trace,
  195. $"[ReceiverGeneral] receive general data from '{data.RemoteEndPoint}' [{general.InstanceIdentifier}] size={data.Buffer.Length} bytes.");
  196. if (_generalCache.TryGetValue(general.InstanceIdentifier, out var value))
  197. {
  198. foreach (var item in general.Items)
  199. {
  200. if (!value.Data.Items.Any(x=>string.Equals(x.Name, item.Name, StringComparison.InvariantCultureIgnoreCase)))
  201. {
  202. value.Data.Items.Add(item);
  203. value.Received = DateTime.Now;
  204. }
  205. }
  206. }
  207. else
  208. {
  209. _generalCache.TryAdd(general.InstanceIdentifier,
  210. new MonReceiverGeneralCahedItem(DateTime.Now, data.RemoteEndPoint, general));
  211. }
  212. _generalReceivedCallback?.Invoke(general);
  213. }
  214. catch (SocketException e)
  215. {
  216. if (e.ErrorCode!= 10004) Log(LogSeverityEnum.Error, $"[ReceiverGeneral] Error receiving general data", e);
  217. }
  218. catch (Exception e)
  219. {
  220. Log(LogSeverityEnum.Error, $"[ReceiverGeneral] Error receiving general data", e);
  221. }
  222. }
  223. private async Task OnReceiveData(QMonWorkerContext context)
  224. {
  225. while (!context.Cancel.IsCancellationRequested)
  226. {
  227. try
  228. {
  229. System.Threading.Thread.CurrentThread.Join(100);
  230. Log(LogSeverityEnum.Trace, $"[ReceiverData] Waiting for data on '{UriData}'...");
  231. var data = await context.Client.ReceiveAsync(context.Cancel);
  232. var monData = JsonSerializer.Deserialize<MonData>(data.Buffer, _jsonSerializerOptions);
  233. if (monData == null)
  234. {
  235. Log(LogSeverityEnum.Warn, $"[ReceiverData] cannot deserialize monitor data from '{data.RemoteEndPoint}' size={data.Buffer.Length} bytes. Skipped.");
  236. continue;
  237. }
  238. _dataCache.Add(monData);
  239. Log(LogSeverityEnum.Trace, $"[ReceiverData] receive monitor data from '{data.RemoteEndPoint}' size={data.Buffer.Length} bytes.");
  240. }
  241. catch (Exception e)
  242. {
  243. Log(LogSeverityEnum.Error, $"[ReceiverData] Error receiving monitor data", e);
  244. }
  245. }
  246. }
  247. protected override UdpClient CreateUdpClient(Uri targetUri)
  248. {
  249. var udpClient = new UdpClient(targetUri.Port);
  250. return udpClient;
  251. }
  252. protected override void OnDisposing()
  253. {
  254. if (_dataReceiverWorker is { IsRunning: true }) _dataReceiverWorker?.Stop();;
  255. _dataReceiverWorker?.Dispose();
  256. _dataReceiverWorker = null;
  257. base.OnDisposing();
  258. }
  259. #endregion
  260. }
  261. }