Files

94 lines
2.5 KiB
C#

using System.Collections.Concurrent;
namespace SafelyYou.Data;
public readonly record struct DeviceId(string Value);
public sealed class KnownDevices
{
private readonly HashSet<DeviceId> _knownDevices;
public KnownDevices(HashSet<DeviceId> knownDevices) => _knownDevices = knownDevices;
public bool Contains(DeviceId deviceId) => _knownDevices.Contains(deviceId);
}
public sealed class DeviceHeartBeatHistory
{
private readonly Lock _lock = new();
public enum AddResult { Added, OutOfSequence }
public long NumberOfHeartBeats { get; private set; }
public DateTimeOffset? FirstHeartBeat { get; private set; }
public DateTimeOffset? LastHeartBeat { get; private set; }
public AddResult Add(DateTimeOffset sentAt)
{
lock (_lock)
{
// Reject heartbeats that arrive before the latest registered time.
// Gaps are fine: uptime can be derived from first, last, and count.
if (LastHeartBeat is null)
{
FirstHeartBeat = sentAt;
LastHeartBeat = sentAt;
NumberOfHeartBeats = 1;
return AddResult.Added;
}
if (sentAt < LastHeartBeat)
return AddResult.OutOfSequence;
// note: does not account for rejecting heartbeats that come between the one minute window.
LastHeartBeat = sentAt;
NumberOfHeartBeats++;
return AddResult.Added;
}
}
}
public sealed class HeartBeatHistories
{
private readonly ConcurrentDictionary<DeviceId, DeviceHeartBeatHistory> _histories = new();
private readonly Lock _lock = new ();
public DeviceHeartBeatHistory.AddResult Add(DeviceId deviceId, DateTimeOffset sentAt)
{
if (!_histories.TryGetValue(deviceId, out var history))
{
lock (_lock)
{
history = new();
_histories[deviceId] = history;
}
}
return history.Add(sentAt);
}
public double Uptime(DeviceId deviceId)
{
if (!_histories.TryGetValue(deviceId, out var history)) return 0;
var heartBeatMinutes =
(history.FirstHeartBeat, history.LastHeartBeat) switch
{
({} first, {} second ) => (second - first).TotalMinutes,
(not null, null) => 1,
(null, _) => 0
};
if (heartBeatMinutes == 0) return 0L;
var uptime = (history.NumberOfHeartBeats / heartBeatMinutes) * 100L;
return uptime;
}
}