Mercurial > servermonitor
comparison 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 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:3e1a2131f897 |
---|---|
1 using System; | |
2 using System.Collections.Generic; | |
3 using System.Diagnostics; | |
4 using System.IO; | |
5 using System.Linq; | |
6 using System.Text; | |
7 using System.Threading; | |
8 using System.Threading.Tasks; | |
9 using System.Xml.Serialization; | |
10 | |
11 namespace ServerMonitorApp | |
12 { | |
13 public class ServerMonitor | |
14 { | |
15 private readonly string configFileDir; | |
16 private readonly Logger logger; | |
17 private readonly Dictionary<int, CancellationTokenSource> tokens = new Dictionary<int, CancellationTokenSource>(); | |
18 private bool running; | |
19 private Dictionary<Task<CheckResult>, int> tasks; | |
20 //private List<Task<CheckResult>> tasks; | |
21 | |
22 public event EventHandler<CheckStatusChangedEventArgs> CheckStatusChanged; | |
23 | |
24 public List<Server> Servers { get; private set; } = new List<Server>(); | |
25 | |
26 public IEnumerable<Check> Checks { get { return Servers.SelectMany(s => s.Checks); } } | |
27 | |
28 public string ConfigFile { get; private set; } | |
29 | |
30 public ServerMonitor() | |
31 { | |
32 configFileDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ServerMonitor"); | |
33 ConfigFile = Path.Combine(configFileDir, "servers.xml"); | |
34 logger = new Logger(Path.Combine(configFileDir, "monitor.log")); | |
35 } | |
36 | |
37 public void AddServer(Server server) | |
38 { | |
39 Servers.Add(server); | |
40 } | |
41 | |
42 public void LoadServers() | |
43 { | |
44 TextReader reader = null; | |
45 try | |
46 { | |
47 reader = new StreamReader(ConfigFile); | |
48 XmlSerializer serializer = CreateXmlSerializer(); | |
49 Servers.Clear(); | |
50 Servers.AddRange((List<Server>)serializer.Deserialize(reader)); | |
51 // Update the Checks so they know what Server they belong to. | |
52 // Would rather do this in the Server object on deserialization, but | |
53 // that doesn't work when using the XML serializer for some reason. | |
54 foreach (Server server in Servers) | |
55 { | |
56 foreach (Check check in server.Checks) | |
57 { | |
58 check.Server = server; | |
59 } | |
60 server.CheckModified += Server_CheckModified; | |
61 } | |
62 } | |
63 // If the file doesn't exist, no special handling is needed. It will be created later. | |
64 catch (FileNotFoundException) { } | |
65 catch (DirectoryNotFoundException) { } | |
66 catch (InvalidOperationException) | |
67 { | |
68 //TODO log | |
69 throw; | |
70 } | |
71 finally | |
72 { | |
73 reader?.Close(); | |
74 } | |
75 Run(); | |
76 } | |
77 | |
78 public void SaveServers() | |
79 { | |
80 GenerateIds(); | |
81 TextWriter writer = null; | |
82 XmlSerializer serializer = null; | |
83 try | |
84 { | |
85 writer = new StreamWriter(ConfigFile); | |
86 serializer = CreateXmlSerializer(); | |
87 serializer.Serialize(writer, Servers); | |
88 } | |
89 catch (DirectoryNotFoundException) | |
90 { | |
91 Directory.CreateDirectory(configFileDir); | |
92 writer = new StreamWriter(ConfigFile); | |
93 serializer = CreateXmlSerializer(); | |
94 serializer.Serialize(writer, Servers); | |
95 } | |
96 catch (Exception) | |
97 { | |
98 //TODO log | |
99 throw; | |
100 } | |
101 finally | |
102 { | |
103 writer?.Close(); | |
104 } | |
105 } | |
106 | |
107 private async void Run() | |
108 { | |
109 if (running) | |
110 return; | |
111 running = true; | |
112 //TODO subscribe to power events. Find any check's NextExecutionTime is in the past. Cancel waiting task and run immediately (or after short delay). | |
113 //tasks = Checks.Select(c => ScheduleExecuteCheckAsync(c)).ToList(); | |
114 tasks = Checks.ToDictionary(c => ScheduleExecuteCheckAsync(c), c => c.Id); | |
115 while (tasks.Count > 0) | |
116 { | |
117 Task<CheckResult> task = await Task.WhenAny(tasks.Keys); | |
118 tasks.Remove(task); | |
119 try | |
120 { | |
121 CheckResult result = await task; | |
122 // Result will be null if a scheduled check was disabled | |
123 if (result != null && result.CheckStatus != CheckStatus.Disabled) | |
124 tasks.Add(ScheduleExecuteCheckAsync(result.Check), result.Check.Id); | |
125 } | |
126 catch (OperationCanceledException) | |
127 { | |
128 | |
129 } | |
130 } | |
131 running = false; | |
132 } | |
133 | |
134 public async Task<CheckResult> ExecuteCheckAsync(Check check, CancellationToken token = default(CancellationToken)) | |
135 { | |
136 check.Status = CheckStatus.Running; | |
137 OnCheckStatusChanged(check); | |
138 CheckResult result = await check.ExecuteAsync(token); | |
139 OnCheckStatusChanged(check, result); | |
140 logger.Log(result); | |
141 return result; | |
142 } | |
143 | |
144 public IList<CheckResult> GetLog(Server server) | |
145 { | |
146 return logger.Read(server); | |
147 } | |
148 | |
149 private void OnCheckStatusChanged(Check check, CheckResult result = null) | |
150 { | |
151 SaveServers(); | |
152 CheckStatusChanged?.Invoke(check, new CheckStatusChangedEventArgs(check, result)); | |
153 } | |
154 | |
155 private void Server_CheckModified(object sender, EventArgs e) | |
156 { | |
157 Check check = (Check)sender; | |
158 Task<CheckResult> task = tasks.FirstOrDefault(kvp => kvp.Value == check.Id).Key; | |
159 if (running) | |
160 { | |
161 if (task == null) | |
162 { | |
163 // No tasks associated with the check, so schedule a new one | |
164 tasks.Add(ScheduleExecuteCheckAsync(check), check.Id); | |
165 } | |
166 else | |
167 { | |
168 // Check was modified or deleted, so remove any waiting tasks | |
169 tasks.Remove(task); | |
170 if (tokens.TryGetValue(check.Id, out CancellationTokenSource cts)) | |
171 cts.Cancel(); | |
172 if (check.Server != null) | |
173 { | |
174 // If the check was not deleted, schedule the new check. | |
175 // But only if it's still running, otherwise restarting the monitor below | |
176 // will create a duplicate run. | |
177 if (running) | |
178 tasks.Add(ScheduleExecuteCheckAsync(check), check.Id); | |
179 } | |
180 } | |
181 } | |
182 // Run again in case removing a task above caused it to stop | |
183 Run(); | |
184 } | |
185 | |
186 private async Task<CheckResult> ScheduleExecuteCheckAsync(Check check) | |
187 { | |
188 if (!check.Enabled) | |
189 return await Task.FromResult(new CheckResult(check, CheckStatus.Disabled, null)); | |
190 | |
191 CancellationTokenSource cts = new CancellationTokenSource(); | |
192 tokens[check.Id] = cts; | |
193 check.NextRunTime = check.Schedule.GetNextTime(check.LastScheduledRunTime); | |
194 await Task.Delay(check.NextRunTime - DateTime.Now, cts.Token); | |
195 check.LastScheduledRunTime = check.NextRunTime; | |
196 return await ExecuteCheckAsync(check, cts.Token); | |
197 } | |
198 | |
199 private void GenerateIds() | |
200 { | |
201 if (Servers.Any()) | |
202 { | |
203 int id = Servers.Max(s => s.Id); | |
204 foreach (Server server in Servers) | |
205 { | |
206 if (server.Id == 0) | |
207 server.Id = ++id; | |
208 } | |
209 } | |
210 | |
211 //TODO if a check is deleted, there might be old results in the log file that share an ID with a new one | |
212 if (Checks.Any()) | |
213 { | |
214 int id = Checks.Max(c => c.Id); | |
215 foreach (Check check in Checks) | |
216 { | |
217 if (check.Id == 0) | |
218 check.Id = ++id; | |
219 } | |
220 } | |
221 } | |
222 | |
223 private XmlSerializer CreateXmlSerializer() | |
224 { | |
225 return new XmlSerializer(typeof(List<Server>), Check.CheckTypes); | |
226 } | |
227 } | |
228 | |
229 public class CheckStatusChangedEventArgs : EventArgs | |
230 { | |
231 public Check Check { get; private set; } | |
232 | |
233 public CheckResult CheckResult { get; private set; } | |
234 | |
235 public CheckStatusChangedEventArgs(Check check, CheckResult result) | |
236 { | |
237 Check = check; | |
238 CheckResult = result; | |
239 } | |
240 | |
241 } | |
242 } |