OK, see below, it fetches configuration updates from the db. It is going to be a lot of code :)
First of all, we need a helper infrastructure base class to help compare configurations:
abstract class ValueObject<T> : IEquatable<ValueObject<T>>
where T : ValueObject<T>
{
protected abstract IEnumerable<object> EqualityCheckAttributes { get; }
public override int GetHashCode() =>
EqualityCheckAttributes
.Aggregate(0, (hash, a) => hash = hash * 31 + (a?.GetHashCode() ?? 0));
public override bool Equals(object obj) =>
Equals(obj as ValueObject<T>);
public bool Equals(ValueObject<T> other) =>
other != null &&
EqualityCheckAttributes.SequenceEqual(other.EqualityCheckAttributes);
public static bool operator==(ValueObject<T> left, ValueObject<T> right) =>
Equals(left, right);
public static bool operator !=(ValueObject<T> left, ValueObject<T> right) =>
!Equals(left, right);
}
Infrastructure interfaces:
interface INotifier
{
void Notify(bool critical);
}
interface IPerformanceCounter
{
double Value { get; }
}
interface IConfigurationReader
{
Configuration Read();
}
Where Configuration
is:
class Configuration : ValueObject<Configuration>
{
public Configuration(Intervals intervals, Thresholds thresholds)
{
Intervals = intervals;
Thresholds = thresholds;
}
public Intervals Intervals { get; set; }
public Thresholds Thresholds { get; }
protected override IEnumerable<object> EqualityCheckAttributes =>
new object[] { Intervals, Thresholds };
}
with Intervals
:
class Intervals : ValueObject<Intervals>
{
public Intervals(TimeSpan configuration, TimeSpan measurement, TimeSpan notification, TimeSpan criticalNotification)
{
Configuration = configuration;
Measurement = measurement;
Notification = notification;
CriticalNotification = criticalNotification;
}
public TimeSpan Configuration { get; }
public TimeSpan Measurement { get; }
public TimeSpan Notification { get; }
public TimeSpan CriticalNotification { get; }
protected override IEnumerable<object> EqualityCheckAttributes =>
new object[] { Configuration, Measurement, Notification, CriticalNotification };
}
and Thresholds
:
class Thresholds : ValueObject<Thresholds>
{
public Thresholds(double notificationLoad, double criticalLoad)
{
NotificationLoad = notificationLoad;
CriticalLoad = criticalLoad;
}
public double NotificationLoad { get; }
public double CriticalLoad { get; }
protected override IEnumerable<object> EqualityCheckAttributes =>
new object[] { NotificationLoad, CriticalLoad };
}
Nothing really interesting so far - we just defined equality logic for configurations to help detect changes.
Here are some fake implementations to make test work:
class Notifier : INotifier
{
public void Notify(bool critical) => Console.WriteLine(critical);
}
class CpuCounter : IPerformanceCounter
{
public double Value => Math.Sin(DateTime.Now.Millisecond / 1000.0);
}
Fake configuration comes from this class (try to change it its content dynamically):
class ConfigurationReader : IConfigurationReader
{
public Configuration Read() => // fake implementation
new Configuration(
new Intervals(
configuration: TimeSpan.FromSeconds(10),
measurement: TimeSpan.FromSeconds(0.01),
notification: TimeSpan.FromSeconds(2),
criticalNotification: TimeSpan.FromSeconds(1)),
new Thresholds(
notificationLoad: 0.3,
criticalLoad: 0.4));
}
Here comes core RX
stuff. I would say that it is really compact and maintainable realization:
class HardwareMonitor : IDisposable
{
readonly IConfigurationReader ConfigurationReader = new ConfigurationReader();
readonly IPerformanceCounter PerformanceCounter = new CpuCounter();
readonly INotifier Notifier = new Notifier();
public HardwareMonitor()
{
Setup(ConfigurationReader.Read());
}
void Setup(Configuration c)
{
_sync?.Dispose();
_notify?.Dispose();
_sync = Observable
.Interval(c.Intervals.Configuration)
.Select(i => ConfigurationReader.Read())
.DistinctUntilChanged()
.Subscribe(Setup);
var alarms = Observable
.Interval(c.Intervals.Measurement)
.Select(i => PerformanceCounter.Value)
.Where(load => load > c.Thresholds.NotificationLoad)
.Select(load => load > c.Thresholds.CriticalLoad);
_notify = Observable.Merge(
alarms
.Where(critical => critical)
.Sample(c.Intervals.Notification),
alarms
.Where(critical => !critical)
.Sample(c.Intervals.CriticalNotification)) // allow non critical notification no more often then ...
.Subscribe(Notifier.Notify);
}
public void Dispose()
{
_sync.Dispose();
_notify.Dispose();
}
IDisposable _sync;
IDisposable _notify;
}
Feel free to let me know if you have questions.