feat(kpi): K8 — AuditLog sample source
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Kpi;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Kpi;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Kpi;
|
||||
|
||||
/// <summary>
|
||||
/// Audit Log (#23) M6 "KPI History & Trends" (K8) — the
|
||||
/// <see cref="IKpiSampleSource"/> for the <see cref="KpiSources.AuditLog"/> source.
|
||||
/// The central recorder singleton enumerates this provider once per sampling pass
|
||||
/// and persists its samples into the central <c>KpiSample</c> history table.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Snapshots the Audit Log volume + error-rate counts the live Health-dashboard
|
||||
/// "Audit" tiles already read (<see cref="IAuditLogRepository.GetKpiSnapshotAsync"/>)
|
||||
/// over the same trailing <see cref="Window"/>, plus the global pending backlog the
|
||||
/// snapshot carries. All three metrics are <see cref="KpiScopes.Global"/> only — the
|
||||
/// snapshot is a system-wide aggregate with no per-site / per-node breakdown, so
|
||||
/// <see cref="KpiSample.ScopeKey"/> is always <c>null</c>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The trailing window is anchored on <c>capturedAtUtc</c> (passed as the snapshot's
|
||||
/// <c>nowUtc</c>) so the sample reflects the same instant the recorder stamps every
|
||||
/// other source with — deterministic across the whole sampling pass rather than the
|
||||
/// repository's server-side <c>UtcNow</c> at call time.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class AuditLogKpiSampleSource : IKpiSampleSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Trailing window for the volume + error-rate aggregate. Fixed at 1 hour to
|
||||
/// match the live Audit KPI tiles (<c>AuditLogQueryService.KpiWindow</c>) and
|
||||
/// the <c>…LastHour</c> metric names below.
|
||||
/// </summary>
|
||||
private static readonly TimeSpan Window = TimeSpan.FromHours(1);
|
||||
|
||||
// Metric catalog — the exact strings persisted in KpiSample.Metric. Stable
|
||||
// identifiers the recorder + UI trend charts key on; do not rename.
|
||||
private const string TotalEventsLastHourMetric = "totalEventsLastHour";
|
||||
private const string ErrorEventsLastHourMetric = "errorEventsLastHour";
|
||||
private const string BacklogTotalMetric = "backlogTotal";
|
||||
|
||||
private readonly IAuditLogRepository _repository;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AuditLogKpiSampleSource"/> class.
|
||||
/// </summary>
|
||||
/// <param name="repository">
|
||||
/// Append-only Audit Log repository — its <see cref="IAuditLogRepository.GetKpiSnapshotAsync"/>
|
||||
/// supplies the volume, error, and backlog counts snapshotted here.
|
||||
/// </param>
|
||||
public AuditLogKpiSampleSource(IAuditLogRepository repository)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(repository);
|
||||
_repository = repository;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Source => KpiSources.AuditLog;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IReadOnlyList<KpiSample>> CollectAsync(
|
||||
DateTime capturedAtUtc,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var snapshot = await _repository
|
||||
.GetKpiSnapshotAsync(Window, nowUtc: capturedAtUtc, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// Defensive: a null snapshot would mean the repo broke its non-null
|
||||
// contract, but the source must degrade to "nothing to report" rather
|
||||
// than NRE the whole sampling pass.
|
||||
if (snapshot is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return
|
||||
[
|
||||
Sample(TotalEventsLastHourMetric, snapshot.TotalEventsLastHour, capturedAtUtc),
|
||||
Sample(ErrorEventsLastHourMetric, snapshot.ErrorEventsLastHour, capturedAtUtc),
|
||||
Sample(BacklogTotalMetric, snapshot.BacklogTotal, capturedAtUtc),
|
||||
];
|
||||
}
|
||||
|
||||
private static KpiSample Sample(string metric, double value, DateTime capturedAtUtc) => new()
|
||||
{
|
||||
Source = KpiSources.AuditLog,
|
||||
Metric = metric,
|
||||
Scope = KpiScopes.Global,
|
||||
ScopeKey = null,
|
||||
Value = value,
|
||||
CapturedAtUtc = capturedAtUtc,
|
||||
};
|
||||
}
|
||||
@@ -7,10 +7,12 @@ using ZB.MOM.WW.Audit;
|
||||
using ZB.MOM.WW.Configuration;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Central;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Configuration;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Kpi;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Payload;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Redaction;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Site;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Site.Telemetry;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Kpi;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using IAuditWriter = ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services.IAuditWriter;
|
||||
|
||||
@@ -234,6 +236,18 @@ public static class ServiceCollectionExtensions
|
||||
// provider as a singleton on both site and central paths.
|
||||
sp.GetRequiredService<INodeIdentityProvider>()));
|
||||
|
||||
// M6 (K8) KPI History & Trends: the AuditLog source the central KPI
|
||||
// recorder enumerates each sampling pass to snapshot Global-scope Audit
|
||||
// Log KPIs (volume / error rate / backlog) into the KpiSample history
|
||||
// table. Scoped to match its IAuditLogRepository dependency — a SCOPED
|
||||
// EF Core service; the recorder opens a fresh scope per sampling pass.
|
||||
// TryAdd-Enumerable so multiple sources can register the same interface
|
||||
// (NotificationOutbox + SiteCallAudit + SiteHealth add their own) without
|
||||
// any one composition root clobbering another, and re-registration is a
|
||||
// no-op if AddAuditLog were ever called twice.
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Scoped<IKpiSampleSource, AuditLogKpiSampleSource>());
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user