Save 0.4% Windows server CPU.

This commit is contained in:
Pieter-Jan Briers
2021-07-31 04:20:50 +02:00
parent 8fea42ff9a
commit a7b9c87926
5 changed files with 167 additions and 46 deletions
+2 -41
View File
@@ -94,10 +94,6 @@ namespace Robust.Server
private ILogHandler? _logHandler;
private IGameLoop _mainLoop = default!;
private TimeSpan _lastTitleUpdate;
private long _lastReceivedBytes;
private long _lastSentBytes;
private string? _shutdownReason;
private readonly ManualResetEventSlim _shutdownEvent = new(false);
@@ -515,28 +511,6 @@ namespace Robust.Server
_mainLoop = gameLoop;
}
/// <summary>
/// Updates the console window title with performance statistics.
/// </summary>
private void UpdateTitle()
{
if (!Environment.UserInteractive || System.Console.IsInputRedirected)
{
return;
}
// every 1 second update stats in the console window title
if ((_time.RealTime - _lastTitleUpdate).TotalSeconds < 1.0)
return;
var netStats = UpdateBps();
System.Console.Title = string.Format("FPS: {0:N2} SD: {1:N2}ms | Net: ({2}) | Memory: {3:N0} KiB",
Math.Round(_time.FramesPerSecondAvg, 2),
_time.RealFrameTimeStdDev.TotalMilliseconds,
netStats,
Process.GetCurrentProcess().PrivateMemorySize64 >> 10);
_lastTitleUpdate = _time.RealTime;
}
/// <summary>
/// Loads the server settings from the ConfigurationManager.
@@ -593,22 +567,9 @@ namespace Robust.Server
}
}
private string UpdateBps()
{
var stats = IoCManager.Resolve<IServerNetManager>().Statistics;
var bps =
$"Send: {(stats.SentBytes - _lastSentBytes) >> 10:N0} KiB/s, Recv: {(stats.ReceivedBytes - _lastReceivedBytes) >> 10:N0} KiB/s";
_lastSentBytes = stats.SentBytes;
_lastReceivedBytes = stats.ReceivedBytes;
return bps;
}
private void Input(FrameEventArgs args)
{
_systemConsole.Update();
_systemConsole.UpdateInput();
_network.ProcessPackets();
_taskManager.ProcessPendingTasks();
@@ -622,7 +583,7 @@ namespace Robust.Server
// These are always the same on the server, there is no prediction.
_time.LastRealTick = _time.CurTick;
UpdateTitle();
_systemConsole.UpdateTick();
using (TickUsage.WithLabels("PreEngine").NewTimer())
{
@@ -8,12 +8,14 @@
/// <summary>
/// process input/output of the console. This needs to be called often.
/// </summary>
void Update();
void UpdateInput();
/// <summary>
/// Prints <paramref name="text" /> to the system console.
/// </summary>
/// <param name="text">Text to write to the system console.</param>
void Print(string text);
void UpdateTick();
}
}
+74 -3
View File
@@ -1,8 +1,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Robust.Shared.Asynchronous;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Con = System.Console;
namespace Robust.Server.Console
@@ -12,6 +16,12 @@ namespace Robust.Server.Console
[Dependency] private readonly IServerConsoleHost _conShell = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
[Dependency] private readonly IBaseServer _baseServer = default!;
[Dependency] private readonly IServerNetManager _netManager = default!;
[Dependency] private readonly IGameTiming _time = default!;
//
// Command entry stuff.
//
private readonly Dictionary<int, string> commandHistory = new();
private string currentBuffer = "";
@@ -21,6 +31,20 @@ namespace Robust.Server.Console
private int tabCompleteIndex;
private ConsoleKey lastKeyPressed = ConsoleKey.NoName;
//
// Title update stuff.
//
// This is ridiculously expensive to fetch for some reason.
// I'm gonna just assume that this can't change during the lifetime of the process. I hope.
// I want this ridiculous 0.1% CPU usage off my profiler.
private readonly bool _userInteractive = Environment.UserInteractive;
private TimeSpan _lastTitleUpdate;
private long _lastReceivedBytes;
private long _lastSentBytes;
public void Dispose()
{
if (Environment.UserInteractive)
@@ -37,7 +61,53 @@ namespace Robust.Server.Console
}
}
public void Update()
public void UpdateTick()
{
UpdateTitle();
}
/// <summary>
/// Updates the console window title with performance statistics.
/// </summary>
private void UpdateTitle()
{
if (!_userInteractive || System.Console.IsInputRedirected)
{
return;
}
// every 1 second update stats in the console window title
if ((_time.RealTime - _lastTitleUpdate).TotalSeconds < 1.0)
return;
var netStats = UpdateBps();
var process = Process.GetCurrentProcess();
var privateSize = process.GetPrivateMemorySize64NotSlowHolyFuckingShitMicrosoft();
System.Console.WriteLine($"A: {privateSize} B: {process.PrivateMemorySize64}");
System.Console.Title = string.Format("FPS: {0:N2} SD: {1:N2}ms | Net: ({2}) | Memory: {3:N0} KiB",
Math.Round(_time.FramesPerSecondAvg, 2),
_time.RealFrameTimeStdDev.TotalMilliseconds,
netStats,
privateSize >> 10);
_lastTitleUpdate = _time.RealTime;
}
private string UpdateBps()
{
var stats = _netManager.Statistics;
var bps =
$"Send: {(stats.SentBytes - _lastSentBytes) >> 10:N0} KiB/s, Recv: {(stats.ReceivedBytes - _lastReceivedBytes) >> 10:N0} KiB/s";
_lastSentBytes = stats.SentBytes;
_lastReceivedBytes = stats.ReceivedBytes;
return bps;
}
public void UpdateInput()
{
if (Con.IsInputRedirected)
{
@@ -161,7 +231,7 @@ namespace Robust.Server.Console
{
var currentLineCursor = Con.CursorTop;
Con.SetCursorPosition(0, Con.CursorTop);
Con.Write(new string(' ', Con.WindowWidth-1));
Con.Write(new string(' ', Con.WindowWidth - 1));
Con.SetCursorPosition(0, currentLineCursor);
}
@@ -174,7 +244,8 @@ namespace Robust.Server.Console
if (tabCompleteList.Count == 0)
{
tabCompleteList = _conShell.RegisteredCommands.Keys.Where(key => key.StartsWith(currentBuffer)).ToList();
tabCompleteList = _conShell.RegisteredCommands.Keys.Where(key => key.StartsWith(currentBuffer))
.ToList();
if (tabCompleteList.Count == 0)
{
return String.Empty;
@@ -2,7 +2,7 @@ namespace Robust.Server.Console
{
internal sealed class SystemConsoleManagerDummy : ISystemConsoleManager
{
public void Update()
public void UpdateInput()
{
// Nada.
}
@@ -11,5 +11,10 @@ namespace Robust.Server.Console
{
// Nada.
}
public void UpdateTick()
{
// Nada.
}
}
}
+82
View File
@@ -0,0 +1,82 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// ReSharper disable InconsistentNaming
// ReSharper disable IdentifierTypo
// ReSharper disable CommentTypo
#pragma warning disable 649
namespace Robust.Shared.Utility
{
internal static unsafe class ProcessExt
{
// THIS ENTIRE METHOD EXISTS PURELY OUT OF SPITE FOR MICROSOFT.
// NOT TO SAVE 0.3% CPU USAGE ON WINDOWS. NAH.
// TO SPITE MICROSOFT.
// WHAT ABOUT THE 2.2% CPU USAGE IN CONSOLE.KEYAVAILABLE? WHAT ABOUT THAT WHATABOUTISM HUH???
// I WANTED TO GO TO BED NOW IT'S 4:20 AM (BASED) GOD DAMNIT.
// AS A RESULT, EVERYTHING HERE WILL BE WRITTEN IN *FUCKING* CAPS.
[MethodImpl(MethodImplOptions.NoInlining)]
public static long GetPrivateMemorySize64NotSlowHolyFuckingShitMicrosoft(this Process process)
{
if (!OperatingSystem.IsWindows())
// IT'S NOT SLOW ON LINUX, *LUCKILY*.
// WELL I DIDN'T PROFILE IT BUT THEY DON'T DO A STUPID SEARCH OVER EVERY PROCESS.
// IT USES PROCFS, IF YOU'RE CURIOUS.
return process.PrivateMemorySize64;
//
// GLASS TOLD ME TO PUT A TL;DR IN SO TL;DR
// PROCESS.PRIVATEMEMORYSIZE64 (AND A BUNCH OF OTHERS) ARE SLOW AS BALLS (2+ms for me).
// THIS ISN'T.
//
// AS IT *FUCKING* TURNS OUT.
// *MANY* OF THE FIELDS ON PROCESS (THE CLASS, YOU KNOW THE ONE THIS METHOD IS AN EXTENSION FOR) HAVE TO FETCH A "PROCESS INFO".
// HOW DO YOU FETCH A PROCESS INFO ON WINDOWS? OH YEAH YOU CALL NTQUERYSYSTEMINFORMATION APPARENTLY.
// OH WAIT. THAT RETURNS INFO FOR *EVERY* PROCESS ON THE FUCKING SYSTEM.
// SO YES. IF YOU LOOK UP PROCESS.PRIVATEMEMORYSIZE64, IT HAS TO SEARCH THROUGH *EVERY PROCESS ON THE FUCKING SYSTEM* TO FIND THE PROCESS IT'S LOOKING FOR.
// THIS TAKES MORE THAN 2 FUCKING MILLISECONDS PER LOOKUP ON MY SYSTEM.
// IF THE TITLE BAR ON THE CONSOLE WAS UPDATED EVERY TICK INSTEAD OF ONCE PER FRAME, THIS WOULD LITERALLY BE MOST OF THE IDLE SERVER CPU USAGE.
// ***ARE YOU FUCKING KIDDING ME ????***
// OH YEAH, NTQUERYSYSTEMINFORMATION IS DOCUMENTED AS AN UNSTABLE API THAT CAN CHANGE AT ANY TIME, APPARENTLY.
// .NET FEELS IT'S FINE TO USE THAT (YEAH GUESS THEY'RE NEVER REMOVING IT FROM WINDOWS LMAO)
// ARE YOU FUCKING TELLING ME THE .NET TEAM COULDN'T HAVE WALKED TO THE OTHER SIDE OF THE OFFICE AND TOLD THE KERNEL TEAM TO ADD A VERSION OF NTQUERYSYSTEMINFORMATION THAT FETCHES INFO FOR A SINGLE PROCESS?
// THEY THOUGHT THIS SHIT WAS FUCKING ACCEPTABLE?
// JESUS FUCKING CHRIST.
// ANYWAYS, THIS EXTENSION METHOD USES GETPROCESSMEMORYINFO() INSTEAD BECAUSE THAT'S PROBABLY NOT FUCKING MORONIC.
//
PROCESS_MEMORY_COUNTERS_EX counters;
if (GetProcessMemoryInfo(process.Handle, &counters, sizeof(PROCESS_MEMORY_COUNTERS_EX)) == 0)
return 0;
var count = counters.PrivateUsage;
return count;
}
private struct PROCESS_MEMORY_COUNTERS_EX
{
public int cb;
public int PageFaultCount;
public nint PeakWorkingSetSize;
public nint WorkingSetSize;
public nint QuotaPeakPagedPoolUsage;
public nint QuotaPagedPoolUsage;
public nint QuotaPeakNonPagedPoolUsage;
public nint QuotaNonPagedPoolUsage;
public nint PagefileUsage;
public nint PeakPagefileUsage;
public nint PrivateUsage;
}
// ReSharper disable once StringLiteralTypo
[DllImport("psapi.dll", SetLastError = true)]
private static extern int GetProcessMemoryInfo(
IntPtr Process,
PROCESS_MEMORY_COUNTERS_EX* ppsmemCounters,
int cb);
}
}