Dalibor Votruba пре 2 година
родитељ
комит
bb065f9a4e

+ 19 - 2
Workbench/QMonitor/qmonlib.console/Commands/MonitorCommand.cs

@@ -37,6 +37,10 @@ namespace Quadarax.Foundation.Core.QMonitor.Console.Commands
         private const string CS_ARG_HINT_GENFILE = "<general_file>";
         private const string CS_ARG_DESC_GENFILE = "Use general metadata from json file during initialization (no needs to wait to receive).";
 
+        protected const string CS_ARG_NAME_REWIND = "rewind";
+        private const string CS_ARG_HINT_REWIND = "<is_rewind>";
+        private const string CS_ARG_DESC_REWIND = "Flag defines if rewind to first available cached data (may live when no cached data), otherwise wait for new data.";
+
         
         #endregion
 
@@ -48,6 +52,7 @@ namespace Quadarax.Foundation.Core.QMonitor.Console.Commands
         private string ChannelName { get; set; }
         private TimeSpan RefreshInterval { get; set; }
         private string GeneralFile { get; set; }
+        private bool IsRewind { get; set; }
         #endregion
 
         #region *** Private fields ***
@@ -72,7 +77,16 @@ namespace Quadarax.Foundation.Core.QMonitor.Console.Commands
             {
                 hnd.ReceiverHandlerStateChanged += OnReceiverHandlerStateChanged;
                 hnd.ReceiverHandlerDataChanged += OnReceiverHandlerDataChanged;
-                hnd.Play(false);
+                if (IsRewind)
+                {
+                    WriteInfo("Rewind to first available cached data.");
+                    hnd.Rewind(isBackward:true);
+                    hnd.Play(false);
+                }
+                else
+                {
+                    hnd.Play(true);
+                }
                 WriteCaption("To close receiver press any key ...");
                 System.Console.ReadKey();
             }
@@ -89,6 +103,7 @@ namespace Quadarax.Foundation.Core.QMonitor.Console.Commands
             ChannelName = GetArgumentValueOrDefault<string>(CS_ARG_NAME_CHANNEL);
             RefreshInterval = GetArgumentValueOrDefault<TimeSpan>(CS_ARG_NAME_REFRESH);
             GeneralFile = GetArgumentValueOrDefault<string>(CS_ARG_NAME_GENFILE);
+            IsRewind = GetArgumentValueOrDefault<bool>(CS_ARG_NAME_REWIND);
         }
 
 
@@ -99,6 +114,7 @@ namespace Quadarax.Foundation.Core.QMonitor.Console.Commands
             list.Add(new NamedArgument(CS_ARG_NAME_CHANNEL, CS_ARG_HINT_CHANNEL, CS_ARG_DESC_CHANNEL, TypeValuesEnum.String,string.Empty,true));
             list.Add(new NamedArgument(CS_ARG_NAME_REFRESH, CS_ARG_HINT_REFRESH, CS_ARG_DESC_REFRESH, TypeValuesEnum.TimeSpan,TimeSpan.FromSeconds(5).ToString(),false));
             list.Add(new NamedArgument(CS_ARG_NAME_GENFILE, CS_ARG_HINT_GENFILE, CS_ARG_DESC_GENFILE, TypeValuesEnum.String, string.Empty, false));
+            list.Add(new FlagArgument(CS_ARG_NAME_REWIND, CS_ARG_HINT_REWIND, CS_ARG_DESC_REWIND, false));
             return list;
         }
 
