2023-10-08 21:18:59 +08:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2024-01-08 16:29:53 +08:00
|
|
|
|
using System.Diagnostics;
|
2023-10-08 21:18:59 +08:00
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using Aitex.Core.RT.DataCenter;
|
|
|
|
|
using Aitex.Core.RT.DBCore;
|
|
|
|
|
using Aitex.Core.RT.Log;
|
|
|
|
|
using Aitex.Core.RT.SCCore;
|
|
|
|
|
using Aitex.Core.Util;
|
|
|
|
|
using MECF.Framework.Common.Equipment;
|
2024-01-10 00:32:32 +08:00
|
|
|
|
using ThreadState = System.Threading.ThreadState;
|
2023-10-08 21:18:59 +08:00
|
|
|
|
|
|
|
|
|
namespace Aitex.Core.RT.DataCollection.HighPerformance
|
2024-01-07 11:29:22 +08:00
|
|
|
|
{
|
2023-10-08 21:18:59 +08:00
|
|
|
|
|
|
|
|
|
public class DataRecorderManager : Singleton<DataRecorderManager>
|
|
|
|
|
{
|
|
|
|
|
#region Variables
|
|
|
|
|
|
2024-01-10 00:32:32 +08:00
|
|
|
|
private readonly object _syncRoot = new();
|
2023-10-08 21:18:59 +08:00
|
|
|
|
private readonly int _dataRecorderInterval;
|
|
|
|
|
private readonly int _dataRecorderCacheSize;
|
|
|
|
|
private readonly int _dataRecorderCachePeriodMs;
|
2024-01-07 11:29:22 +08:00
|
|
|
|
private string[] _dataTableCategory = { ModuleName.System.ToString() };
|
2024-01-08 16:29:53 +08:00
|
|
|
|
private readonly Dictionary<string, Func<object>> _dataGetters = new();
|
2024-01-10 00:32:32 +08:00
|
|
|
|
private readonly R_TRIG _rTrigPersistThreadError = new();
|
2024-01-07 11:29:22 +08:00
|
|
|
|
private readonly CancellationTokenSource _ctsDataRecorderThread;
|
|
|
|
|
private readonly CancellationToken _ctDataRecorderThread;
|
2024-01-07 11:45:22 +08:00
|
|
|
|
private Dictionary<string, DataRecorderCache> _dictDataRecCachePerDataTable;
|
2024-01-08 16:29:53 +08:00
|
|
|
|
private DateTime _datePrefixTableName;
|
2023-10-08 21:18:59 +08:00
|
|
|
|
|
2024-01-10 00:32:32 +08:00
|
|
|
|
private readonly Thread _threadCache;
|
|
|
|
|
private readonly Thread _threadPersist;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-10-08 21:18:59 +08:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Constructors
|
|
|
|
|
|
|
|
|
|
public DataRecorderManager()
|
|
|
|
|
{
|
|
|
|
|
_dataRecorderInterval = (SC.ContainsItem("System.DataRecorderInterval")
|
|
|
|
|
? SC.GetValue<int>("System.DataCollectionInterval")
|
|
|
|
|
: 1000);
|
|
|
|
|
|
|
|
|
|
_dataRecorderCacheSize = (SC.ContainsItem("System.DataRecorderCacheSize")
|
|
|
|
|
? SC.GetValue<int>("System.DataRecorderCacheSize")
|
|
|
|
|
: DataRecorderCache.MIN_CACHE_SIZE);
|
|
|
|
|
|
|
|
|
|
_dataRecorderCachePeriodMs = (SC.ContainsItem("System.DataRecorderCachePeriod")
|
|
|
|
|
? SC.GetValue<int>("System.DataRecorderCachePeriod")
|
|
|
|
|
: DataRecorderCache.MIN_CACHE_PERIOD_MS);
|
2024-01-07 11:29:22 +08:00
|
|
|
|
|
|
|
|
|
_ctsDataRecorderThread = new();
|
|
|
|
|
_ctDataRecorderThread = _ctsDataRecorderThread.Token;
|
2024-01-10 00:32:32 +08:00
|
|
|
|
|
|
|
|
|
_threadCache = new Thread(CacheThread);
|
|
|
|
|
_threadCache.Name = nameof(CacheThread);
|
|
|
|
|
_threadPersist = new Thread(PersistThread);
|
|
|
|
|
_threadPersist.Name = nameof(PersistThread);
|
2023-10-08 21:18:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Methods
|
|
|
|
|
|
2024-01-08 16:29:53 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 初始化<see cref="DataRecorderManager"/>对象。
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="dataTableCategory"></param>
|
2024-01-07 11:29:22 +08:00
|
|
|
|
public void Initialize(string[] dataTableCategory)
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
2024-01-10 00:32:32 +08:00
|
|
|
|
|
2024-01-12 18:14:32 +08:00
|
|
|
|
DATA.Subscribe($"{ModuleHelper.GetDiagnosisPath(ModuleName.System)}.DTPersistCostMs", () => _statPersistCostMs);
|
|
|
|
|
DATA.Subscribe($"{ModuleHelper.GetDiagnosisPath(ModuleName.System)}.DTPersistInvMs", () => _statPersistInvMs);
|
|
|
|
|
DATA.Subscribe($"{ModuleHelper.GetDiagnosisPath(ModuleName.System)}.DTPersistLines", () => _statPersistLines);
|
|
|
|
|
DATA.Subscribe($"{ModuleHelper.GetDiagnosisPath(ModuleName.System)}.DTPersistCounter", () => _statPersistCnt);
|
|
|
|
|
DATA.Subscribe($"{ModuleHelper.GetDiagnosisPath(ModuleName.System)}.DTPersistErrorCounter", () => _statPersistErrorCnt);
|
2024-01-10 00:32:32 +08:00
|
|
|
|
|
2024-01-12 18:14:32 +08:00
|
|
|
|
DATA.Subscribe($"{ModuleHelper.GetDiagnosisPath(ModuleName.System)}.DTCacheCostMs", () => _statCacheCostMs);
|
|
|
|
|
DATA.Subscribe($"{ModuleHelper.GetDiagnosisPath(ModuleName.System)}.DTCacheInvMs", () => _statCacheInvMs);
|
|
|
|
|
DATA.Subscribe($"{ModuleHelper.GetDiagnosisPath(ModuleName.System)}.DTCacheCounter", () => _statCacheCnt);
|
|
|
|
|
|
|
|
|
|
#if CHECK_DATA_TRACE_OVERRUN_ISSUE
|
|
|
|
|
DATA.Subscribe($"{ModuleHelper.GetDiagnosisPath(ModuleName.System)}.DTOverrunDuration", () => _statCacheOverrunDuration);
|
|
|
|
|
#endif
|
2024-01-07 11:29:22 +08:00
|
|
|
|
_dataTableCategory = dataTableCategory;
|
2024-01-10 00:32:32 +08:00
|
|
|
|
_threadCache.Start();
|
2023-10-08 21:18:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 立即缓存所有模组的数据。
|
|
|
|
|
/// </summary>
|
2024-01-08 16:29:53 +08:00
|
|
|
|
public void ImmediateCache(CacheDiagnosisInfo diagnosisInfo = null)
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
2024-01-08 16:29:53 +08:00
|
|
|
|
if (diagnosisInfo != null)
|
|
|
|
|
Debug.WriteLine($"{diagnosisInfo.Module}.{diagnosisInfo.IoName} changed to {diagnosisInfo.Value}",
|
|
|
|
|
$"{nameof(DataRecorderManager)} - {nameof(ImmediateCache)}");
|
2024-01-10 18:24:10 +08:00
|
|
|
|
|
|
|
|
|
lock (_lockTmrCacheThreadDelay)
|
|
|
|
|
{
|
|
|
|
|
_tmrCacheThreadDelay.Start(_dataRecorderInterval);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-08 21:18:59 +08:00
|
|
|
|
DoCache();
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-10 18:24:10 +08:00
|
|
|
|
#region Background Threads
|
|
|
|
|
|
2024-01-10 00:32:32 +08:00
|
|
|
|
private readonly DeviceTimer _tmrPersistDelay = new();
|
|
|
|
|
|
|
|
|
|
private void PersistThread()
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
2024-01-07 11:29:22 +08:00
|
|
|
|
try
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
2024-01-07 11:29:22 +08:00
|
|
|
|
while (true)
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
2024-01-07 11:29:22 +08:00
|
|
|
|
_ctDataRecorderThread.ThrowIfCancellationRequested();
|
2024-01-10 00:32:32 +08:00
|
|
|
|
Thread.Sleep(10);
|
2024-01-10 18:24:10 +08:00
|
|
|
|
if (!_tmrPersistDelay.IsIdle() && !_tmrPersistDelay.IsTimeout())
|
2024-01-10 00:32:32 +08:00
|
|
|
|
continue;
|
2024-01-10 18:24:10 +08:00
|
|
|
|
|
2024-01-10 00:32:32 +08:00
|
|
|
|
// next persist will be called 5sec later
|
|
|
|
|
_tmrPersistDelay.Start(5000);
|
|
|
|
|
|
|
|
|
|
var ret = Persist();
|
|
|
|
|
|
|
|
|
|
// check whether the DB write operation is succeeded
|
|
|
|
|
_rTrigPersistThreadError.CLK = ret < 0;
|
|
|
|
|
if (_rTrigPersistThreadError.Q) // 插入数据错误,退出
|
|
|
|
|
{
|
|
|
|
|
// DB Write Error
|
|
|
|
|
LOG.Error(
|
|
|
|
|
$"{nameof(DataRecorderManager)}.{nameof(PersistThread)} Unable to persist the cache.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
{
|
|
|
|
|
LOG.Info($"{nameof(DataRecorderManager)}.{nameof(PersistThread)} terminated.");
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2024-01-10 18:24:10 +08:00
|
|
|
|
LOG.Write(ex, $"{nameof(DataRecorderManager)}.{nameof(PersistThread)} terminated unexpectedly,{ex.Message}");
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
Persist();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
LOG.Write(ex,
|
|
|
|
|
$"{nameof(DataRecorderManager)}.{nameof(PersistThread)} Failed to persist cache when exit the thread, {ex.Message}");
|
|
|
|
|
}
|
2024-01-10 00:32:32 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-01-07 11:29:22 +08:00
|
|
|
|
|
2024-01-10 00:32:32 +08:00
|
|
|
|
private readonly DeviceTimer _tmrCacheThreadDelay = new();
|
2024-01-10 18:24:10 +08:00
|
|
|
|
private readonly object _lockTmrCacheThreadDelay = new();
|
2024-01-10 00:32:32 +08:00
|
|
|
|
private void CacheThread()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
Thread.Sleep(_dataRecorderInterval);
|
|
|
|
|
_ctDataRecorderThread.ThrowIfCancellationRequested();
|
2023-10-08 21:18:59 +08:00
|
|
|
|
CreateCache();
|
2024-01-10 00:32:32 +08:00
|
|
|
|
_ctDataRecorderThread.ThrowIfCancellationRequested();
|
|
|
|
|
|
|
|
|
|
// start persist thread once the cache instances are built.
|
|
|
|
|
// note the persist thread must be started before CreateCache() method.
|
|
|
|
|
if (_threadPersist.ThreadState == ThreadState.Unstarted)
|
|
|
|
|
_threadPersist.Start();
|
2023-10-08 21:18:59 +08:00
|
|
|
|
|
|
|
|
|
// 开始周期性数据采集
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
2024-01-07 11:29:22 +08:00
|
|
|
|
_ctDataRecorderThread.ThrowIfCancellationRequested();
|
2024-01-10 00:32:32 +08:00
|
|
|
|
Thread.Sleep(10);
|
2024-01-07 11:29:22 +08:00
|
|
|
|
|
2024-01-10 18:24:10 +08:00
|
|
|
|
lock (_lockTmrCacheThreadDelay)
|
|
|
|
|
{
|
|
|
|
|
if (!_tmrCacheThreadDelay.IsIdle() && !_tmrCacheThreadDelay.IsTimeout())
|
|
|
|
|
continue;
|
|
|
|
|
_tmrCacheThreadDelay.Start(_dataRecorderInterval);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-08 21:18:59 +08:00
|
|
|
|
// 跨天,退出循环并创建新表
|
2024-01-08 16:29:53 +08:00
|
|
|
|
if (DateTime.Now.Date != _datePrefixTableName)
|
2023-10-08 21:18:59 +08:00
|
|
|
|
break;
|
2024-01-10 18:24:10 +08:00
|
|
|
|
|
2023-10-08 21:18:59 +08:00
|
|
|
|
// 立即缓存一次数据
|
|
|
|
|
DoCache();
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-01-07 11:29:22 +08:00
|
|
|
|
}
|
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
{
|
2024-01-10 00:32:32 +08:00
|
|
|
|
LOG.Info($"{nameof(DataRecorderManager)}.{nameof(CacheThread)} terminated.");
|
2024-01-07 11:29:22 +08:00
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2024-01-10 00:32:32 +08:00
|
|
|
|
LOG.Write(ex, $"{nameof(DataRecorderManager)}.{nameof(CacheThread)}线程意外终止,{ex.Message}");
|
2023-10-08 21:18:59 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-01-10 18:24:10 +08:00
|
|
|
|
|
|
|
|
|
#endregion
|
2023-10-08 21:18:59 +08:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 扫描<see cref="DataManager"/>,获取所有可记录的数据。
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// 仅数据管理器中注册的数据,且类型为<see cref="bool"/>、<see cref="double"/>、<see cref="float"/>、<see cref="int"/>、<see cref="ushort"/>、<see cref="short"/>的数据支持保存到数据库。
|
|
|
|
|
/// </remarks>>
|
|
|
|
|
private void GetRecordableDataSource()
|
|
|
|
|
{
|
|
|
|
|
var dBRecorderList = DATA.GetDBRecorderList();
|
|
|
|
|
foreach (var item in dBRecorderList)
|
|
|
|
|
{
|
2024-01-08 16:29:53 +08:00
|
|
|
|
if (_dataGetters.ContainsKey(item.Key))
|
2023-10-08 21:18:59 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
2024-01-08 16:29:53 +08:00
|
|
|
|
var getter = item.Value;
|
|
|
|
|
var value = getter.Invoke();
|
|
|
|
|
if (value != null)
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
2024-01-08 16:29:53 +08:00
|
|
|
|
var type = value.GetType();
|
2024-01-09 17:27:33 +08:00
|
|
|
|
if(DataRecorderHelper.SupportedValueTypes.Contains(type))
|
2024-01-08 16:29:53 +08:00
|
|
|
|
_dataGetters[item.Key] = getter;
|
2023-10-08 21:18:59 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 创建数据缓存对象。
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void CreateCache()
|
|
|
|
|
{
|
2024-01-10 00:32:32 +08:00
|
|
|
|
lock (_syncRoot)
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
|
|
|
|
GetRecordableDataSource();
|
|
|
|
|
|
2024-01-07 11:45:22 +08:00
|
|
|
|
_dictDataRecCachePerDataTable = new();
|
2024-01-09 17:27:33 +08:00
|
|
|
|
Dictionary<string, List<IDataHolder>> dictDataHolderPerDataTable = new();
|
2023-10-08 21:18:59 +08:00
|
|
|
|
|
2024-01-07 11:29:22 +08:00
|
|
|
|
var defaultModuleName = (_dataTableCategory.Contains(ModuleName.System.ToString())
|
2023-10-08 21:18:59 +08:00
|
|
|
|
? ModuleName.System.ToString()
|
2024-01-08 16:29:53 +08:00
|
|
|
|
: (_dataTableCategory.Contains(ModuleName.System.ToString())
|
|
|
|
|
? ModuleName.System.ToString()
|
|
|
|
|
: _dataTableCategory[0]));
|
2023-10-08 21:18:59 +08:00
|
|
|
|
|
|
|
|
|
|
2024-01-08 16:29:53 +08:00
|
|
|
|
if (_dataGetters is { Count: > 0 })
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
2024-01-08 16:29:53 +08:00
|
|
|
|
foreach (var dataName in _dataGetters.Keys)
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
2024-01-08 16:29:53 +08:00
|
|
|
|
Type valueType;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// determine the type of the data.
|
|
|
|
|
var getter = _dataGetters[dataName];
|
|
|
|
|
var value = getter.Invoke();
|
|
|
|
|
valueType = value.GetType();
|
|
|
|
|
Debug.Assert(valueType != null, $"Unable to determine the type of DATA item {dataName}");
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Debug.Assert(false, $"Unable to determine the type of DATA item {dataName}");
|
|
|
|
|
LOG.Write(ex, $"Unable to determine the type of DATA item {dataName}, {ex.Message}");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var isTableAssigned = false;
|
2024-01-07 11:29:22 +08:00
|
|
|
|
foreach (var categoryName in _dataTableCategory)
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
2024-01-07 11:29:22 +08:00
|
|
|
|
if (dataName.StartsWith(categoryName + ".") ||
|
|
|
|
|
dataName.StartsWith("IO." + categoryName + "."))
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
2024-01-07 11:45:22 +08:00
|
|
|
|
if (!dictDataHolderPerDataTable.ContainsKey(categoryName))
|
2024-01-09 17:27:33 +08:00
|
|
|
|
dictDataHolderPerDataTable[categoryName] = [];
|
2023-10-08 21:18:59 +08:00
|
|
|
|
|
2024-01-09 17:27:33 +08:00
|
|
|
|
dictDataHolderPerDataTable[categoryName].Add(new DataHolder<ValueType>(dictDataHolderPerDataTable[categoryName].Count,
|
|
|
|
|
dataName, _dataGetters[dataName]));
|
2024-01-08 16:29:53 +08:00
|
|
|
|
isTableAssigned = true;
|
2023-10-08 21:18:59 +08:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-08 16:29:53 +08:00
|
|
|
|
if (!isTableAssigned)
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
|
|
|
|
|
2024-01-07 11:45:22 +08:00
|
|
|
|
if (!dictDataHolderPerDataTable.ContainsKey(defaultModuleName))
|
2024-01-09 17:27:33 +08:00
|
|
|
|
dictDataHolderPerDataTable[defaultModuleName] = [];
|
2023-10-08 21:18:59 +08:00
|
|
|
|
|
2024-01-09 17:27:33 +08:00
|
|
|
|
dictDataHolderPerDataTable[defaultModuleName].Add(new DataHolder<ValueType>(
|
2024-01-07 11:45:22 +08:00
|
|
|
|
dictDataHolderPerDataTable[defaultModuleName].Count,
|
2024-01-09 17:27:33 +08:00
|
|
|
|
dataName, _dataGetters[dataName]));
|
2023-10-08 21:18:59 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-08 16:29:53 +08:00
|
|
|
|
_datePrefixTableName = DateTime.Now.Date;
|
2024-01-07 11:45:22 +08:00
|
|
|
|
foreach (var category in dictDataHolderPerDataTable.Keys)
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
2024-01-07 11:45:22 +08:00
|
|
|
|
var holders = dictDataHolderPerDataTable[category];
|
2024-01-08 16:29:53 +08:00
|
|
|
|
var tableName = $"{_datePrefixTableName:yyyyMMdd}.{category}";
|
2024-01-07 11:45:22 +08:00
|
|
|
|
_dictDataRecCachePerDataTable[category] =
|
|
|
|
|
new DataRecorderCache(tableName, category, holders, minCachePeriodMs: _dataRecorderCachePeriodMs,
|
2023-10-08 21:18:59 +08:00
|
|
|
|
maxCacheSize: _dataRecorderCacheSize);
|
2024-01-07 11:29:22 +08:00
|
|
|
|
|
2024-01-07 11:45:22 +08:00
|
|
|
|
UpdateTableSchema(tableName, holders);
|
2023-10-08 21:18:59 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-10 18:24:10 +08:00
|
|
|
|
private int _statCacheCostMs;
|
|
|
|
|
private int _statCacheInvMs;
|
|
|
|
|
private int _statCacheCnt;
|
2024-01-12 18:14:32 +08:00
|
|
|
|
|
|
|
|
|
private int _statCacheOverrunDuration;
|
|
|
|
|
private readonly R_TRIG _rTrigTraceOverrun = new();
|
|
|
|
|
private readonly Stopwatch _swTraceOverrun = new();
|
|
|
|
|
|
2024-01-10 18:24:10 +08:00
|
|
|
|
private readonly Stopwatch _swCacheCostMsStat = new();
|
|
|
|
|
private readonly Stopwatch _swCacheInvMsStat = new();
|
|
|
|
|
|
2023-10-08 21:18:59 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 立即缓存所有模组的数据。
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
private void DoCache()
|
|
|
|
|
{
|
2024-01-10 00:32:32 +08:00
|
|
|
|
lock (_syncRoot)
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
2024-01-10 18:24:10 +08:00
|
|
|
|
// statistic cache called interval in ms.
|
|
|
|
|
_swCacheInvMsStat.Stop();
|
|
|
|
|
_statCacheInvMs = (int)_swCacheInvMsStat.Elapsed.TotalMilliseconds;
|
2024-01-12 18:14:32 +08:00
|
|
|
|
|
|
|
|
|
#if CHECK_DATA_TRACE_OVERRUN_ISSUE
|
|
|
|
|
// Check data-trace-overrun issue
|
|
|
|
|
_rTrigTraceOverrun.CLK = _statCacheInvMs < 900;
|
|
|
|
|
if (_rTrigTraceOverrun.Q && !_swTraceOverrun.IsRunning)
|
|
|
|
|
{
|
|
|
|
|
_swTraceOverrun.Restart();
|
|
|
|
|
}
|
|
|
|
|
else if (!_rTrigTraceOverrun.M && _swTraceOverrun.IsRunning)
|
|
|
|
|
{
|
|
|
|
|
_swTraceOverrun.Stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_swTraceOverrun.Elapsed.TotalSeconds > 120)
|
|
|
|
|
{
|
|
|
|
|
_swTraceOverrun.Stop();
|
|
|
|
|
_rTrigTraceOverrun.RST = true;
|
|
|
|
|
Debug.Assert(false, "Data-Trace-Overrun Issue Detected",
|
|
|
|
|
$"The {nameof(DataRecorderManager)} continues to trace data at a high frequency " +
|
|
|
|
|
$"within 120 seconds. Please check whether there are any Monitor() methods running frequently " +
|
|
|
|
|
$"to assign values to DO and AO.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_statCacheOverrunDuration = (int)_swTraceOverrun.Elapsed.TotalSeconds;
|
|
|
|
|
#endif
|
2024-01-10 18:24:10 +08:00
|
|
|
|
_swCacheCostMsStat.Restart();
|
|
|
|
|
|
2023-10-08 21:18:59 +08:00
|
|
|
|
// DO调用ForceCache方法可能发生在当前对象初始化之前,此时_dictCachePerModule为null
|
2024-01-07 11:45:22 +08:00
|
|
|
|
if (_dictDataRecCachePerDataTable == null)
|
2023-10-08 21:18:59 +08:00
|
|
|
|
return;
|
2024-01-10 18:24:10 +08:00
|
|
|
|
|
2023-10-08 21:18:59 +08:00
|
|
|
|
// 立即缓存一次数据
|
2024-01-07 11:45:22 +08:00
|
|
|
|
foreach (var drc in _dictDataRecCachePerDataTable.Values)
|
2023-10-08 21:18:59 +08:00
|
|
|
|
drc.Cache();
|
2024-01-10 18:24:10 +08:00
|
|
|
|
|
|
|
|
|
_swCacheCostMsStat.Stop();
|
|
|
|
|
_statCacheCostMs = (int)_swCacheCostMsStat.Elapsed.TotalMilliseconds;
|
|
|
|
|
|
|
|
|
|
_swCacheInvMsStat.Restart();
|
|
|
|
|
_statCacheCnt++;
|
2023-10-08 21:18:59 +08:00
|
|
|
|
}
|
2024-01-07 11:29:22 +08:00
|
|
|
|
|
2023-10-08 21:18:59 +08:00
|
|
|
|
}
|
2024-01-10 18:24:10 +08:00
|
|
|
|
|
|
|
|
|
private readonly Stopwatch _swPersisCostMsStat = new();
|
|
|
|
|
private readonly Stopwatch _swPersisInvMsStat = new();
|
|
|
|
|
private int _statPersistCostMs;
|
|
|
|
|
private int _statPersistInvMs;
|
|
|
|
|
private int _statPersistCnt;
|
|
|
|
|
private int _statPersistErrorCnt;
|
|
|
|
|
private int _statPersistLines;
|
2023-10-08 21:18:59 +08:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 缓存写入数据库。
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>插入数据库的行数。-1 表示写入数据库错误。</returns>
|
|
|
|
|
public int Persist()
|
|
|
|
|
{
|
2024-01-10 18:24:10 +08:00
|
|
|
|
var effectedLines = 0;
|
2023-10-08 21:18:59 +08:00
|
|
|
|
|
2024-01-10 00:32:32 +08:00
|
|
|
|
lock (_syncRoot)
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
2024-01-10 18:24:10 +08:00
|
|
|
|
// statistic persist called interval in ms.
|
|
|
|
|
_swPersisInvMsStat.Stop();
|
|
|
|
|
_statPersistInvMs = (int)_swPersisInvMsStat.Elapsed.TotalMilliseconds;
|
|
|
|
|
|
|
|
|
|
// persist data every 5sec
|
|
|
|
|
_swPersisCostMsStat.Restart();
|
|
|
|
|
|
2024-01-07 11:45:22 +08:00
|
|
|
|
foreach (var dcc in _dictDataRecCachePerDataTable.Values)
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
|
|
|
|
var sql = dcc.GetInsertSql();
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(sql))
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2024-01-10 18:24:10 +08:00
|
|
|
|
var num = DB.ExecuteNonQuery(sql);
|
|
|
|
|
effectedLines += num;
|
2023-10-08 21:18:59 +08:00
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
LOG.Write(ex, $"{ModuleName.System.ToString()}写入数据库发生异常");
|
2024-01-10 18:24:10 +08:00
|
|
|
|
effectedLines = int.MinValue;
|
2023-10-08 21:18:59 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-01-10 18:24:10 +08:00
|
|
|
|
|
|
|
|
|
_swPersisCostMsStat.Stop();
|
|
|
|
|
_statPersistCostMs = (int)_swPersisCostMsStat.Elapsed.TotalMilliseconds;
|
2023-10-08 21:18:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-01-10 18:24:10 +08:00
|
|
|
|
if (effectedLines < 0)
|
|
|
|
|
{
|
|
|
|
|
_statPersistErrorCnt++;
|
|
|
|
|
_statPersistLines = -1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_statPersistCnt++;
|
|
|
|
|
_statPersistLines = effectedLines;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// start to calculate the interval of persist operation
|
|
|
|
|
_swPersisInvMsStat.Restart();
|
|
|
|
|
|
|
|
|
|
return effectedLines;
|
2023-10-08 21:18:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-01-07 11:29:22 +08:00
|
|
|
|
public void Terminate()
|
|
|
|
|
{
|
|
|
|
|
_ctsDataRecorderThread.Cancel();
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-09 17:27:33 +08:00
|
|
|
|
private bool UpdateTableSchema(string tblName, IEnumerable<IDataHolder> holders)
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
|
|
|
|
var cmdText = $"select column_name from information_schema.columns where table_name = '{tblName}';";
|
|
|
|
|
var reader = DB.ExecuteReader(cmdText);
|
|
|
|
|
var sqlExprAddColumn = string.Empty;
|
|
|
|
|
var existedColumns = new List<string>();
|
|
|
|
|
while (reader.Read())
|
|
|
|
|
{
|
|
|
|
|
for (var i = 0; i < reader.FieldCount; i++)
|
|
|
|
|
{
|
|
|
|
|
existedColumns.Add(reader[i].ToString());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reader.Close();
|
|
|
|
|
if (existedColumns.Count > 0)
|
|
|
|
|
{
|
2024-01-08 16:29:53 +08:00
|
|
|
|
foreach (var holder in holders)
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
2024-01-08 16:29:53 +08:00
|
|
|
|
var colName = holder.Name;
|
2023-10-08 21:18:59 +08:00
|
|
|
|
if (!existedColumns.Contains(colName))
|
|
|
|
|
{
|
2024-01-08 16:29:53 +08:00
|
|
|
|
var type = holder.ValueType;
|
2023-10-08 21:18:59 +08:00
|
|
|
|
if (type == typeof(bool))
|
|
|
|
|
{
|
|
|
|
|
sqlExprAddColumn += $"ALTER TABLE \"{tblName}\" ADD COLUMN \"{colName}\" {"Boolean"};";
|
|
|
|
|
}
|
2024-01-09 17:27:33 +08:00
|
|
|
|
else if (DataRecorderHelper.SupportedValueTypes.Contains(type))
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
|
|
|
|
sqlExprAddColumn += $"ALTER TABLE \"{tblName}\" ADD COLUMN \"{colName}\" {"Real"};";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(sqlExprAddColumn))
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
DB.ExecuteNonQuery(sqlExprAddColumn);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
LOG.Write(ex);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var sqlExprCreateTable = $"CREATE TABLE \"{tblName}\"(Time bigint NOT NULL,";
|
2024-01-07 11:45:22 +08:00
|
|
|
|
foreach (var dataItem in holders)
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
|
|
|
|
var colName = dataItem.Name;
|
|
|
|
|
var dataType = dataItem.Read().GetType();
|
|
|
|
|
if (dataType == typeof(bool))
|
|
|
|
|
{
|
|
|
|
|
sqlExprCreateTable += $"\"{colName}\" Boolean,\n";
|
|
|
|
|
}
|
2024-01-09 17:27:33 +08:00
|
|
|
|
else if (DataRecorderHelper.SupportedValueTypes.Contains(dataType))
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
|
|
|
|
sqlExprCreateTable += $"\"{colName}\" Real,\n";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sqlExprCreateTable += $"CONSTRAINT \"{tblName}_pkey\" PRIMARY KEY (Time));";
|
|
|
|
|
sqlExprCreateTable += $"GRANT SELECT ON TABLE \"{tblName}\" TO postgres;";
|
|
|
|
|
try
|
|
|
|
|
{
|
2024-01-12 18:14:32 +08:00
|
|
|
|
DB.ExecuteNonQuery(sqlExprCreateTable);
|
2023-10-08 21:18:59 +08:00
|
|
|
|
}
|
|
|
|
|
catch (Exception ex2)
|
|
|
|
|
{
|
|
|
|
|
LOG.Write(ex2, "创建数据库表格失败");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-07 11:29:22 +08:00
|
|
|
|
#endregion
|
2023-10-08 21:18:59 +08:00
|
|
|
|
|
2024-01-07 11:29:22 +08:00
|
|
|
|
}
|
2023-10-08 21:18:59 +08:00
|
|
|
|
}
|