From 02c8a103aa93980a56ff43f6329129e058f3fd0a Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter <nikolaus.ehrenfeuchter@unibas.ch> Date: Tue, 24 Jul 2018 17:59:31 +0200 Subject: [PATCH] Use an abstract class "MonitorBase" for load monitors --- ATxCommon/ATxCommon.csproj | 1 + ATxCommon/Monitoring/Cpu.cs | 229 ++------------------------ ATxCommon/Monitoring/MonitorBase.cs | 232 +++++++++++++++++++++++++++ ATxCommon/Monitoring/PhysicalDisk.cs | 229 ++------------------------ 4 files changed, 255 insertions(+), 436 deletions(-) create mode 100644 ATxCommon/Monitoring/MonitorBase.cs diff --git a/ATxCommon/ATxCommon.csproj b/ATxCommon/ATxCommon.csproj index c006aed..7e3dffd 100644 --- a/ATxCommon/ATxCommon.csproj +++ b/ATxCommon/ATxCommon.csproj @@ -48,6 +48,7 @@ <Compile Include="Conv.cs" /> <Compile Include="FsUtils.cs" /> <Compile Include="Monitoring\Cpu.cs" /> + <Compile Include="Monitoring\MonitorBase.cs" /> <Compile Include="Monitoring\PhysicalDisk.cs" /> <Compile Include="NLog\RateLimitWrapper.cs" /> <Compile Include="Serializables\DriveToCheck.cs" /> diff --git a/ATxCommon/Monitoring/Cpu.cs b/ATxCommon/Monitoring/Cpu.cs index 5500d22..fec80c3 100644 --- a/ATxCommon/Monitoring/Cpu.cs +++ b/ATxCommon/Monitoring/Cpu.cs @@ -1,226 +1,23 @@ -using System; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Timers; -using NLog; -using Timer = System.Timers.Timer; - -namespace ATxCommon.Monitoring +namespace ATxCommon.Monitoring { + /// <inheritdoc /> /// <summary> - /// CPU load monitoring class, constantly checking the current load at the given <see - /// cref="Interval"/> in a separate (timer-based) thread. - /// - /// The load is determined using a <see cref="PerformanceCounter"/>, and is compared against - /// a configurable <see cref="Limit"/>. If the load changes from below the limit to above, a - /// <see cref="LoadAboveLimit"/> event will be raised. If the load has been above the limit - /// and is then dropping below, an <see cref="OnLoadBelowLimit"/> event will be raised as soon - /// as a given number of consecutive measurements (defined via <see cref="Probation"/>) were - /// found to be below the limit. + /// Monitoring class for CPU, using the idle/non-idle time fraction for measuring the load. /// </summary> - public class Cpu + public class Cpu : MonitorBase { - private static readonly Logger Log = LogManager.GetCurrentClassLogger(); - - /// <summary> - /// The generic event handler delegate for CPU events. - /// </summary> - public delegate void EventHandler(object sender, EventArgs e); - - /// <summary> - /// Event raised when the CPU load exceeds the configured limit for any measurement. - /// </summary> - public event EventHandler LoadAboveLimit; - - /// <summary> - /// Event raised when the CPU load is below the configured limit for at least four - /// consecutive measurements after having exceeded this limit before. - /// </summary> - public event EventHandler LoadBelowLimit; - - private readonly Timer _monitoringTimer; - private readonly PerformanceCounter _cpuCounter; - private readonly float[] _loadReadings = {0F, 0F, 0F, 0F}; - - private int _interval; - private int _limit; - private int _behaving; - private int _probation; - - /// <summary> - /// Performance counter category name, <see cref="PerformanceCounter"/> for details. - /// </summary> - private const string Category = "Processor"; - - /// <summary> - /// Description string to be used in log messages. - /// </summary> - private readonly string _description; - - #region properties - - /// <summary> - /// Current CPU load (usage percentage over all cores), averaged of the last four readings. - /// </summary> - /// <returns>The average CPU load from the last four readings.</returns> - public float Load { get; private set; } - - /// <summary> - /// Flag representing whether the load is considered to be high or low. - /// </summary> - public bool HighLoad { get; private set; } - - /// <summary> - /// How often (in ms) to check the CPU load. - /// </summary> - public int Interval { - get => _interval; - set { - _interval = value; - _monitoringTimer.Interval = value; - Log.Debug("{0} monitoring interval: {1}ms", _description, _interval); - } - } - - /// <summary> - /// Upper limit of CPU load (usage in % over all cores) before it is classified as "high". - /// </summary> - public int Limit { - get => _limit; - set { - _limit = value; - Log.Debug("{0} monitoring limit: {1}", _description, _limit); - } - } - - /// <summary> - /// Number of cycles where the CPU load value has to be below the limit before it is - /// classified as "low" again. - /// </summary> - public int Probation { - get => _probation; - set { - _probation = value; - Log.Debug("{0} monitoring probation cycles when violating limit: {1}", - _description, _probation); - } - } - - /// <summary> - /// Indicating whether the CPU load monitoring is active. - /// </summary> - public bool Enabled { - get => _monitoringTimer.Enabled; - set { - Log.Debug("{0} - {1} monitoring.", _description, value ? "enabling" : "disabling"); - _monitoringTimer.Enabled = value; - } - } - - /// <summary> - /// Log level to use for reporting current performance readings. - /// </summary> - public LogLevel LogPerformanceReadings { get; set; } = LogLevel.Trace; - - #endregion - - + /// <inheritdoc /> + protected sealed override string Category { get; set; } = "Processor"; + /// <inheritdoc /> /// <summary> /// Create performance counter and initialize it. /// </summary> - public Cpu(string counterName = "% Processor Time") { - _interval = 250; - _limit = 25; - _probation = 40; - Log.Info("Initializing {0} performance monitoring for [{1}].", Category, counterName); - // assemble the description string to be used in messages: - _description = $"{Category} {counterName}"; - try { - Log.Debug("{0} monitoring initializing PerformanceCounter (takes one second)...", Category); - _cpuCounter = new PerformanceCounter(Category, counterName, "_Total"); - var curLoad = _cpuCounter.NextValue(); - Log.Debug("{0} initial value: {1:0.0}", _description, curLoad); - Thread.Sleep(1000); - curLoad = _cpuCounter.NextValue(); - Log.Debug("{0} current value: {1:0.0}", _description, curLoad); - // now initialize the load state: - HighLoad = curLoad > _limit; - _monitoringTimer = new Timer(_interval); - _monitoringTimer.Elapsed += UpdateCpuLoad; - } - catch (Exception) { - Log.Error("{0} monitoring initialization failed!", Category); - throw; - } - - Log.Debug("{0} monitoring initialization completed.", _description); - } - - /// <summary> - /// Check current CPU load, update the history of readings and trigger the corresponding - /// events if the required criteria are met. - /// </summary> - private void UpdateCpuLoad(object sender, ElapsedEventArgs e) { - _monitoringTimer.Enabled = false; - try { - // ConstrainedCopy seems to be the most efficient approach to shift the array: - Array.ConstrainedCopy(_loadReadings, 1, _loadReadings, 0, 3); - _loadReadings[3] = _cpuCounter.NextValue(); - Load = _loadReadings.Average(); - if (_loadReadings[3] > _limit) { - if (_behaving > _probation) { - // this means the load was considered as "low" before, so raise an event: - OnLoadAboveLimit(); - Log.Trace("{0} ({1:0.0}) violating limit ({2})!", - _description, _loadReadings[3], _limit); - } else if (_behaving > 0) { - // this means we were still in probation, so no need to trigger again... - Log.Trace("{0}: resetting behaving counter (was {1}).", - _description, _behaving); - } - _behaving = 0; - } else { - _behaving++; - if (_behaving == _probation) { - Log.Trace("{0} below limit for {1} cycles, passing probation!", - _description, _probation); - OnLoadBelowLimit(); - } else if (_behaving > _probation) { - Log.Trace("{0} behaving well since {1} cycles.", - _description, _behaving); - } else if (_behaving < 0) { - Log.Info("{0}: integer wrap around happened, resetting probation " + - "counter (no reason to worry).", _description); - _behaving = _probation + 1; - } - } - } - catch (Exception ex) { - Log.Error("Updating {0} counters failed: {1}", _description, ex.Message); - } - finally { - _monitoringTimer.Enabled = true; - } - Log.Log(LogPerformanceReadings, "{0}: {1:0.0} {2}", _description, - _loadReadings[3], _loadReadings[3] < Limit ? " [" + _behaving + "]" : ""); - } - - /// <summary> - /// Raise the "LoadAboveLimit" event. - /// </summary> - protected virtual void OnLoadAboveLimit() { - HighLoad = true; - LoadAboveLimit?.Invoke(this, EventArgs.Empty); - } - - /// <summary> - /// Raise the "LoadBelowLimit" event. - /// </summary> - protected virtual void OnLoadBelowLimit() { - HighLoad = false; - LoadBelowLimit?.Invoke(this, EventArgs.Empty); + /// <param name="counterName">The counter to use for the monitoring, default is the + /// overall "% Processor Time". + /// </param> + public Cpu(string counterName = "% Processor Time") + : base(counterName) { } } -} +} \ No newline at end of file diff --git a/ATxCommon/Monitoring/MonitorBase.cs b/ATxCommon/Monitoring/MonitorBase.cs new file mode 100644 index 0000000..cbdccca --- /dev/null +++ b/ATxCommon/Monitoring/MonitorBase.cs @@ -0,0 +1,232 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Timers; +using NLog; +using Timer = System.Timers.Timer; + +namespace ATxCommon.Monitoring +{ + /// <summary> + /// Abstract load monitoring class, constantly checking the load at the given <see + /// cref="Interval"/> in a separate (timer-based) thread. + /// + /// The load (depending on the implementation in the derived class) is determined using a <see + /// cref="PerformanceCounter"/>, and is compared against a configurable <see cref="Limit"/>. If + /// the load changes from below the limit to above, a <see cref="LoadAboveLimit"/> event will + /// be raised. If the load has been above the limit and is then dropping below, an <see + /// cref="OnLoadBelowLimit"/> event will be raised as soon as a given number of consecutive + /// load measurements (defined via <see cref="Probation"/>) were found to be below the limit. + /// </summary> + public abstract class MonitorBase + { + protected static readonly Logger Log = LogManager.GetCurrentClassLogger(); + + /// <summary> + /// The generic event handler delegate for load monitoring events. + /// </summary> + public delegate void EventHandler(object sender, EventArgs e); + + /// <summary> + /// Event raised when the load exceeds the limit for any (i.e. single!) measurement. + /// </summary> + public event EventHandler LoadAboveLimit; + + /// <summary> + /// Event raised when the load is below the configured limit for at least the number of + /// consecutive measurements configured in <see cref="Probation"/> after having exceeded + /// this limit before. + /// </summary> + public event EventHandler LoadBelowLimit; + + protected readonly Timer MonitoringTimer; + protected readonly PerformanceCounter PerfCounter; + protected readonly float[] LoadReadings = {0F, 0F, 0F, 0F}; + + private int _interval; + private int _behaving; + private int _probation; + + private float _limit; + + /// <summary> + /// Description string to be used in log messages. + /// </summary> + private readonly string _description; + + + #region properties + + /// <summary> + /// Name of the performance counter category, see also <see cref="PerformanceCounter"/>. + /// </summary> + protected abstract string Category { get; set; } + + /// <summary> + /// Current load, averaged of the last four readings. + /// </summary> + /// <returns>The average load from the last four readings.</returns> + public float Load { get; private set; } + + /// <summary> + /// Flag representing whether the load is considered to be high or low. + /// </summary> + public bool HighLoad { get; private set; } + + /// <summary> + /// Time interval (in ms) after which to update the current load measurement. + /// </summary> + public int Interval { + get => _interval; + set { + _interval = value; + MonitoringTimer.Interval = value; + Log.Debug("{0} monitoring interval: {1}ms", _description, _interval); + } + } + + /// <summary> + /// Upper limit of the load before it is classified as "high". + /// </summary> + public float Limit { + get => _limit; + set { + _limit = value; + Log.Debug("{0} monitoring limit: {1:0.000}", _description, _limit); + } + } + + /// <summary> + /// Number of cycles where the load value has to be below the limit before it is + /// classified as "low" again. + /// </summary> + public int Probation { + get => _probation; + set { + _probation = value; + Log.Debug("{0} monitoring probation cycles when violating limit: {1}", + _description, _probation); + } + } + + /// <summary> + /// Indicating whether this load monitor instance is active. + /// </summary> + public bool Enabled { + get => MonitoringTimer.Enabled; + set { + Log.Debug("{0} - {1} monitoring.", _description, value ? "enabling" : "disabling"); + MonitoringTimer.Enabled = value; + } + } + + /// <summary> + /// Log level to use for reporting current performance readings, default = Trace. + /// </summary> + public LogLevel LogPerformanceReadings { get; set; } = LogLevel.Trace; + + #endregion + + + /// <summary> + /// Create performance counter and initialize it. + /// </summary> + /// <param name="counterName">The counter to use for the monitoring, depends on the + /// category used in the derived class. + /// </param> + protected MonitorBase(string counterName) { + _interval = 250; + _limit = 0.5F; + _probation = 40; + Log.Info("Initializing {0} PerformanceCounter for [{1}].", Category, counterName); + // assemble the description string to be used in messages: + _description = $"{Category} {counterName}"; + try { + PerfCounter = new PerformanceCounter(Category, counterName, "_Total"); + var curLoad = PerfCounter.NextValue(); + Log.Debug("{0} initial value: {1:0.000}", _description, curLoad); + /* this initialization might be necessary for "Processor" counters, so we just + * temporarily disable those calls: + Thread.Sleep(1000); + curLoad = _perfCounter.NextValue(); + Log.Debug("{0} current value: {1:0.000}", _description, curLoad); + */ + // initialize the load state as high, so we have to pass probation at least once: + HighLoad = true; + MonitoringTimer = new Timer(_interval); + MonitoringTimer.Elapsed += UpdateLoadReadings; + } + catch (Exception) { + Log.Error("{0} monitoring initialization failed!", Category); + throw; + } + + Log.Debug("{0} monitoring initialization completed.", _description); + } + + /// <summary> + /// Check current load value, update the history of readings and trigger the corresponding + /// events if the required criteria are met. + /// </summary> + private void UpdateLoadReadings(object sender, ElapsedEventArgs e) { + MonitoringTimer.Enabled = false; + try { + // ConstrainedCopy seems to be the most efficient approach to shift the array: + Array.ConstrainedCopy(LoadReadings, 1, LoadReadings, 0, 3); + LoadReadings[3] = PerfCounter.NextValue(); + Load = LoadReadings.Average(); + if (LoadReadings[3] > _limit) { + if (_behaving > _probation) { + // this means the load was considered as "low" before, so raise an event: + OnLoadAboveLimit(); + Log.Trace("{0} ({1:0.00}) violating limit ({2})!", + _description, LoadReadings[3], _limit); + } else if (_behaving > 0) { + // this means we were still in probation, so no need to trigger again... + Log.Trace("{0}: resetting behaving counter (was {1}).", + _description, _behaving); + } + _behaving = 0; + } else { + _behaving++; + if (_behaving == _probation) { + Log.Trace("{0} below limit for {1} cycles, passing probation!", + _description, _probation); + OnLoadBelowLimit(); + } else if (_behaving > _probation) { + Log.Trace("{0} behaving well since {1} cycles.", + _description, _behaving); + } else if (_behaving < 0) { + Log.Info("{0}: integer wrap around happened, resetting probation " + + "counter (no reason to worry).", _description); + _behaving = _probation + 1; + } + } + } + catch (Exception ex) { + Log.Error("Updating {0} counters failed: {1}", _description, ex.Message); + } + finally { + MonitoringTimer.Enabled = true; + } + Log.Log(LogPerformanceReadings, "{0}: {1:0.000} {2}", _description, + LoadReadings[3], LoadReadings[3] < Limit ? " [" + _behaving + "]" : ""); + } + + /// <summary> + /// Raise the "LoadAboveLimit" event. + /// </summary> + protected virtual void OnLoadAboveLimit() { + HighLoad = true; + LoadAboveLimit?.Invoke(this, EventArgs.Empty); + } + + /// <summary> + /// Raise the "LoadBelowLimit" event. + /// </summary> + protected virtual void OnLoadBelowLimit() { + HighLoad = false; + LoadBelowLimit?.Invoke(this, EventArgs.Empty); + } + } +} diff --git a/ATxCommon/Monitoring/PhysicalDisk.cs b/ATxCommon/Monitoring/PhysicalDisk.cs index 115766f..db32759 100644 --- a/ATxCommon/Monitoring/PhysicalDisk.cs +++ b/ATxCommon/Monitoring/PhysicalDisk.cs @@ -1,135 +1,15 @@ -using System; -using System.Diagnostics; -using System.Linq; -// using System.Threading; -using System.Timers; -using NLog; -using Timer = System.Timers.Timer; - -namespace ATxCommon.Monitoring +namespace ATxCommon.Monitoring { + /// <inheritdoc /> /// <summary> - /// Load monitoring class for physical disks, constantly checking the queue length at the - /// given <see cref="Interval"/> in a separate (timer-based) thread. - /// - /// The load (=queue length) is determined using a <see cref="PerformanceCounter"/>, and is - /// compared against a configurable <see cref="Limit"/>. If the load changes from below the - /// limit to above, a <see cref="LoadAboveLimit"/> event will be raised. If the load has been - /// above the limit and is then dropping below, an <see cref="OnLoadBelowLimit"/> event will - /// be raised as soon as a given number of consecutive load measurements (defined via <see - /// cref="Probation"/>) were found to be below the limit. + /// Monitoring class for disk I/O, using the disk queue length for measuring the load. /// </summary> - public class PhysicalDisk + public class PhysicalDisk : MonitorBase { - private static readonly Logger Log = LogManager.GetCurrentClassLogger(); - - /// <summary> - /// The generic event handler delegate for PhysicalDisk events. - /// </summary> - public delegate void EventHandler(object sender, EventArgs e); - - /// <summary> - /// Event raised when the PhysicalDisk load exceeds the limit for any measurement. - /// </summary> - public event EventHandler LoadAboveLimit; - - /// <summary> - /// Event raised when the PhysicalDisk load is below the configured limit for at least - /// the number of consecutive measurements configured in <see cref="Probation"/> after - /// having exceeded this limit before. - /// </summary> - public event EventHandler LoadBelowLimit; - - private readonly Timer _monitoringTimer; - private readonly PerformanceCounter _diskQueueLength; - private readonly float[] _loadReadings = {0F, 0F, 0F, 0F}; - - private int _interval; - private int _behaving; - private int _probation; - - private float _limit; - - /// <summary> - /// Name of the performance counter category, <see cref="PerformanceCounter"/>. - /// </summary> - private const string Category = "PhysicalDisk"; - - /// <summary> - /// Description string to be used in log messages. - /// </summary> - private readonly string _description; - - - #region properties - - /// <summary> - /// Current PhysicalDisk Queue Length, averaged of the last four readings. - /// </summary> - /// <returns>The average PhysicalDisk Queue Length from the last four readings.</returns> - public float Load { get; private set; } - - /// <summary> - /// Flag representing whether the load is considered to be high or low. - /// </summary> - public bool HighLoad { get; private set; } - - /// <summary> - /// How often (in ms) to check the PhysicalDisk Queue Length. - /// </summary> - public int Interval { - get => _interval; - set { - _interval = value; - _monitoringTimer.Interval = value; - Log.Debug("{0} monitoring interval: {1}ms", _description, _interval); - } - } - - /// <summary> - /// Upper limit of PhysicalDisk load before it is classified as "high". - /// </summary> - public float Limit { - get => _limit; - set { - _limit = value; - Log.Debug("{0} monitoring limit: {1:0.000}", _description, _limit); - } - } - - /// <summary> - /// Number of cycles where the PhysicalDisk load value has to be below the limit before it is - /// classified as "low" again. - /// </summary> - public int Probation { - get => _probation; - set { - _probation = value; - Log.Debug("{0} monitoring probation cycles when violating limit: {1}", - _description, _probation); - } - } - - /// <summary> - /// Indicating whether the PhysicalDisk load monitoring is active. - /// </summary> - public bool Enabled { - get => _monitoringTimer.Enabled; - set { - Log.Debug("{0} - {1} monitoring.", _description, value ? "enabling" : "disabling"); - _monitoringTimer.Enabled = value; - } - } - - /// <summary> - /// Log level to use for reporting current performance readings, default = Trace. - /// </summary> - public LogLevel LogPerformanceReadings { get; set; } = LogLevel.Trace; - - #endregion - - + /// <inheritdoc /> + protected sealed override string Category { get; set; } = "PhysicalDisk"; + /// <inheritdoc /> /// <summary> /// Create performance counter and initialize it. /// </summary> @@ -137,99 +17,8 @@ namespace ATxCommon.Monitoring /// overall "Avg. Disk Queue Length", other reasonable options are the corresponding read /// or write queues ("Avg. Disk Read Queue Length" and "Avg. Disk Write Queue Length"). /// </param> - public PhysicalDisk(string counterName = "Avg. Disk Queue Length") { - _interval = 250; - _limit = 0.5F; - _probation = 40; - Log.Info("Initializing {0} PerformanceCounter for [{1}].", Category, counterName); - // assemble the description string to be used in messages: - _description = $"{Category} {counterName}"; - try { - _diskQueueLength = new PerformanceCounter(Category, counterName, "_Total"); - var curLoad = _diskQueueLength.NextValue(); - Log.Debug("{0} initial value: {1:0.000}", _description, curLoad); - /* this initialization doesn't seem to be necessary for PhysicalDisk, so we just - * disable those calls for now: - Thread.Sleep(1000); - curLoad = _diskQueueLength.NextValue(); - Log.Debug("{0} current value: {1:0.000}", _description, curLoad); - */ - // now initialize the load state: - HighLoad = curLoad > _limit; - _monitoringTimer = new Timer(_interval); - _monitoringTimer.Elapsed += UpdatePhysicalDiskLoad; - } - catch (Exception) { - Log.Error("{0} monitoring initialization failed!", Category); - throw; - } - - Log.Debug("{0} monitoring initialization completed.", _description); - } - - /// <summary> - /// Check current PhysicalDisk queue length, update the history of readings and trigger - /// the corresponding events if the required criteria are met. - /// </summary> - private void UpdatePhysicalDiskLoad(object sender, ElapsedEventArgs e) { - _monitoringTimer.Enabled = false; - try { - // ConstrainedCopy seems to be the most efficient approach to shift the array: - Array.ConstrainedCopy(_loadReadings, 1, _loadReadings, 0, 3); - _loadReadings[3] = _diskQueueLength.NextValue(); - Load = _loadReadings.Average(); - if (_loadReadings[3] > _limit) { - if (_behaving > _probation) { - // this means the load was considered as "low" before, so raise an event: - OnLoadAboveLimit(); - Log.Trace("{0} ({1:0.00}) violating limit ({2})!", - _description, _loadReadings[3], _limit); - } else if (_behaving > 0) { - // this means we were still in probation, so no need to trigger again... - Log.Trace("{0}: resetting behaving counter (was {1}).", - _description, _behaving); - } - _behaving = 0; - } else { - _behaving++; - if (_behaving == _probation) { - Log.Trace("{0} below limit for {1} cycles, passing probation!", - _description, _probation); - OnLoadBelowLimit(); - } else if (_behaving > _probation) { - Log.Trace("{0} behaving well since {1} cycles.", - _description, _behaving); - } else if (_behaving < 0) { - Log.Info("{0}: integer wrap around happened, resetting probation " + - "counter (no reason to worry).", _description); - _behaving = _probation + 1; - } - } - } - catch (Exception ex) { - Log.Error("Updating {0} counters failed: {1}", _description, ex.Message); - } - finally { - _monitoringTimer.Enabled = true; - } - Log.Log(LogPerformanceReadings, "{0}: {1:0.000} {2}", _description, - _loadReadings[3], _loadReadings[3] < Limit ? " [" + _behaving + "]" : ""); - } - - /// <summary> - /// Raise the "LoadAboveLimit" event. - /// </summary> - protected virtual void OnLoadAboveLimit() { - HighLoad = true; - LoadAboveLimit?.Invoke(this, EventArgs.Empty); - } - - /// <summary> - /// Raise the "LoadBelowLimit" event. - /// </summary> - protected virtual void OnLoadBelowLimit() { - HighLoad = false; - LoadBelowLimit?.Invoke(this, EventArgs.Empty); + public PhysicalDisk(string counterName = "Avg. Disk Queue Length") + : base(counterName) { } } } \ No newline at end of file -- GitLab