I have written a windows service that I am currently not that happy with as I am facing design issues (first windows service).
The aim of this service is to monitor CPU usage and send an alert when it reaches a threshold (coming from the DB).
So the user has set a threshold of > 60% CPU usage for 10 Seconds. Then the code below will need to check the CPU usage every second and if it's > 60% for 10 seconds send out an alert. Then send an alert once it's < 60%.
This is my current implementation:
Main Program
private static void Main(string[] args)
{
HostFactory.Run(
x =>
{
x.StartAutomatically();
x.Service<HardwareMonitor>(
s =>
{
s.ConstructUsing(monitor => new HardwareMonitor());
s.WhenStarted(tc => tc.Start());
s.WhenStopped(tc => tc.Stop());
});
x.RunAsLocalSystem();
x.EnableServiceRecovery(
r =>
{
r.RestartService(1);
});
x.SetDescription("NZBDash Monitor");
x.SetDisplayName("NZBDash Monitor");
x.SetServiceName("NZBDashMonitor");
x.UseNLog();
});
}
As you can see I am using Topshelf to schedule the service.
Hardware Monitor
public class HardwareMonitor
{
private static void TaskManagerUnobservedTaskException(TaskExceptionInformation sender, UnhandledExceptionEventArgs e)
{
Console.WriteLine("An error happened with a scheduled task: " + e.ExceptionObject);
}
public void Start()
{
TaskManager.UnobservedTaskException += TaskManagerUnobservedTaskException;
TaskManager.TaskFactory = new NinjectTaskFactory(ServiceKernel.GetKernel());
TaskManager.Initialize(new TaskRegistry());
}
public void Stop()
{
TaskManager.Stop();
}
}
The Hardware Monitor is using FluentScheduler to schedule different tasks
Task Registry
public class TaskRegistry : Registry
{
public TaskRegistry()
{
Schedule<CpuMonitor>().ToRunNow();
}
}
The main concern is in the CPU Monitor:
CPU Monitor
public class CpuMonitor : ITask, IRegisteredObject
{
private readonly object _lock = new object();
public int ThresholdPercentage { get; set; }
public int TimeThresholdSec { get; set; }
public int ThresholdBreachCount { get; set; }
public DateTime BreachStart { get; set; }
public DateTime BreachEnd { get; set; }
private bool ShuttingDown { get; set; }
private bool MonitoringEnabled { get; set; }
private ISettingsService<HardwareSettingsDto> SettingsService { get; set; }
private HardwareSettingsDto Settings { get; set; }
private IEventService EventService { get; set; }
private EmailAlert EmailAlert { get; set; }
private ILogger Logger { get; set; }
private ISmtpClient SmtpClient { get; set; }
public CpuMonitor(ISettingsService<HardwareSettingsDto> settingsService, IEventService eventService, ILogger logger, ISmtpClient client)
{
Logger = logger;
SmtpClient = client;
EventService = eventService;
SettingsService = settingsService;
HostingEnvironment.RegisterObject(this);
GetThresholds();
if (!MonitoringEnabled)
ShuttingDown = true;
}
private void GetThresholds()
{
Settings = SettingsService.GetSettings();
MonitoringEnabled = Settings.EmailAlertSettings.AlertOnBreach || Settings.EmailAlertSettings.AlertOnBreachEnd;
ThresholdPercentage = Settings.CpuMonitoring.CpuPercentageLimit;
TimeThresholdSec = Settings.CpuMonitoring.ThresholdTime;
}
public void Alert()
{
EmailAlert = new EmailAlert(EventService, Logger,SmtpClient, Settings.EmailAlertSettings, BreachStart, BreachEnd);
EmailAlert.Alert();
}
public void StartMonitoring()
{
Logger.Info("Starting CPU Monitor");
try
{
using (var process = new PerformanceCounter("Processor", "% Processor Time", "_Total"))
{
var hasBeenBreached = false;
while (true)
{
hasBeenBreached = Monitor(process, hasBeenBreached);
}
}
}
catch (Exception e)
{
Logger.Fatal(e);
Stop();
//TODO: We need to possibly restart the service.
}
}
public bool Monitor(PerformanceCounter process, bool hasBeenBreached)
{
Console.WriteLine("Monitoring");
var breached = CheckBreach();
if (breached)
{
hasBeenBreached = true;
BreachStart = DateTime.Now;
Alert();
}
else if (hasBeenBreached)
{
BreachEnd = DateTime.Now;
EmailAlert = new EmailAlert(EventService, Logger,SmtpClient, Settings.EmailAlertSettings, BreachStart, BreachEnd);
EmailAlert.Alert();
}
process.NextValue();
Thread.Sleep(1000);
var currentValue = process.NextValue();
if (currentValue >= ThresholdPercentage)
{
ThresholdBreachCount++;
}
else
{
ThresholdBreachCount = 0;
}
return hasBeenBreached;
}
private bool CheckBreach()
{
// Check if the breach count >= than the time.
// Each breach count should equal to 1 second
return ThresholdBreachCount >= TimeThresholdSec;
}
public void Stop(bool immediate = false)
{
lock (_lock)
{
ShuttingDown = true;
}
Logger.Info("Stopping CPU Monitor");
HostingEnvironment.UnregisterObject(this);
}
public void Execute()
{
lock (_lock)
{
if (ShuttingDown)
{
return;
}
StartMonitoring();
}
}
}
In the constructor we are going to the DB via the SettingsService
(Injected via IoC Container) and then setting the thresholds. These thresholds are then never updated.
If the thresholds ever change (via Web UI) the only way to refresh this service is to restart it. I have thought about setting a timer and calling SettingsService.GetSettings()
after a set amount of time but this seems like a smelly workaround.