Mercurial > servermonitor
view ServerMonitor/Objects/ServerMonitor.cs @ 0:3e1a2131f897
Initial commit. Ping check, scheduling, UI working. SSH check mostly working.
author | Brad Greco <brad@bgreco.net> |
---|---|
date | Mon, 31 Dec 2018 18:32:14 -0500 |
parents | |
children | 3142e52cbe69 |
line wrap: on
line source
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml.Serialization; namespace ServerMonitorApp { public class ServerMonitor { private readonly string configFileDir; private readonly Logger logger; private readonly Dictionary<int, CancellationTokenSource> tokens = new Dictionary<int, CancellationTokenSource>(); private bool running; private Dictionary<Task<CheckResult>, int> tasks; //private List<Task<CheckResult>> tasks; public event EventHandler<CheckStatusChangedEventArgs> CheckStatusChanged; public List<Server> Servers { get; private set; } = new List<Server>(); public IEnumerable<Check> Checks { get { return Servers.SelectMany(s => s.Checks); } } public string ConfigFile { get; private set; } public ServerMonitor() { configFileDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ServerMonitor"); ConfigFile = Path.Combine(configFileDir, "servers.xml"); logger = new Logger(Path.Combine(configFileDir, "monitor.log")); } public void AddServer(Server server) { Servers.Add(server); } public void LoadServers() { TextReader reader = null; try { reader = new StreamReader(ConfigFile); XmlSerializer serializer = CreateXmlSerializer(); Servers.Clear(); Servers.AddRange((List<Server>)serializer.Deserialize(reader)); // Update the Checks so they know what Server they belong to. // Would rather do this in the Server object on deserialization, but // that doesn't work when using the XML serializer for some reason. foreach (Server server in Servers) { foreach (Check check in server.Checks) { check.Server = server; } server.CheckModified += Server_CheckModified; } } // If the file doesn't exist, no special handling is needed. It will be created later. catch (FileNotFoundException) { } catch (DirectoryNotFoundException) { } catch (InvalidOperationException) { //TODO log throw; } finally { reader?.Close(); } Run(); } public void SaveServers() { GenerateIds(); TextWriter writer = null; XmlSerializer serializer = null; try { writer = new StreamWriter(ConfigFile); serializer = CreateXmlSerializer(); serializer.Serialize(writer, Servers); } catch (DirectoryNotFoundException) { Directory.CreateDirectory(configFileDir); writer = new StreamWriter(ConfigFile); serializer = CreateXmlSerializer(); serializer.Serialize(writer, Servers); } catch (Exception) { //TODO log throw; } finally { writer?.Close(); } } private async void Run() { if (running) return; running = true; //TODO subscribe to power events. Find any check's NextExecutionTime is in the past. Cancel waiting task and run immediately (or after short delay). //tasks = Checks.Select(c => ScheduleExecuteCheckAsync(c)).ToList(); tasks = Checks.ToDictionary(c => ScheduleExecuteCheckAsync(c), c => c.Id); while (tasks.Count > 0) { Task<CheckResult> task = await Task.WhenAny(tasks.Keys); tasks.Remove(task); try { CheckResult result = await task; // Result will be null if a scheduled check was disabled if (result != null && result.CheckStatus != CheckStatus.Disabled) tasks.Add(ScheduleExecuteCheckAsync(result.Check), result.Check.Id); } catch (OperationCanceledException) { } } running = false; } public async Task<CheckResult> ExecuteCheckAsync(Check check, CancellationToken token = default(CancellationToken)) { check.Status = CheckStatus.Running; OnCheckStatusChanged(check); CheckResult result = await check.ExecuteAsync(token); OnCheckStatusChanged(check, result); logger.Log(result); return result; } public IList<CheckResult> GetLog(Server server) { return logger.Read(server); } private void OnCheckStatusChanged(Check check, CheckResult result = null) { SaveServers(); CheckStatusChanged?.Invoke(check, new CheckStatusChangedEventArgs(check, result)); } private void Server_CheckModified(object sender, EventArgs e) { Check check = (Check)sender; Task<CheckResult> task = tasks.FirstOrDefault(kvp => kvp.Value == check.Id).Key; if (running) { if (task == null) { // No tasks associated with the check, so schedule a new one tasks.Add(ScheduleExecuteCheckAsync(check), check.Id); } else { // Check was modified or deleted, so remove any waiting tasks tasks.Remove(task); if (tokens.TryGetValue(check.Id, out CancellationTokenSource cts)) cts.Cancel(); if (check.Server != null) { // If the check was not deleted, schedule the new check. // But only if it's still running, otherwise restarting the monitor below // will create a duplicate run. if (running) tasks.Add(ScheduleExecuteCheckAsync(check), check.Id); } } } // Run again in case removing a task above caused it to stop Run(); } private async Task<CheckResult> ScheduleExecuteCheckAsync(Check check) { if (!check.Enabled) return await Task.FromResult(new CheckResult(check, CheckStatus.Disabled, null)); CancellationTokenSource cts = new CancellationTokenSource(); tokens[check.Id] = cts; check.NextRunTime = check.Schedule.GetNextTime(check.LastScheduledRunTime); await Task.Delay(check.NextRunTime - DateTime.Now, cts.Token); check.LastScheduledRunTime = check.NextRunTime; return await ExecuteCheckAsync(check, cts.Token); } private void GenerateIds() { if (Servers.Any()) { int id = Servers.Max(s => s.Id); foreach (Server server in Servers) { if (server.Id == 0) server.Id = ++id; } } //TODO if a check is deleted, there might be old results in the log file that share an ID with a new one if (Checks.Any()) { int id = Checks.Max(c => c.Id); foreach (Check check in Checks) { if (check.Id == 0) check.Id = ++id; } } } private XmlSerializer CreateXmlSerializer() { return new XmlSerializer(typeof(List<Server>), Check.CheckTypes); } } public class CheckStatusChangedEventArgs : EventArgs { public Check Check { get; private set; } public CheckResult CheckResult { get; private set; } public CheckStatusChangedEventArgs(Check check, CheckResult result) { Check = check; CheckResult = result; } } }