88 lines
2.4 KiB
C#
88 lines
2.4 KiB
C#
using Explorer.Contracts;
|
|
|
|
namespace Explorer.Application;
|
|
|
|
public interface IHostActivitySink
|
|
{
|
|
void Record(string category, string message);
|
|
IReadOnlyList<HostActivityEvent> TakeRecent(int max = 120);
|
|
}
|
|
|
|
public sealed class NullHostActivitySink : IHostActivitySink
|
|
{
|
|
public static NullHostActivitySink Instance { get; } = new();
|
|
|
|
public void Record(string category, string message)
|
|
{
|
|
}
|
|
|
|
public IReadOnlyList<HostActivityEvent> TakeRecent(int max = 120) => [];
|
|
}
|
|
|
|
/// <summary>Bounded ring of host activity lines for the live monitor. Cheap to write; snapshot is a copy.</summary>
|
|
public sealed class HostActivityLog : IHostActivitySink
|
|
{
|
|
private readonly object _gate = new();
|
|
private readonly HostActivityEvent[] _ring;
|
|
private int _next;
|
|
private int _count;
|
|
private string? _lastKey;
|
|
private DateTimeOffset _lastUtc;
|
|
|
|
public HostActivityLog(int capacity = 48)
|
|
{
|
|
_ring = new HostActivityEvent[Math.Clamp(capacity, 32, 200)];
|
|
}
|
|
|
|
public void Record(string category, string message)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(message))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var cat = string.IsNullOrWhiteSpace(category) ? "Host" : category.Trim();
|
|
var msg = message.Trim();
|
|
var key = cat + "\u001f" + msg;
|
|
var now = DateTimeOffset.UtcNow;
|
|
lock (_gate)
|
|
{
|
|
if (key == _lastKey && (now - _lastUtc) < TimeSpan.FromMilliseconds(750))
|
|
{
|
|
return;
|
|
}
|
|
|
|
_lastKey = key;
|
|
_lastUtc = now;
|
|
_ring[_next] = new HostActivityEvent { Utc = now, Category = cat, Message = msg };
|
|
_next = (_next + 1) % _ring.Length;
|
|
if (_count < _ring.Length)
|
|
{
|
|
_count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
public IReadOnlyList<HostActivityEvent> TakeRecent(int max = 120)
|
|
{
|
|
max = Math.Clamp(max, 1, _ring.Length);
|
|
lock (_gate)
|
|
{
|
|
var take = Math.Min(max, _count);
|
|
if (take == 0)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
var result = new HostActivityEvent[take];
|
|
var start = (_next - take + _ring.Length) % _ring.Length;
|
|
for (var i = 0; i < take; i++)
|
|
{
|
|
result[i] = _ring[(start + i) % _ring.Length];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
}
|