@@ -116,7 +132,8 @@ namespace Quadarax.Foundation.Core.QMonitor.Console.Commands
         {
             var firstLine = $"*** Dump [{_dumpCnt}] {descriptor.ViewType} * {descriptor.Caption} ***";
             WriteInfo(firstLine);
-            WriteCaption($"Timestamp: {data.Timestamp} Name: {data.Name}");
+            WriteCaption($"Timestamp: {DateTime.FromFileTimeUtc(data.TimestampFtUtc)} ({data.TimestampFtUtc})");
+            WriteCaption($"Name: {data.Name}");
             WriteInfo(new('-', firstLine.Length));
             var diff = _lastData?.Diff(data);
             if (descriptor.ViewType == MonItemGeneral.MonViewType.Item)

+ 5 - 1
Workbench/QMonitor/qmonlib.console/Properties/launchSettings.json

@@ -15,9 +15,13 @@
       "commandName": "Project",
       "commandLineArgs": "monitor -iid:test -channel:Quadarax.Foundation.Core.QMonitor.Emit.TestStructures.ServiceStatus -purge -dbg"
     },
-    "Monitor w/o purge": {
+    "Monitor w/o purge LIVE": {
       "commandName": "Project",
       "commandLineArgs": "monitor -iid:test -channel:Quadarax.Foundation.Core.QMonitor.Emit.TestStructures.ServiceStatus -dbg -genfile:general.json"
+    },
+    "Monitor w/o purge REWIND": {
+      "commandName": "Project",
+      "commandLineArgs": "monitor -iid:test -channel:Quadarax.Foundation.Core.QMonitor.Emit.TestStructures.ServiceStatus -dbg -rewind -genfile:general.json"
     }
   }
 }

+ 19 - 7
Workbench/QMonitor/qmonlib.emit/Program.cs

@@ -12,7 +12,10 @@ namespace Quadarax.Foundation.Core.QMonitor.Emit // Note: actual namespace depen
         static void Main(string[] args)
         {
             System.Console.WriteLine("qmonlib.emit * qmon random activity generator");
-            System.Console.WriteLine("usage: qmonlib.emit <instance_name>");
+            System.Console.WriteLine("usage: qmonlib.emit <instance_name> <list_of_data>");
+            System.Console.WriteLine("arguments:");
+            System.Console.WriteLine("<instance_name> - mandatory. name of instance that generated data belongs");
+            System.Console.WriteLine("<list_of_data> - optional. list of data categories to be generated (separated with semicolon). Available values are all (default),service,process,user");
             var instanceIdentifier = args.Length > 0 ? args[0] : "test";
 
             using (var qmonClient = new QMonClient(instanceIdentifier, QMonClientConfiuration.CreateDefault(), new ConsoleLogger()))
@@ -23,16 +26,25 @@ namespace Quadarax.Foundation.Core.QMonitor.Emit // Note: actual namespace depen
                     Status = new ServiceStatus(ServiceStatus.StatusEnum.Online, DateTime.Now),
                     AvailableUsers = CreateAvailableUsers()
                 };
-                var tmStatus = new Timer(OnServiceStatusTimer, ctx, TimeSpan.Zero, TimeSpan.FromSeconds(5));
-                var tmProcesses = new Timer(OnProcessesTimer, ctx, TimeSpan.Zero, TimeSpan.FromSeconds(2));
-                var tmUsers = new Timer(OnUsersTimer, ctx, TimeSpan.Zero, TimeSpan.FromSeconds(2));
+                var servicelist = "all";
+                if (args.Length == 2)
+                    servicelist = args[1].ToLower();
+                Timer? tmStatus = null;
+                Timer? tmProcesses = null;
+                Timer? tmUsers = null;
+                if (servicelist.Contains("all") || servicelist.Contains("service"))
+                    tmStatus = new Timer(OnServiceStatusTimer, ctx, TimeSpan.Zero, TimeSpan.FromSeconds(5));
+                if (servicelist.Contains("all") || servicelist.Contains("process"))
+                    tmProcesses = new Timer(OnProcessesTimer, ctx, TimeSpan.Zero, TimeSpan.FromSeconds(2));
+                if (servicelist.Contains("all") || servicelist.Contains("user"))
+                    tmUsers = new Timer(OnUsersTimer, ctx, TimeSpan.Zero, TimeSpan.FromSeconds(2));
 
 
                 System.Console.WriteLine("Press any key to stop...");
                 System.Console.ReadKey();
