2023-10-08 21:18:59 +08:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Data;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
namespace Aitex.Core.RT.DataCollection.HighPerformance
|
|
|
|
|
{
|
2024-01-15 09:38:57 +08:00
|
|
|
|
internal class DataTraceDataTableCache : IDataTraceCache
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
|
|
|
|
#region Variables
|
|
|
|
|
|
|
|
|
|
private readonly object _syncRoot = new();
|
|
|
|
|
private readonly int _maxRowsPerFlush;
|
|
|
|
|
private readonly string _tableName;
|
|
|
|
|
|
|
|
|
|
private DataTable _dataTable;
|
|
|
|
|
private readonly Queue<long> _qCachedTimestamps = new();
|
|
|
|
|
private readonly int _minCachePeriodMs;
|
|
|
|
|
private readonly Stopwatch _stopwatch = new();
|
|
|
|
|
|
|
|
|
|
private readonly StringBuilder _sqlExpr = new(5000000); // preallocate 5MB
|
|
|
|
|
private readonly StringBuilder _sqlValuesList = new(5000000); // preallocate 5MB
|
|
|
|
|
|
|
|
|
|
private int _lastPersistRows = 0;
|
|
|
|
|
private double _lastCachePeriodMs = 0;
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Constructors
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 创建数据记录器缓存对象。
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="tableName">当前Cache对应的数据库表名称。</param>
|
|
|
|
|
/// <param name="module">当前Cache对应的分组名称。</param>
|
|
|
|
|
/// <param name="dataHolders">数据获取器对象集合,保存当前Cache需要缓存的数据源。</param>
|
|
|
|
|
/// <param name="minCachePeriodMs">最小缓存周期,如果外部频繁调用<see cref="Cache"/>方法请求缓存数据,该周期内的重复请求被忽略,以避免高频缓存导致性能下降。</param>
|
|
|
|
|
/// <param name="maxRowsPerFlush">每次持久化的最大行数,避免一次性对数据库写入太多数据造成性能问题。</param>
|
|
|
|
|
/// <exception cref="ArgumentException"></exception>
|
2024-01-15 19:20:23 +08:00
|
|
|
|
public DataTraceDataTableCache(string tableName, string module, IReadOnlyList<IDataBuffer> dataHolders, int minCachePeriodMs = 50, int maxRowsPerFlush = 2000)
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
|
|
|
|
Debug.Assert(dataHolders is { Count: > 0 }, "Incorrect Data Collector List.");
|
|
|
|
|
|
2024-01-15 19:20:23 +08:00
|
|
|
|
Category = module;
|
|
|
|
|
DataBuffers = dataHolders;
|
2023-10-08 21:18:59 +08:00
|
|
|
|
_tableName = tableName;
|
|
|
|
|
_maxRowsPerFlush = maxRowsPerFlush;
|
|
|
|
|
_minCachePeriodMs = minCachePeriodMs;
|
|
|
|
|
|
|
|
|
|
// 必须传入有效的数据收集器列表。
|
|
|
|
|
if (dataHolders == null || dataHolders.Count == 0)
|
|
|
|
|
throw new ArgumentException("数据收集器列表不能为空。", nameof(dataHolders));
|
|
|
|
|
|
|
|
|
|
// 检查数据收集器列表中是否存在重复的序号。
|
|
|
|
|
if (dataHolders.GroupBy(dc => dc.Index).Any(g => g.Count() > 1))
|
|
|
|
|
throw new ArgumentException("数据收集器列表中存在重复的序号。", nameof(dataHolders));
|
|
|
|
|
|
|
|
|
|
// 检查数据收集器列表中是否存在重复的名称。
|
|
|
|
|
if (dataHolders.GroupBy(dc => dc.Name).Any(g => g.Count() > 1))
|
|
|
|
|
throw new ArgumentException("数据收集器列表中存在重复的名称。", nameof(dataHolders));
|
|
|
|
|
|
|
|
|
|
// 检查数据收集器列表中是否存在空的获取器。
|
|
|
|
|
if (dataHolders.Any(dc => dc.Read == null))
|
|
|
|
|
throw new ArgumentException("数据收集器列表中存在空的获取器。", nameof(dataHolders));
|
|
|
|
|
|
|
|
|
|
_dataTable = CreateDataTable();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*DATA.Subscribe($"{ModuleName.System}.DBRC.{Module}.CachedCount", ()=> _qCachedTimestamps.Count, SubscriptionAttribute.FLAG.IgnoreSaveDB);
|
|
|
|
|
DATA.Subscribe($"{ModuleName.System}.DBRC.{Module}.PersistRows", ()=> _lastPersistRows, SubscriptionAttribute.FLAG.IgnoreSaveDB);
|
|
|
|
|
DATA.Subscribe($"{ModuleName.System}.DBRC.{Module}.PersistPeriodMs", ()=> _lastPersistPeriodMs, SubscriptionAttribute.FLAG.IgnoreSaveDB);*/
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Properties
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 返回当前缓存服务的模组名称。
|
|
|
|
|
/// </summary>
|
2024-01-15 19:20:23 +08:00
|
|
|
|
public string Category { get; }
|
2023-10-08 21:18:59 +08:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 返回数据获取器列表。
|
|
|
|
|
/// </summary>
|
2024-01-15 19:20:23 +08:00
|
|
|
|
public IReadOnlyList<IDataBuffer> DataBuffers { get; }
|
2023-10-08 21:18:59 +08:00
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Methods
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 创建临时数据表。
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
private DataTable CreateDataTable()
|
|
|
|
|
{
|
|
|
|
|
lock (_syncRoot)
|
|
|
|
|
{
|
|
|
|
|
var dt = new DataTable(_tableName);
|
|
|
|
|
dt.Columns.Add("time", typeof(long));
|
2024-01-15 19:20:23 +08:00
|
|
|
|
foreach (var dh in DataBuffers)
|
2024-01-09 17:27:33 +08:00
|
|
|
|
dt.Columns.Add(dh.Name, dh.ValueType);
|
|
|
|
|
|
2023-10-08 21:18:59 +08:00
|
|
|
|
return dt;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 立即缓存当前数据。
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void Cache()
|
|
|
|
|
{
|
|
|
|
|
lock (_syncRoot)
|
|
|
|
|
{
|
|
|
|
|
// 限制最大Cache频率
|
|
|
|
|
if (_stopwatch.IsRunning && _stopwatch.ElapsedMilliseconds < _minCachePeriodMs)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
//TODO 需要限制Q的最大数据量,并且检测HalfFull和Full事件,并触发警告,可能PC性能出现问题
|
|
|
|
|
|
|
|
|
|
_lastCachePeriodMs = _stopwatch.ElapsedMilliseconds;
|
|
|
|
|
_stopwatch.Restart();
|
|
|
|
|
var ts = DateTime.Now.Ticks;
|
|
|
|
|
var row = _dataTable.NewRow();
|
|
|
|
|
row["time"] = ts;
|
2024-01-15 19:20:23 +08:00
|
|
|
|
var ret = Parallel.ForEach(DataBuffers, dc =>
|
2023-10-08 21:18:59 +08:00
|
|
|
|
{
|
|
|
|
|
var ret = dc.Read();
|
|
|
|
|
row[dc.Name] = ret;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (ret.IsCompleted)
|
|
|
|
|
{
|
|
|
|
|
_dataTable.Rows.Add(row);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
}
|
|
|
|
|
}
|