Skip to main content
deleted 60 characters in body
Source Link
Dmitry Nogin
  • 6.1k
  • 3
  • 20
  • 40

Fake configuration comes from this class (try to change it its content dynamically):

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;
}

Fake configuration comes from this class (try to change it its content dynamically):

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;
}

Fake configuration comes from this class (try to change its content dynamically):

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)) 
            .Subscribe(Notifier.Notify);
    }
    
    public void Dispose()
    {
        _sync.Dispose();
        _notify.Dispose();
    }

    IDisposable _sync;
    IDisposable _notify;
}
Source Link
Dmitry Nogin
  • 6.1k
  • 3
  • 20
  • 40

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.