From 99da8fb5cc712cbeb132d24a92840ab22c38db9a Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Mon, 1 Feb 2021 15:34:33 -0900 Subject: [PATCH 1/7] Create partial implementation of HeartbeatManager This is a prety simple *Manager class that can send periodic requests to another server as a form of heartbeat. This is generally useful and could be used by a server to report uptime, player info, and so on. Not qutie finished yet here but this works for my testing. --- .../ACE.Server/Managers/HeartbeatManager.cs | 88 +++++++++++++++++++ Source/ACE.Server/Managers/WorldManager.cs | 2 + Source/ACE.Server/Program.cs | 3 + 3 files changed, 93 insertions(+) create mode 100644 Source/ACE.Server/Managers/HeartbeatManager.cs diff --git a/Source/ACE.Server/Managers/HeartbeatManager.cs b/Source/ACE.Server/Managers/HeartbeatManager.cs new file mode 100644 index 0000000000..4e702a28c8 --- /dev/null +++ b/Source/ACE.Server/Managers/HeartbeatManager.cs @@ -0,0 +1,88 @@ +using System; + +using log4net; + +using ACE.Common.Performance; +using System.Net.Http; +using System.Text; +using ACE.Common; +using Newtonsoft.Json; + +namespace ACE.Server.Managers +{ + public static class HeartbeatManager + { + private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// The rate at which HeartbeatManager.Tick() executes + /// + private static readonly RateLimiter updateHeartbeatManagerRateLimiter = new RateLimiter(1, TimeSpan.FromSeconds(10)); + + private static Uri heartbeatUri; + + public static void Initialize() + { + + // TODO: Factor out into config + heartbeatUri = new Uri("https://servers.treestats.net/heartbeat"); + } + + /// + /// One-off class to aid in serializing the heartbeat's JSON payload + /// + private class HeartbeatBody + { + public string server; + public int online; + public bool pk_server; + public double xp_modifier; + } + + /// + /// Runs every ~1 minute + /// + public static void Tick() + { + + if (updateHeartbeatManagerRateLimiter.GetSecondsToWaitBeforeNextEvent() > 0) + return; + + log.Debug($"HeartbeatManager.Tick()"); + + updateHeartbeatManagerRateLimiter.RegisterEvent(); + DoHeartbeat(); + } + + public static void DoHeartbeat() + { + using (var client = new HttpClient()) + { + HttpResponseMessage response; + + try + { + HeartbeatBody body = new HeartbeatBody + { + online = PlayerManager.GetOnlineCount(), + server = ConfigManager.Config.Server.WorldName, + xp_modifier = PropertyManager.GetDouble("xp_modifier").Item, + pk_server = PropertyManager.GetBool("pk_server").Item + }; + + HttpContent content = new StringContent(JsonConvert.SerializeObject(body)); + response = client.PostAsync(heartbeatUri, content).Result; + + if (!response.IsSuccessStatusCode) + { + log.Debug("Heartbeat Failed: " + response.Content); + } + } + catch (Exception e) + { + log.Debug("Exception: " + e.Message); + } + } + } + } +} diff --git a/Source/ACE.Server/Managers/WorldManager.cs b/Source/ACE.Server/Managers/WorldManager.cs index eeb944cdf9..0c826a4bc7 100644 --- a/Source/ACE.Server/Managers/WorldManager.cs +++ b/Source/ACE.Server/Managers/WorldManager.cs @@ -375,6 +375,8 @@ private static void UpdateWorld() Thread.Sleep(sessionCount == 0 ? 10 : 1); // Relax the CPU more if no sessions are connected Timers.PortalYearTicks += worldTickTimer.Elapsed.TotalSeconds; + + HeartbeatManager.Tick(); } // World has finished operations and concedes the thread to garbage collection diff --git a/Source/ACE.Server/Program.cs b/Source/ACE.Server/Program.cs index 1f8f2d2123..4f1622f832 100644 --- a/Source/ACE.Server/Program.cs +++ b/Source/ACE.Server/Program.cs @@ -196,6 +196,9 @@ public static void Main(string[] args) log.Info("Starting PropertyManager..."); PropertyManager.Initialize(); + log.Info("Starting HeartbeatManager..."); + HeartbeatManager.Initialize(); + log.Info("Initializing GuidManager..."); GuidManager.Initialize(); From b2002cd7dc4fe9833f7cb3a7c97a1b54ecec1a2d Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Tue, 2 Feb 2021 00:03:40 -0900 Subject: [PATCH 2/7] Refactor HeartbeatManager to use config This isn't quite working yet --- Source/ACE.Common/GameConfiguration.cs | 2 ++ Source/ACE.Common/HeartbeatDefaults.cs | 28 +++++++++++++++++++ .../ACE.Server/Managers/HeartbeatManager.cs | 22 +++++++++++---- 3 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 Source/ACE.Common/HeartbeatDefaults.cs diff --git a/Source/ACE.Common/GameConfiguration.cs b/Source/ACE.Common/GameConfiguration.cs index c22871febd..779c3d7048 100644 --- a/Source/ACE.Common/GameConfiguration.cs +++ b/Source/ACE.Common/GameConfiguration.cs @@ -35,5 +35,7 @@ public class GameConfiguration public bool LandblockPreloading { get; set; } public List PreloadedLandblocks { get; set; } + + public HeartbeatDefaults Heartbeat { get; set; } } } diff --git a/Source/ACE.Common/HeartbeatDefaults.cs b/Source/ACE.Common/HeartbeatDefaults.cs new file mode 100644 index 0000000000..607940ec62 --- /dev/null +++ b/Source/ACE.Common/HeartbeatDefaults.cs @@ -0,0 +1,28 @@ +using Newtonsoft.Json; + +namespace ACE.Common +{ + public struct HeartbeatDefaults + { + /// + /// Whether hearbeats are enabled + /// + [System.ComponentModel.DefaultValue(true)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public bool Enabled { get; set; } + + /// + /// The heartbeat endpoint + /// + [System.ComponentModel.DefaultValue("https://heartbeat.treestats.net")] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public string Endpoint { get; set; } + + /// + /// The heartbeat interval, in seconds + /// + [System.ComponentModel.DefaultValue(500)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public int Interval { get; set; } + } +} diff --git a/Source/ACE.Server/Managers/HeartbeatManager.cs b/Source/ACE.Server/Managers/HeartbeatManager.cs index 4e702a28c8..2dae6a166a 100644 --- a/Source/ACE.Server/Managers/HeartbeatManager.cs +++ b/Source/ACE.Server/Managers/HeartbeatManager.cs @@ -17,19 +17,31 @@ public static class HeartbeatManager /// /// The rate at which HeartbeatManager.Tick() executes /// - private static readonly RateLimiter updateHeartbeatManagerRateLimiter = new RateLimiter(1, TimeSpan.FromSeconds(10)); + private static RateLimiter updateHeartbeatManagerRateLimiter; - private static Uri heartbeatUri; + /// + /// Endpoint to send heartbeats to + /// + private static Uri endpoint; public static void Initialize() { + //if (!ConfigManager.Config.Server.Heartbeat.Enabled) + //{ + // log.Debug("Not starting HeartbeatManager because it's disabled in config"); + + // return; + //} + + // endpoint = new Uri(ConfigManager.Config.Server.Heartbeat.Endpoint); + endpoint = new Uri("https://treestats-servers.herokuapp.com/"); - // TODO: Factor out into config - heartbeatUri = new Uri("https://servers.treestats.net/heartbeat"); + // updateHeartbeatManagerRateLimiter = new RateLimiter(1, TimeSpan.FromSeconds(ConfigManager.Config.Server.Heartbeat.Interval)); + updateHeartbeatManagerRateLimiter = new RateLimiter(1, TimeSpan.FromSeconds(10)); } /// - /// One-off class to aid in serializing the heartbeat's JSON payload + /// One-off class to help serialize the heartbeat's JSON payload /// private class HeartbeatBody { From a36bc821255bdea5c66a07b87b6e43908bdaf211 Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Tue, 2 Feb 2021 00:04:02 -0900 Subject: [PATCH 3/7] Tweak heartbeat payload --- Source/ACE.Server/Managers/HeartbeatManager.cs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Source/ACE.Server/Managers/HeartbeatManager.cs b/Source/ACE.Server/Managers/HeartbeatManager.cs index 2dae6a166a..810ec71147 100644 --- a/Source/ACE.Server/Managers/HeartbeatManager.cs +++ b/Source/ACE.Server/Managers/HeartbeatManager.cs @@ -43,12 +43,10 @@ public static void Initialize() /// /// One-off class to help serialize the heartbeat's JSON payload /// - private class HeartbeatBody + private class HeartbeatPayload { - public string server; - public int online; - public bool pk_server; - public double xp_modifier; + public string WorldName; + public int OnlineCount; } /// @@ -74,16 +72,14 @@ public static void DoHeartbeat() try { - HeartbeatBody body = new HeartbeatBody + HeartbeatPayload body = new HeartbeatPayload { - online = PlayerManager.GetOnlineCount(), - server = ConfigManager.Config.Server.WorldName, - xp_modifier = PropertyManager.GetDouble("xp_modifier").Item, - pk_server = PropertyManager.GetBool("pk_server").Item + OnlineCount = PlayerManager.GetOnlineCount(), + WorldName = ConfigManager.Config.Server.WorldName }; HttpContent content = new StringContent(JsonConvert.SerializeObject(body)); - response = client.PostAsync(heartbeatUri, content).Result; + response = client.PostAsync(endpoint, content).Result; if (!response.IsSuccessStatusCode) { From a55a2b446fe37b2446485a2be1ebdb3c1c33ddbf Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Tue, 2 Feb 2021 00:04:26 -0900 Subject: [PATCH 4/7] Fix doc on HeartbeatManager.Tick() --- Source/ACE.Server/Managers/HeartbeatManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ACE.Server/Managers/HeartbeatManager.cs b/Source/ACE.Server/Managers/HeartbeatManager.cs index 810ec71147..3261dacb1e 100644 --- a/Source/ACE.Server/Managers/HeartbeatManager.cs +++ b/Source/ACE.Server/Managers/HeartbeatManager.cs @@ -50,7 +50,7 @@ private class HeartbeatPayload } /// - /// Runs every ~1 minute + /// Runs at intervals according to config /// public static void Tick() { From 727bf77f0e3d17b9622514aab45fe9e27f07da0a Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Tue, 2 Feb 2021 00:04:57 -0900 Subject: [PATCH 5/7] Tweak exception text in HeartbeatManager --- Source/ACE.Server/Managers/HeartbeatManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ACE.Server/Managers/HeartbeatManager.cs b/Source/ACE.Server/Managers/HeartbeatManager.cs index 3261dacb1e..b6deb04767 100644 --- a/Source/ACE.Server/Managers/HeartbeatManager.cs +++ b/Source/ACE.Server/Managers/HeartbeatManager.cs @@ -88,7 +88,7 @@ public static void DoHeartbeat() } catch (Exception e) { - log.Debug("Exception: " + e.Message); + log.Debug("Exception while sending Heartbeat: " + e.Message); } } } From 3e1aae073724b6248aba02a2d6701bb9fb7c9fe9 Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Sat, 6 Feb 2021 22:09:38 -0900 Subject: [PATCH 6/7] Fix HeartbeatManager config not working --- Source/ACE.Common/GameConfiguration.cs | 2 +- ...eartbeatDefaults.cs => HeartbeatSettings.cs} | 4 ++-- Source/ACE.Server/Managers/HeartbeatManager.cs | 17 +++++++---------- 3 files changed, 10 insertions(+), 13 deletions(-) rename Source/ACE.Common/{HeartbeatDefaults.cs => HeartbeatSettings.cs} (86%) diff --git a/Source/ACE.Common/GameConfiguration.cs b/Source/ACE.Common/GameConfiguration.cs index 779c3d7048..b2ad309619 100644 --- a/Source/ACE.Common/GameConfiguration.cs +++ b/Source/ACE.Common/GameConfiguration.cs @@ -36,6 +36,6 @@ public class GameConfiguration public List PreloadedLandblocks { get; set; } - public HeartbeatDefaults Heartbeat { get; set; } + public HeartbeatSettings Heartbeat { get; set; } } } diff --git a/Source/ACE.Common/HeartbeatDefaults.cs b/Source/ACE.Common/HeartbeatSettings.cs similarity index 86% rename from Source/ACE.Common/HeartbeatDefaults.cs rename to Source/ACE.Common/HeartbeatSettings.cs index 607940ec62..67cd2107fd 100644 --- a/Source/ACE.Common/HeartbeatDefaults.cs +++ b/Source/ACE.Common/HeartbeatSettings.cs @@ -2,7 +2,7 @@ namespace ACE.Common { - public struct HeartbeatDefaults + public class HeartbeatSettings { /// /// Whether hearbeats are enabled @@ -14,7 +14,7 @@ public struct HeartbeatDefaults /// /// The heartbeat endpoint /// - [System.ComponentModel.DefaultValue("https://heartbeat.treestats.net")] + [System.ComponentModel.DefaultValue("https://treestats-servers.herokuapp.com")] [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] public string Endpoint { get; set; } diff --git a/Source/ACE.Server/Managers/HeartbeatManager.cs b/Source/ACE.Server/Managers/HeartbeatManager.cs index b6deb04767..08da32eb1c 100644 --- a/Source/ACE.Server/Managers/HeartbeatManager.cs +++ b/Source/ACE.Server/Managers/HeartbeatManager.cs @@ -26,18 +26,15 @@ public static class HeartbeatManager public static void Initialize() { - //if (!ConfigManager.Config.Server.Heartbeat.Enabled) - //{ - // log.Debug("Not starting HeartbeatManager because it's disabled in config"); - - // return; - //} + if (!ConfigManager.Config.Server.Heartbeat.Enabled) + { + log.Debug("Not starting HeartbeatManager because it's disabled in config"); - // endpoint = new Uri(ConfigManager.Config.Server.Heartbeat.Endpoint); - endpoint = new Uri("https://treestats-servers.herokuapp.com/"); + return; + } - // updateHeartbeatManagerRateLimiter = new RateLimiter(1, TimeSpan.FromSeconds(ConfigManager.Config.Server.Heartbeat.Interval)); - updateHeartbeatManagerRateLimiter = new RateLimiter(1, TimeSpan.FromSeconds(10)); + endpoint = new Uri(ConfigManager.Config.Server.Heartbeat.Endpoint); + updateHeartbeatManagerRateLimiter = new RateLimiter(1, TimeSpan.FromSeconds(ConfigManager.Config.Server.Heartbeat.Interval)); } /// From 52fb5cb89ccef5304f6b9f3b3ef00acefd226843 Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Sat, 6 Feb 2021 22:10:15 -0900 Subject: [PATCH 7/7] Do HeartbeatManager request in a one-off thread Also capture HTTP exceptions and tweak log messages --- Source/ACE.Server/Managers/HeartbeatManager.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Source/ACE.Server/Managers/HeartbeatManager.cs b/Source/ACE.Server/Managers/HeartbeatManager.cs index 08da32eb1c..c8bbde4522 100644 --- a/Source/ACE.Server/Managers/HeartbeatManager.cs +++ b/Source/ACE.Server/Managers/HeartbeatManager.cs @@ -7,6 +7,7 @@ using System.Text; using ACE.Common; using Newtonsoft.Json; +using System.Threading; namespace ACE.Server.Managers { @@ -58,11 +59,15 @@ public static void Tick() log.Debug($"HeartbeatManager.Tick()"); updateHeartbeatManagerRateLimiter.RegisterEvent(); - DoHeartbeat(); + + Thread mythread = new Thread(DoHeartbeat); + mythread.Start(); } public static void DoHeartbeat() { + log.Debug($"HeartbeatManager.DoHeartbeat Called"); + using (var client = new HttpClient()) { HttpResponseMessage response; @@ -77,15 +82,20 @@ public static void DoHeartbeat() HttpContent content = new StringContent(JsonConvert.SerializeObject(body)); response = client.PostAsync(endpoint, content).Result; + response.EnsureSuccessStatusCode(); if (!response.IsSuccessStatusCode) { - log.Debug("Heartbeat Failed: " + response.Content); + log.Debug($"Heartbeat request failed: {response.Content}"); } } + catch (HttpRequestException e) + { + log.Debug($"HttpRequestException while sending Heartbeat {e.Message}"); + } catch (Exception e) { - log.Debug("Exception while sending Heartbeat: " + e.Message); + log.Debug($"Exception while sending Heartbeat: {e.Message}"); } } }