-                tmStatus.Dispose();
-                tmProcesses.Dispose();
-                tmUsers.Dispose();
+                tmStatus?.Dispose();
+                tmProcesses?.Dispose();
+                tmUsers?.Dispose();
                 System.Console.WriteLine("Stopped.");
             }
         }

+ 2 - 3
Workbench/QMonitor/qmonlib/MonData.cs

@@ -1,6 +1,5 @@
 using System.Text.Json;
 using System.Text.Json.Serialization;
-using Quadarax.Foundation.Core.Data;
 using Quadarax.Foundation.Core.Json.Extensions;
 
 namespace Quadarax.Foundation.Core.QMonitor
@@ -12,8 +11,8 @@ namespace Quadarax.Foundation.Core.QMonitor
 
         [JsonPropertyName("nam")]
         public string? Name { get; set; }
-        [JsonPropertyName("tms")]
-        public DateTime Timestamp { get; set; }
+        [JsonPropertyName("tms-ftutc")]
+        public long TimestampFtUtc { get; set; }
         [JsonPropertyName("dta")]
         public string Data { get; set; } = string.Empty;
 

+ 3 - 3
Workbench/QMonitor/qmonlib/QMonClient.cs

@@ -1,8 +1,8 @@
 using System.Net;
 using System.Net.Sockets;
 using System.Reflection;
-using System.Runtime.Intrinsics.X86;
 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;
@@ -23,7 +23,7 @@ namespace Quadarax.Foundation.Core.QMonitor
 
         #region *** Fields ***
         private readonly IDictionary<string, MonItemGeneral> _generalCache = new Dictionary<string, MonItemGeneral>();
-        private readonly IList<MonData> _dataCache = new List<MonData>();
+        private readonly IList<MonData> _dataCache = new LockedList<MonData>();
         #endregion
 
         #region *** Constructors ***
@@ -94,7 +94,7 @@ namespace Quadarax.Foundation.Core.QMonitor
             {
                 InstanceIdentifier = InstanceIdentifier,
                 Name = fullName,
-                Timestamp = DateTime.Now,
+                TimestampFtUtc = DateTime.Now.ToFileTimeUtc(),
                 Data = Binder.SaveToString(monitoredItem)
             };
             _dataCache.Add(data);

+ 8 - 7
Workbench/QMonitor/qmonlib/QMonReceiver.cs

@@ -7,6 +7,7 @@ using System.Net.Sockets;
 using System.Text;
 using System.Text.Json;
 using System.Text.Json.Serialization;
+using Quadarax.Foundation.Core.Collections;
 using Quadarax.Foundation.Core.Json;
 using Quadarax.Foundation.Core.Logging;
 using Quadarax.Foundation.Core.QMonitor.Interfaces;
@@ -29,9 +30,9 @@ namespace Quadarax.Foundation.Core.QMonitor
         private readonly GeneralDataReceivedDelegate? _generalReceivedCallback;
         private readonly DataReceivedDelegate? _dataReceivedCallback;
         private readonly IDictionary<string, MonReceiverGeneralCahedItem> _generalCache = new ConcurrentDictionary<string, MonReceiverGeneralCahedItem>();
-        private readonly IList<MonData> _dataCache = new List<MonData>();
+        private readonly IList<MonData> _dataCache = new LockedList<MonData>();
         private readonly JsonSerializerOptions _jsonSerializerOptions;
-        private DateTime _lastSavedTimestamp;
+        private long _lastSavedTimestamp;
         private readonly IFileSystem _fileSystem;
         private ContiguousWorker? _dataReceiverWorker;
         private ILogger? _logger;
