using System.Net; using System.Net.Sockets; using System.Reflection; using System.Text; using Quadarax.Foundation.Core.Collections; using Quadarax.Foundation.Core.Logging; using Quadarax.Foundation.Core.QMonitor.Attributes; using Quadarax.Foundation.Core.Reflection.Extensions; namespace Quadarax.Foundation.Core.QMonitor { public class QMonClient : QMonHostBase { #region *** Properties *** protected override Uri UriGeneral => Configuration.TargetUriGeneral; protected override Uri UriData => Configuration.TargetUriData; protected override TimeSpan IntervalGeneral => Configuration.EmitGeneralInterval; protected override TimeSpan? IntervalData => Configuration.EmitDataInterval; public string InstanceIdentifier { get; } #endregion #region *** Fields *** private readonly IDictionary _generalCache = new Dictionary(); private readonly IList _dataCache = new LockedList(); #endregion #region *** Constructors *** public QMonClient(string instanceIdentifier, QMonClientConfiuration configuration, ILogger? externalLogger) : base(configuration, externalLogger) { InstanceIdentifier = instanceIdentifier ?? throw new ArgumentNullException(nameof(instanceIdentifier)); ScanAssemblies(); } private void ScanAssemblies() { var assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (var assembly in assemblies) { var types = assembly.GetTypesWithAttribute(true); foreach (var type in types) { var monItemGeneral = new MonItemGeneral { Caption = type.GetCustomAttribute()?.Caption ?? type.Name, Description = type.GetCustomAttribute()?.Description ?? string.Empty, ViewType = MonItemGeneral.MonViewType.Item, Name = type.FullName, }; var properties = type.GetPropertiesWithAttribute(true); var keys = new List>(); foreach (var property in properties) { var itemDescriptor = new MonItemGeneral.ItemDescriptor { Name = property.Name, TypeName = property.PropertyType.Name, Label = property.GetCustomAttribute()?.Label ?? property.Name, Description = property.GetCustomAttribute()?.Description ?? string.Empty, Order = property.GetCustomAttribute()?.Order ?? 0, _propertyInfo = property }; monItemGeneral.Items.Add(itemDescriptor); if (property.GetCustomAttribute() != null) { keys.Add(new Tuple(property.Name, property.GetCustomAttribute()!.KeyOrder)); } } foreach (var key in keys.OrderBy(x => x.Item2)) { var ord = monItemGeneral.Items.FindIndex(x => x.Name == key.Item1); if (ord >= 0) monItemGeneral.KeyOrdinals.Add(ord); } _generalCache.Add(type.FullName!, monItemGeneral); } } } #endregion #region *** Public Operations *** public void Notify(object? monitoredItem) { if (monitoredItem == null) return; var fullName = monitoredItem.GetType().FullName; if (fullName != null && !_generalCache.ContainsKey(fullName)) throw new NotSupportedException($"Type {monitoredItem.GetType().FullName} is not flagged by MonitoredClassAttribute as monitored"); var data = new MonData() { InstanceIdentifier = InstanceIdentifier, Name = fullName, TimestampFtUtc = DateTime.Now.ToFileTimeUtc(), Data = Binder.SaveToString(monitoredItem) }; _dataCache.Add(data); } #endregion #region *** Protected Operations *** protected override void OnDisposing() { base.OnDisposing(); _generalCache.Clear(); } protected override async Task OnTimerData(QMonWorkerContext context) { if (_dataCache.Count == 0) return; try { var start = DateTime.Now; var itmCnt = 0; var declaredSize = 0; var sentSize = 0; var dataToSent = _dataCache.ToArray(); foreach (var data in dataToSent) { var dataOut = Serialize(data); declaredSize += dataOut.Length; sentSize += await context.Client.SendAsync(dataOut, context.Cancel); _dataCache.Remove(data); itmCnt++; } Log(LogSeverityEnum.Trace, $"[{InstanceIdentifier}] Sent data blocks = {itmCnt} of declared {declaredSize} bytes/sent {sentSize} bytes @ {DateTime.Now.Subtract(start).TotalMilliseconds} ms."); } catch (Exception ex) { Log(LogSeverityEnum.Error, $"[{InstanceIdentifier}] Error sending general data", ex); } } protected override async Task OnTimerGeneral(QMonWorkerContext context) { try { var data = new MonGeneral() { InstanceIdentifier = InstanceIdentifier, Items = new List() }; data.Items.AddRange(_generalCache.Values); var dataOut = Serialize(data); await context.Client.SendAsync(dataOut, context.Cancel); Log(LogSeverityEnum.Trace, $"[{InstanceIdentifier}] Sent General packet size = {dataOut.Length} bytes"); } catch (Exception ex) { Log(LogSeverityEnum.Error, $"[{InstanceIdentifier}] Error sending general data", ex); } } private byte[] Serialize(object data) { if (data == null) throw new ArgumentNullException(nameof(data)); var json = Binder.SaveToString(data); return Encoding.UTF8.GetBytes(json); } protected override UdpClient CreateUdpClient(Uri targetUri) { var ep = new IPEndPoint(IPAddress.Parse(targetUri.DnsSafeHost), targetUri.Port); var udpClient = new UdpClient(); udpClient.Connect(ep); return udpClient; } protected override void OnOpen() { Log(LogSeverityEnum.Info, $"Started."); } protected override void OnClose() { Log(LogSeverityEnum.Info, $"Closed."); } #endregion #region *** Nested Types *** #endregion } }