remove the total history tracking and the priority queue for just tracking the variables.
This commit is contained in:
+38
-54
@@ -1,6 +1,4 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using SafelyYou.Config;
|
|
||||||
|
|
||||||
namespace SafelyYou;
|
namespace SafelyYou;
|
||||||
|
|
||||||
@@ -16,82 +14,68 @@ public sealed class KnownDevices
|
|||||||
public bool Contains(DeviceId deviceId) => _knownDevices.Contains(deviceId);
|
public bool Contains(DeviceId deviceId) => _knownDevices.Contains(deviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly record struct HeartBeatRange(DateTimeOffset Start, DateTimeOffset? End);
|
|
||||||
|
|
||||||
public sealed class DeviceHeartBeatHistory
|
public sealed class DeviceHeartBeatHistory
|
||||||
{
|
{
|
||||||
private readonly HeartBeatHistoryConfig _config;
|
|
||||||
|
|
||||||
public enum AddResult { Added, OutOfSequence }
|
public enum AddResult { Added, OutOfSequence }
|
||||||
|
|
||||||
public DeviceHeartBeatHistory(HeartBeatHistoryConfig config) => _config = config;
|
public long NumberOfHeartBeats { get; private set; }
|
||||||
|
public DateTimeOffset? FirstHeartBeat { get; private set; }
|
||||||
|
public DateTimeOffset? LastHeartBeat { get; private set; }
|
||||||
|
|
||||||
private readonly PriorityQueue<HeartBeatRange, DateTimeOffset> _histories =
|
|
||||||
// lowest priority is first, but this allows the highest date to be dequeued first
|
|
||||||
new PriorityQueue<HeartBeatRange, DateTimeOffset>(new InverseDateTimeComparer());
|
|
||||||
|
|
||||||
private sealed class InverseDateTimeComparer : IComparer<DateTimeOffset>
|
|
||||||
{
|
|
||||||
public int Compare(DateTimeOffset x, DateTimeOffset y) => -(x.CompareTo(y));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Returns Added, needed to be used as an expression</summary>
|
|
||||||
private static AddResult Replace(PriorityQueue<HeartBeatRange, DateTimeOffset> histories, HeartBeatRange updated)
|
|
||||||
{
|
|
||||||
histories.DequeueEnqueue(updated, updated.Start);
|
|
||||||
return AddResult.Added;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Returns Added, needed to be used as an expression</summary>
|
|
||||||
private static AddResult EnqueueNew(PriorityQueue<HeartBeatRange, DateTimeOffset> histories, DateTimeOffset sentAt)
|
|
||||||
{
|
|
||||||
histories.Enqueue(new HeartBeatRange(sentAt, null), sentAt);
|
|
||||||
return AddResult.Added;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AddResult Add(DateTimeOffset sentAt)
|
public AddResult Add(DateTimeOffset sentAt)
|
||||||
{
|
{
|
||||||
// check the latest first, assuming things are in order
|
// Reject heartbeats that arrive before the latest registered time.
|
||||||
// if it's "in the past" and doesn't make sense, return an error
|
// Gaps are fine: uptime can be derived from first, last, and count.
|
||||||
|
if (LastHeartBeat is null)
|
||||||
// histories that are adjacent are combined, if there's more than a one minute gap (what about a margin of error?)
|
|
||||||
// then create a new entry. The margin is coming from the config. I've set it to 1 second.
|
|
||||||
|
|
||||||
if (!_histories.TryPeek(out var last, out _))
|
|
||||||
return EnqueueNew(_histories, sentAt);
|
|
||||||
|
|
||||||
// last registered heartbeat is End if set, otherwise Start
|
|
||||||
return (sentAt, last.Start, last.End) switch
|
|
||||||
{
|
{
|
||||||
var (sent, start, _) when sent < start => AddResult.OutOfSequence,
|
FirstHeartBeat = sentAt;
|
||||||
(var sent, _, { } end) when sent < end => AddResult.OutOfSequence,
|
LastHeartBeat = sentAt;
|
||||||
// no End yet: extend if within Window of Start
|
NumberOfHeartBeats = 1;
|
||||||
(var sent, var start, null) when sent <= start + _config.Window => Replace(_histories, last with { End = sentAt }),
|
return AddResult.Added;
|
||||||
// End set: extend if within Window of End
|
}
|
||||||
(var sent, _, { } end) when sent <= end + _config.Window => Replace(_histories, last with { End = sentAt }),
|
|
||||||
// gap larger than Window: start a new range
|
if (sentAt < LastHeartBeat)
|
||||||
_ => EnqueueNew(_histories, sentAt),
|
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
|
public sealed class HeartBeatHistories
|
||||||
{
|
{
|
||||||
private readonly HeartBeatHistoryConfig _config;
|
|
||||||
private readonly ConcurrentDictionary<DeviceId, DeviceHeartBeatHistory> _histories = new();
|
private readonly ConcurrentDictionary<DeviceId, DeviceHeartBeatHistory> _histories = new();
|
||||||
|
|
||||||
public HeartBeatHistories(HeartBeatHistoryConfig config) => _config = config;
|
|
||||||
|
|
||||||
public DeviceHeartBeatHistory.AddResult Add(DeviceId deviceId, DateTimeOffset sentAt)
|
public DeviceHeartBeatHistory.AddResult Add(DeviceId deviceId, DateTimeOffset sentAt)
|
||||||
{
|
{
|
||||||
if (!_histories.TryGetValue(deviceId, out var history))
|
if (!_histories.TryGetValue(deviceId, out var history))
|
||||||
{
|
{
|
||||||
history = new(_config);
|
history = new();
|
||||||
_histories[deviceId] = history;
|
_histories[deviceId] = history;
|
||||||
}
|
}
|
||||||
|
|
||||||
return history.Add(sentAt);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user