@@ -41,7 +42,7 @@ namespace Quadarax.Foundation.Core.QMonitor
         {
             _logger = logger;
             _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
-            _lastSavedTimestamp = DateTime.Now;
+            _lastSavedTimestamp = DateTime.Now.ToFileTimeUtc();
             _generalReceivedCallback = generalDataReceivedCallback;
             _dataReceivedCallback = liveDataReceivedCallback;
             _jsonSerializerOptions = new JsonSerializerOptions
@@ -166,8 +167,8 @@ namespace Quadarax.Foundation.Core.QMonitor
 
         protected override async Task OnTimerData(QMonWorkerContext context)
         {
-            _lastSavedTimestamp = DateTime.Now;
-            IEnumerable<MonData> itemsToSave = _dataCache.Where(x => x.Timestamp < _lastSavedTimestamp).ToImmutableArray();
+            _lastSavedTimestamp = DateTime.Now.ToFileTimeUtc();
+            IEnumerable<MonData> itemsToSave = _dataCache.Where(x => x.TimestampFtUtc < _lastSavedTimestamp).ToImmutableArray();
             IEnumerable<string> instances = itemsToSave.Select(x => x.InstanceIdentifier).Distinct();
        
             foreach (var instance in instances)
@@ -179,13 +180,13 @@ namespace Quadarax.Foundation.Core.QMonitor
                     Log(LogSeverityEnum.Debug, $"Data receiver created data cache directory '{dirName}'");
                 }
 
-                var batchData = itemsToSave.Where(x => x.InstanceIdentifier == instance).OrderBy(x => x.Timestamp).ToArray();
+                var batchData = itemsToSave.Where(x => x.InstanceIdentifier == instance).OrderBy(x => x.TimestampFtUtc).ToArray();
                 _dataReceivedCallback?.Invoke(batchData);
 
                 foreach (var data in batchData)
                 {
                     var sbFileName = new StringBuilder();
-                    sbFileName.Append(data.Name).Append("_").Append(data.Timestamp.ToFileTimeUtc()).Append(".json");
+                    sbFileName.Append(data.Name).Append("_").Append(data.TimestampFtUtc).Append(".json");
                     var fileName = _fileSystem.Path.Combine(dirName, sbFileName.ToString());
                     await using (var streamWriter = File.CreateText(fileName))
                     {

+ 40 - 49
Workbench/QMonitor/qmonlib/QMonReceiverHandler.cs

@@ -1,11 +1,11 @@
-using System.Collections.Concurrent;
-using System.IO.Abstractions;
+using System.IO.Abstractions;
 using System.Text.Json;
 using Quadarax.Foundation.Core.Collections;
 using Quadarax.Foundation.Core.Json.Extensions;
 using Quadarax.Foundation.Core.Logging;
 using Quadarax.Foundation.Core.QMonitor.Interfaces;
 using Quadarax.Foundation.Core.Object;
+using Quadarax.Foundation.Core.Thread;
 using Quadarax.Foundation.Core.Value.Extensions;
 
 namespace Quadarax.Foundation.Core.QMonitor
@@ -28,7 +28,9 @@ namespace Quadarax.Foundation.Core.QMonitor
         }
 
         public bool IsLiveBroadcast {get; private set; }
-        public DateTime? Timestamp { get; private set; }
+
+        public DateTime? Timestamp => _currentTimestampFtUtc == null ? null : DateTime.FromFileTimeUtc(_currentTimestampFtUtc.Value);
+
         public TimeSpan RefreshInterval { get => _refreshInterval; set => SetRefreshInterval(value); }
         public MonData[]? CurrentData { get; private set; }
         public event ReceiverHandlerStateChanged? ReceiverHandlerStateChanged;
@@ -44,10 +46,11 @@ namespace Quadarax.Foundation.Core.QMonitor
         private string _cachePath;
         private ILog? _log;
         private IFileSystemWatcher? _watcher;
-        private Timer _refreshTimer;
+        private LoopWorker _refreshTimer;
         private readonly LockedList<DataCacheItem> _dataCache = new();
+        private ILogger? _logger;
 
-        private bool _isOverlapOnRefreshTimer;
+        private long? _currentTimestampFtUtc;
         #endregion
 
         #region *** Constructor ***
@@ -57,7 +60,8 @@ namespace Quadarax.Foundation.Core.QMonitor
             if (string.IsNullOrEmpty(cachePath)) throw new ArgumentNullException(nameof(cachePath));
             _cachePath = cachePath;
             _log = logger?.GetLogger(nameof(QMonReceiverHandler));
-            
+            _logger = logger;
+
             if(string.IsNullOrEmpty(instanceIdentifier)) throw new ArgumentNullException(nameof(instanceIdentifier));
 
             InstanceIdentifier = instanceIdentifier;
@@ -69,7 +73,7 @@ namespace Quadarax.Foundation.Core.QMonitor
             _watcher.Created += Watcher_Created;
             _watcher.Deleted += Watcher_Deleted;
             RefreshInterval = refreshInterval;
-            Timestamp = GetLastAvailableDataTimestamp();
+            _currentTimestampFtUtc = GetLastAvailableDataTimestamp()?.ToFileTimeUtc();
             _state = ReceiverHandlerStateEnum.Stop;
         }
 
@@ -80,16 +84,16 @@ namespace Quadarax.Foundation.Core.QMonitor
         {
             
                 if (_dataCache.Count == 0) return null;
-                var first = _dataCache.Select(x => x.Timestamp).MinBy(x => x);
-                return first;
+                var first = _dataCache.Select(x => x.TimestampFtUtc).MinBy(x => x);
+                return DateTime.FromFileTimeUtc(first);
             
         }
         public DateTime? GetLastAvailableDataTimestamp()
         {
             
                 if (_dataCache.Count == 0) return null;
-                var last = _dataCache.Select(x => x.Timestamp).MinBy(x => x);
-                return last;
+                var last = _dataCache.Select(x => x.TimestampFtUtc).MinBy(x => x);
+                return DateTime.FromFileTimeUtc(last);
             
         }
 
@@ -140,13 +144,13 @@ namespace Quadarax.Foundation.Core.QMonitor
             }
 
             var findTimestamp = (Timestamp ?? DateTime.Now);
