From 9505d4555852460009c8d7a4d1a91a90c6375a11 Mon Sep 17 00:00:00 2001 From: Alexandru Macocian Date: Wed, 23 Oct 2019 12:03:26 +0200 Subject: [PATCH] Implemented usage monitors to monitor and observe server resource usage. --- MTSC-TestServer/Program.cs | 4 +- MTSC/MTSC.csproj | 6 +- MTSC/Server/Server.cs | 48 ++++++------- MTSC/Server/UsageMonitors/CPUUsageLimiter.cs | 67 +++++++++++++++++++ .../UsageMonitors/IServerUsageMonitor.cs | 23 +++++++ MTSC/Server/UsageMonitors/TickrateEnforcer.cs | 38 +++++++++++ 6 files changed, 154 insertions(+), 32 deletions(-) create mode 100644 MTSC/Server/UsageMonitors/CPUUsageLimiter.cs create mode 100644 MTSC/Server/UsageMonitors/IServerUsageMonitor.cs create mode 100644 MTSC/Server/UsageMonitors/TickrateEnforcer.cs diff --git a/MTSC-TestServer/Program.cs b/MTSC-TestServer/Program.cs index 63c0be1..10f7dbe 100644 --- a/MTSC-TestServer/Program.cs +++ b/MTSC-TestServer/Program.cs @@ -4,6 +4,7 @@ using MTSC.Exceptions; using MTSC.Logging; using MTSC.Server; using MTSC.Server.Handlers; +using MTSC.Server.UsageMonitors; using System; using System.Numerics; using System.Security.Cryptography; @@ -17,14 +18,13 @@ namespace MTSC_TestServer { X509Certificate2 certificate = new X509Certificate2("localhost.pfx", "psdsd"); Server server = new Server(80); - server.TickRate = 60; - server.ScaleUsage = true; RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024); EncryptionHandler encryptionHandler = new EncryptionHandler(rsa); server //.AddHandler(encryptionHandler) .AddLogger(new ConsoleLogger()) .AddLogger(new DebugConsoleLogger()) + .AddServerUsageMonitor(new TickrateEnforcer().SetTicksPerSecond(60)) .AddExceptionHandler(new ExceptionConsoleLogger()) //.AddHandler(new BroadcastHandler()) .AddHandler(new WebsocketHandler().AddWebsocketHandler(new BroadcastModule())) diff --git a/MTSC/MTSC.csproj b/MTSC/MTSC.csproj index 502e34b..a7ad732 100644 --- a/MTSC/MTSC.csproj +++ b/MTSC/MTSC.csproj @@ -5,12 +5,12 @@ netcoreapp2.1;net48;netstandard2.0;netcoreapp3.0 - 1.0.7 + 1.1.0 Alexandru-Victor Macocian MTSC Modular TCP Server and Client - 0.1.0.7 - 0.1.0.7 + 0.1.1.0 + 0.1.1.0 true AnyCPU;x64 https://github.com/AlexMacocian/MTSC diff --git a/MTSC/Server/Server.cs b/MTSC/Server/Server.cs index 025d9c3..43cef12 100644 --- a/MTSC/Server/Server.cs +++ b/MTSC/Server/Server.cs @@ -1,6 +1,7 @@ using MTSC.Exceptions; using MTSC.Logging; using MTSC.Server.Handlers; +using MTSC.Server.UsageMonitors; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -23,14 +24,14 @@ namespace MTSC.Server bool running; X509Certificate2 certificate; TcpListener listener; - int port = 50; + int port = 80; List toRemove = new List(); List clients = new List(); List handlers = new List(); List loggers = new List(); List exceptionHandlers = new List(); + List serverUsageMonitors = new List(); ConcurrentQueue> messageQueue = new ConcurrentQueue>(); - int loopMillis = 16; #endregion #region Properties /// @@ -45,21 +46,6 @@ namespace MTSC.Server /// List of clients currently connected to the server. /// public List Clients { get => clients; set => clients = value; } - /// - /// If set to true, the server will use an algorithm to lower or increase the CPU usage - /// based on demands. - /// - public bool ScaleUsage { get; set; } - /// - /// How many times does the server proc per second. Cannot be set higher than 1000. - /// This tickrate will be respected if either the ForceTickrate is set to true or - /// ScaleUsage is set to true and the server is in power saving mode. - /// - public int TickRate { get => 1000 / loopMillis; set => loopMillis = 1000 / Math.Min(value, 1000); } - /// - /// If set to true, the server will respect the provided TickRate, regardless of current server load. - /// - public bool ForceTickrate { get; set; } #endregion #region Constructors /// @@ -100,7 +86,7 @@ namespace MTSC.Server return this; } /// - /// Adds a handler to the server. + /// Adds a to the server. /// /// Handler to be added. /// This server object. @@ -110,7 +96,7 @@ namespace MTSC.Server return this; } /// - /// Adds a logger to the server. + /// Adds a to the server. /// /// Logger to be added. /// This server object. @@ -120,7 +106,7 @@ namespace MTSC.Server return this; } /// - /// Adds an exception handler to the server. + /// Adds an to the server. /// /// Handler to be added. /// This server object. @@ -130,6 +116,16 @@ namespace MTSC.Server return this; } /// + /// Adds a to the server. + /// + /// Monitor to be added. + /// This server object. + public Server AddServerUsageMonitor(IServerUsageMonitor serverUsageMonitor) + { + serverUsageMonitors.Add(serverUsageMonitor); + return this; + } + /// /// Queues a message to be sent. /// /// Target client. @@ -141,7 +137,7 @@ namespace MTSC.Server /// /// Adds a message to be logged by the associated loggers. /// - /// Message to be logged + /// Message to be logged public void Log(string log) { foreach (ILogger logger in loggers) @@ -152,7 +148,7 @@ namespace MTSC.Server /// /// Adds a debug message to be logged by the associated loggers. /// - /// Debug message to be logged + /// Debug message to be logged public void LogDebug(string debugMessage) { foreach (ILogger logger in loggers) @@ -366,13 +362,11 @@ namespace MTSC.Server } } /* - * If the server load is low or the server has a forced tickrate, - * sleep an amount of milliseconds to lower the CPU usage. + * Call the usage monitors and let them scale or determine current resource usage. */ - if(((DateTime.Now - lastLoad).TotalMilliseconds > 1000) && ScaleUsage || - ForceTickrate) + foreach(IServerUsageMonitor usageMonitor in serverUsageMonitors) { - Thread.Sleep((int)Math.Max(loopMillis - (DateTime.Now - startLoopTime).TotalMilliseconds, 0)); + usageMonitor.Tick(this); } } } diff --git a/MTSC/Server/UsageMonitors/CPUUsageLimiter.cs b/MTSC/Server/UsageMonitors/CPUUsageLimiter.cs new file mode 100644 index 0000000..631378d --- /dev/null +++ b/MTSC/Server/UsageMonitors/CPUUsageLimiter.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace MTSC.Server.UsageMonitors +{ + /// + /// Implementation of that limits CPU usage to a given value. + /// + public class CPUUsageLimiter : IServerUsageMonitor + { + private double cpuUsage; + private volatile bool polling = false; + private int cpuUsageLimit = 60; + + /// + /// Set the CPU usage limit. Bounded between 0 and 100%. + /// + public int CPUUsageLimit { get => cpuUsageLimit; + set + { + cpuUsageLimit = Math.Max(Math.Min(value, 100), 0); + } + } + /// + /// Sets the value. + /// + /// Value to be set. + /// This + public CPUUsageLimiter SetCPUUsageLimit(int cpuUsageLimit) + { + CPUUsageLimit = cpuUsageLimit; + return this; + } + + void IServerUsageMonitor.Tick(Server server) + { + PollCPUUsage(); + while(cpuUsage > cpuUsageLimit) + { + Thread.Sleep(10); + } + } + + private async void PollCPUUsage() + { + if (!polling) + { + polling = true; + var startTime = DateTime.UtcNow; + var startCpuUsage = Process.GetCurrentProcess().TotalProcessorTime; + await Task.Delay(500); + + var endTime = DateTime.UtcNow; + var endCpuUsage = Process.GetCurrentProcess().TotalProcessorTime; + var cpuUsedMs = (endCpuUsage - startCpuUsage).TotalMilliseconds; + var totalMsPassed = (endTime - startTime).TotalMilliseconds; + var cpuUsageTotal = cpuUsedMs / (Environment.ProcessorCount * totalMsPassed); + cpuUsage = cpuUsageTotal * 100; + polling = false; + } + } + } +} diff --git a/MTSC/Server/UsageMonitors/IServerUsageMonitor.cs b/MTSC/Server/UsageMonitors/IServerUsageMonitor.cs new file mode 100644 index 0000000..c859542 --- /dev/null +++ b/MTSC/Server/UsageMonitors/IServerUsageMonitor.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MTSC.Server.UsageMonitors +{ + /// + /// Interface for server usage monitors. + /// + public interface IServerUsageMonitor + { + /// + /// Called each server tick. + /// This method determines the usage of the server as well as behaviour. + /// + /// + /// This is useful in case the server needs a forced tickrate or should lower CPU usage when not under heavy load. + /// Sleeping the current thread in this method will sleep the server thread. + /// + /// + void Tick(Server server); + } +} diff --git a/MTSC/Server/UsageMonitors/TickrateEnforcer.cs b/MTSC/Server/UsageMonitors/TickrateEnforcer.cs new file mode 100644 index 0000000..bc7a929 --- /dev/null +++ b/MTSC/Server/UsageMonitors/TickrateEnforcer.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace MTSC.Server.UsageMonitors +{ + /// + /// Implementation of that enforces a specified tickrate. + /// + public class TickrateEnforcer : IServerUsageMonitor + { + private DateTime lastTickTime = DateTime.Now; + + /// + /// Number of server ticks in one second. + /// + public int TicksPerSecond { get; set; } + /// + /// Sets the value. + /// + /// Value to be set. + /// This + public TickrateEnforcer SetTicksPerSecond(int ticksPerSecond) + { + TicksPerSecond = ticksPerSecond; + return this; + } + + void IServerUsageMonitor.Tick(Server server) + { + if((DateTime.Now - lastTickTime).TotalMilliseconds < 1000f / TicksPerSecond) + { + Thread.Sleep((int)(1000f / TicksPerSecond - (DateTime.Now - lastTickTime).TotalMilliseconds)); + } + } + } +}