diff --git a/Cachet-Monitor.sln b/Cachet-Monitor.sln new file mode 100644 index 0000000..46b35ba --- /dev/null +++ b/Cachet-Monitor.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29728.190 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cachet-Monitor", "Cachet-Monitor\Cachet-Monitor.csproj", "{95DC5C5F-1837-4577-BD25-CA8BE900060D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {95DC5C5F-1837-4577-BD25-CA8BE900060D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {95DC5C5F-1837-4577-BD25-CA8BE900060D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {95DC5C5F-1837-4577-BD25-CA8BE900060D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {95DC5C5F-1837-4577-BD25-CA8BE900060D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {564B86A6-C853-4278-B04A-BF3776B746B0} + EndGlobalSection +EndGlobal diff --git a/Cachet-Monitor/Cachet-Monitor.csproj b/Cachet-Monitor/Cachet-Monitor.csproj new file mode 100644 index 0000000..73d1f50 --- /dev/null +++ b/Cachet-Monitor/Cachet-Monitor.csproj @@ -0,0 +1,23 @@ + + + + Exe + netcoreapp3.1 + Cachet_Monitor + 2020.2.24.0 + 2020.2.24.0 + + + + + + + + + + + ..\CachNet.dll + + + + diff --git a/Cachet-Monitor/Models/Configuration/Configuration.cs b/Cachet-Monitor/Models/Configuration/Configuration.cs new file mode 100644 index 0000000..ce41d5a --- /dev/null +++ b/Cachet-Monitor/Models/Configuration/Configuration.cs @@ -0,0 +1,62 @@ +using Cachet_Monitor.Models.Config; +using System; +using System.Collections.Generic; + +namespace Cachet_Monitor.Models +{ + public class Configuration + { + public Uri ApiBase { get; set; } + public string ApiKey { get; set; } + + public List Monitors { get; set; } + + public static Configuration Default = new Configuration + { + ApiBase = new Uri("https://domain.example.com"), + ApiKey = "AAABBBCCC11223344556", + Monitors = new List + { + new Monitor + { + Name = "Website", + Target = "https://example.com", + ComponentId = 1, + MetricId = 1, + Interval = 60, + Timeout = 5, + Type = MonitorType.HTTP, + Settings = new MonitorSettings + { + ExpectedStatusCode = 200, + Method = "GET" + } + }, + new Monitor + { + Name = "Database", + Target = "example.com", + ComponentId = 1, + MetricId = 1, + Interval = 60, + Timeout = 5, + Type = MonitorType.PORT, + Settings = new MonitorSettings + { + Port = 3306 + } + }, + new Monitor + { + Name = "Server", + Target = "example.com", + ComponentId = 1, + MetricId = 1, + Interval = 60, + Timeout = 5, + Type = MonitorType.ICMP + } + } + }; + } +} \ No newline at end of file diff --git a/Cachet-Monitor/Models/Configuration/Monitor/Monitor.cs b/Cachet-Monitor/Models/Configuration/Monitor/Monitor.cs new file mode 100644 index 0000000..305549e --- /dev/null +++ b/Cachet-Monitor/Models/Configuration/Monitor/Monitor.cs @@ -0,0 +1,24 @@ +namespace Cachet_Monitor.Models.Config +{ + public class Monitor + { + //Meta + public string Name; + + public string Target; + + //Cachet + public int ComponentId; + + public int MetricId; + + //Timing + public int Interval; + + public int Timeout; + + public MonitorType Type; + + public MonitorSettings Settings; + } +} \ No newline at end of file diff --git a/Cachet-Monitor/Models/Configuration/Monitor/MonitorSettings.cs b/Cachet-Monitor/Models/Configuration/Monitor/MonitorSettings.cs new file mode 100644 index 0000000..5713dd2 --- /dev/null +++ b/Cachet-Monitor/Models/Configuration/Monitor/MonitorSettings.cs @@ -0,0 +1,15 @@ +namespace Cachet_Monitor.Models.Config +{ + public class MonitorSettings + { + //Http + public string Method = "GET"; + + public int ExpectedStatusCode = 200; + + //TCP + public int Port; + + public int TTL; + } +} \ No newline at end of file diff --git a/Cachet-Monitor/Models/Configuration/Monitor/MonitorType.cs b/Cachet-Monitor/Models/Configuration/Monitor/MonitorType.cs new file mode 100644 index 0000000..9e373a2 --- /dev/null +++ b/Cachet-Monitor/Models/Configuration/Monitor/MonitorType.cs @@ -0,0 +1,11 @@ +using System; + +namespace Cachet_Monitor.Models.Config +{ + public enum MonitorType + { + HTTP, + PORT, + ICMP + } +} \ No newline at end of file diff --git a/Cachet-Monitor/Program.cs b/Cachet-Monitor/Program.cs new file mode 100644 index 0000000..593a09b --- /dev/null +++ b/Cachet-Monitor/Program.cs @@ -0,0 +1,223 @@ +using Cachet_Monitor.Models; +using Cachet_Monitor.Models.Config; +using Monitor = Cachet_Monitor.Models.Config.Monitor; +using CachNet; +using Newtonsoft.Json; +using System; +using System.IO; +using System.Threading.Tasks; +using System.Threading; +using System.Net.Sockets; +using System.Net; +using System.Diagnostics; +using CachNet.Entities; +using System.Net.NetworkInformation; + +namespace Cachet_Monitor +{ + internal class Program + { + private static Configuration Configuration; + private static CachetClient Cachet; + + private static void Main() + { + { + var config = Path.Combine(Environment.CurrentDirectory, "settings.json"); + + if (File.Exists(config)) + { + Configuration = JsonConvert.DeserializeObject(File.ReadAllText(config)); + } + else + { + File.WriteAllText(config, JsonConvert.SerializeObject(Configuration.Default, Formatting.Indented)); + + Console.WriteLine($"Written a new instance of the configuration to: {config}"); + Console.ReadKey(); + Environment.Exit(0); + } + } + + Cachet = new CachetClient(Configuration.ApiBase.OriginalString, Configuration.ApiKey); + + MainAsync().GetAwaiter().GetResult(); + } + + private static async Task MainAsync() + { + foreach (var monitor in Configuration.Monitors) + { + new Thread(async () => + { + Thread.CurrentThread.IsBackground = true; + while (true) + { + await DoMonitorCheck(Cachet, monitor).ConfigureAwait(false); + + await Task.Delay(monitor.Interval * 1000); + } + }).Start(); + } + + await Task.Delay(-1).ConfigureAwait(false); + } + + private static async Task DoMonitorCheck(CachetClient Cachet, Monitor monitor) + { + ComponentStatus componentStatus = 0; + switch (monitor.Type) + { + case MonitorType.PORT: + { + using (TcpClient tcpClient = new TcpClient()) + { + try + { + tcpClient.Connect(monitor.Target, monitor.Settings.Port); + await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent + { + Status = ComponentStatus.Operational, + }); + } + catch (Exception) + { + await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent + { + Status = ComponentStatus.MajorOutage, + }); + } + } + } + break; + + case MonitorType.ICMP: + { + Ping ping = new Ping(); + PingOptions options = new PingOptions + { + DontFragment = true, + Ttl = monitor.Settings.TTL + }; + + PingReply reply = ping.Send(monitor.Target, monitor.Timeout, null, options); + + await Cachet.AddMetricPointAsync(monitor.MetricId, new PostMetricPoint + { + Value = (int)reply.RoundtripTime + }); + + if (reply.Status == IPStatus.Success) + { + await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent + { + Status = ComponentStatus.Operational, + }); + } + + if (reply.RoundtripTime >= monitor.Timeout) + { + await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent + { + Status = ComponentStatus.PerformanceIssues, + }); + } + + if (reply.Status != IPStatus.Success) + { + if (reply.Status == IPStatus.TimedOut) + { + await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent + { + Status = ComponentStatus.PartialOutage, + }); + } + else + { + await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent + { + Status = ComponentStatus.MajorOutage, + }); + } + } + } + break; + + case MonitorType.HTTP: + { + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(monitor.Target); + request.Timeout = monitor.Timeout * 1000; + + Stopwatch timer = new Stopwatch(); + + timer.Start(); + try + { + HttpWebResponse response = (HttpWebResponse)request.GetResponse(); + timer.Stop(); + + if (response.StatusCode == (HttpStatusCode)monitor.Settings.ExpectedStatusCode) + { + var x = await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent + { + Status = ComponentStatus.Operational, + }); + } + else + { + await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent + { + Status = ComponentStatus.PartialOutage, + }); + } + + response.Close(); + } + catch (WebException ex) + { + timer.Stop(); + + if (ex.Status == WebExceptionStatus.ProtocolError) + { + if (ex.Response is HttpWebResponse response) + { + if (response.StatusCode == (HttpStatusCode)monitor.Settings.ExpectedStatusCode) + { + await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent + { + Status = ComponentStatus.Operational, + }); + } + } + } + else + { + if (ex.Status == WebExceptionStatus.Timeout) + { + await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent + { + Status = ComponentStatus.PerformanceIssues, + }); + } + else + { + await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent + { + Status = ComponentStatus.MajorOutage, + }); + } + } + } + + await Cachet.AddMetricPointAsync(monitor.MetricId, new PostMetricPoint + { + Value = (int)timer.ElapsedMilliseconds + }); + } + break; + } + + Console.WriteLine($"Ran check on \"{monitor.Target}\" Status: {componentStatus}"); + } + } +} \ No newline at end of file