-            findTimestamp = isBackward ? findTimestamp.Subtract(duration.Value) : findTimestamp.Add(duration.Value);
+            var findTimestampFtUtc = (isBackward ? findTimestamp.Subtract(duration.Value) : findTimestamp.Add(duration.Value)).ToFileTimeUtc();
             int newIndex = -1;
             if (isBackward)
             {
                     for (var i = 0; i < _dataCache.Count; i++)
                     {
-                        if (_dataCache[i].Timestamp >= findTimestamp)
+                        if (_dataCache[i].TimestampFtUtc >= findTimestampFtUtc)
                         {
                             newIndex = i;
                             break;
@@ -159,7 +163,7 @@ namespace Quadarax.Foundation.Core.QMonitor
             
                     for (var i = _dataCache.Count - 1; i >= 0; i--)
                     {
-                        if (_dataCache[i].Timestamp <= findTimestamp)
+                        if (_dataCache[i].TimestampFtUtc <= findTimestampFtUtc)
                         {
                             newIndex = i;
                             break;
@@ -208,11 +212,11 @@ namespace Quadarax.Foundation.Core.QMonitor
 
         public void RewindToChange(bool isForward)
         {
-            if (Timestamp == null) return;
-            var timestamp = Timestamp;
+            if (_currentTimestampFtUtc == null) return;
+            var timestamp = _currentTimestampFtUtc;
             int currentIndex = -1;
             
-            var currentItem = _dataCache.FirstOrDefault(x => x.Timestamp == timestamp);
+            var currentItem = _dataCache.FirstOrDefault(x => x.TimestampFtUtc == _currentTimestampFtUtc);
             if (currentItem == null) return;
             currentIndex = _dataCache.IndexOf(currentItem);
             
@@ -232,7 +236,7 @@ namespace Quadarax.Foundation.Core.QMonitor
                 if (data != null)
                 {
                     ReceiverHandlerDataChanged?.Invoke(this, data);
-                    Timestamp = item.Timestamp;
+                    _currentTimestampFtUtc = item.TimestampFtUtc;
                     CurrentData = new[] { data };
                     result = true;
                 }
@@ -242,47 +246,34 @@ namespace Quadarax.Foundation.Core.QMonitor
         
         private void SetRefreshInterval(TimeSpan refreshInterval)
         {
+            _refreshTimer?.Stop(true);
             _refreshInterval = refreshInterval;
             _refreshTimer?.Dispose();
-            _log?.Log(LogSeverityEnum.Debug, $"[{InstanceIdentifier}/{Descriptor.Name}] Refresh interval was set to {RefreshInterval.ToReadableString()}");
-            _refreshTimer = new Timer(OnRefreshTimer, this, TimeSpan.Zero, refreshInterval);
+            _log?.Log(LogSeverityEnum.Debug, $"[{InstanceIdentifier}/{Descriptor.Name}] Refresh interval was set to {_refreshInterval.ToReadableString()}");
+            _refreshTimer = new LoopWorker("ReceiverHandler",null,async (_)=>await OnRefreshTimer(this), _logger);
+            _refreshTimer.Start(_refreshInterval);
         }
 
 
-        private void OnRefreshTimer(object? state)
+        private async Task OnRefreshTimer(object? state)
         {
             //TODO: add overlap checking here
-            if (!_isOverlapOnRefreshTimer)
+
+            if (State == ReceiverHandlerStateEnum.Play)
             {
-                _isOverlapOnRefreshTimer = true;
-                try
-                {
-                    if (State == ReceiverHandlerStateEnum.Play)
-                    {
-                        if (_dataCache.Count == 0) return;
-                        if (Timestamp < _dataCache.Last().Timestamp)
-                        {
-                            var currentItem = _dataCache.FirstOrDefault(x => x.Timestamp == Timestamp);
-                            if (currentItem == null) return;
-                            var currentIndex = _dataCache.IndexOf(currentItem);
-                            var newChangeIndex = FindNextChangeIndex(currentIndex, true);
-                            if (newChangeIndex >= 0)
-                                SetAsCurrent(_dataCache[newChangeIndex]);
-                        }
-                    }
-                }
-                catch (Exception ex)
-                {
-                    _log?.Log(LogSeverityEnum.Error,
-                        $"[{InstanceIdentifier}/{Descriptor.Name}] Error on refresh timer: {ex.Message}");
-                }
-                finally
+                if (_dataCache.Count == 0) return;
+                if (_currentTimestampFtUtc < _dataCache.Last().TimestampFtUtc)
                 {
-                    _isOverlapOnRefreshTimer = false;
+                    var currentItem = _dataCache.FirstOrDefault(x => x.TimestampFtUtc == _currentTimestampFtUtc);
+                    if (currentItem == null) return;
+                    var currentIndex = _dataCache.IndexOf(currentItem);
+                    var newChangeIndex = FindNextChangeIndex(currentIndex, true);
+                    if (newChangeIndex >= 0)
+                        SetAsCurrent(_dataCache[newChangeIndex]);
                 }
             }
         }
-
+        
 
         private void RefreshFiles()
         {
@@ -362,13 +353,13 @@ namespace Quadarax.Foundation.Core.QMonitor
 
         private class DataCacheItem
         {
-            public DateTime Timestamp { get; set; }
+            public long TimestampFtUtc { get; set; }
             public JsonDocument Data { get; set; }
             public string FileName { get; set; }
 
             public DataCacheItem(string fileName, MonData data)
             {
-                Timestamp = data.Timestamp;
+                TimestampFtUtc = data.TimestampFtUtc;
                 Data = JsonDocument.Parse(data.Data);
                 FileName = fileName;
             }

+ 1 - 1
Workbench/QMonitor/run_emit.cmd

@@ -1,2 +1,2 @@
 @echo off
-qmonlib.emit\bin\Debug\net7.0\qdr.fnd.core.qmonlib.emit.exe
+qmonlib.emit\bin\Debug\net7.0\qdr.fnd.core.qmonlib.emit.exe test service