Rewrite 'all processes CPU usage' in /sinfo to not use performance counters, addresses #712

Fixes /sinfo not working on Linux when using .NET core
This commit is contained in:
UnknownShadow200 2022-07-16 11:13:10 +10:00
parent d8da8b6d13
commit 02509ef3b9
2 changed files with 150 additions and 95 deletions

View File

@ -1,103 +1,101 @@
/*
Copyright 2010 MCSharp team (Modified for use with MCZall/MCLawl/MCGalaxy)
Dual-licensed under the Educational Community License, Version 2.0 and
the GNU General Public License, Version 3 (the "Licenses"); you may
not use this file except in compliance with the Licenses. You may
obtain a copy of the Licenses at
http://www.opensource.org/licenses/ecl2.php
http://www.gnu.org/licenses/gpl-3.0.html
Unless required by applicable law or agreed to in writing,
software distributed under the Licenses are distributed on an "AS IS"
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the Licenses for the specific language governing
permissions and limitations under the Licenses.
*/
using System;
using System.Diagnostics;
using System.Threading;
using MCGalaxy.SQL;
namespace MCGalaxy.Commands.Info {
public sealed class CmdServerInfo : Command2 {
public override string name { get { return "ServerInfo"; } }
public override string shortcut { get { return "SInfo"; } }
public override string type { get { return CommandTypes.Information; } }
public override bool UseableWhenFrozen { get { return true; } }
public override CommandAlias[] Aliases {
get { return new[] { new CommandAlias("Host"), new CommandAlias("ZAll") }; }
}
public override CommandPerm[] ExtraPerms {
get { return new[] { new CommandPerm(LevelPermission.Admin, "can see server CPU and memory usage") }; }
/*
Copyright 2010 MCSharp team (Modified for use with MCZall/MCLawl/MCGalaxy)
Dual-licensed under the Educational Community License, Version 2.0 and
the GNU General Public License, Version 3 (the "Licenses"); you may
not use this file except in compliance with the Licenses. You may
obtain a copy of the Licenses at
http://www.opensource.org/licenses/ecl2.php
http://www.gnu.org/licenses/gpl-3.0.html
Unless required by applicable law or agreed to in writing,
software distributed under the Licenses are distributed on an "AS IS"
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the Licenses for the specific language governing
permissions and limitations under the Licenses.
*/
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using MCGalaxy.SQL;
namespace MCGalaxy.Commands.Info
{
public sealed class CmdServerInfo : Command2
{
public override string name { get { return "ServerInfo"; } }
public override string shortcut { get { return "SInfo"; } }
public override string type { get { return CommandTypes.Information; } }
public override bool UseableWhenFrozen { get { return true; } }
public override CommandAlias[] Aliases {
get { return new[] { new CommandAlias("Host"), new CommandAlias("ZAll") }; }
}
public override void Use(Player p, string message, CommandData data) {
int count = Database.CountRows("Players");
p.Message("About &b{0}&S", Server.Config.Name);
p.Message(" &a{0} &Splayers total. (&a{1} &Sonline, &8{2} banned&S)",
count, PlayerInfo.GetOnlineCanSee(p, data.Rank).Count, Group.BannedRank.Players.Count);
p.Message(" &a{0} &Slevels total (&a{1} &Sloaded). Currency is &3{2}&S.",
public override CommandPerm[] ExtraPerms {
get { return new[] { new CommandPerm(LevelPermission.Admin, "can see server CPU and memory usage") }; }
}
public override void Use(Player p, string message, CommandData data) {
int count = Database.CountRows("Players");
p.Message("About &b{0}&S", Server.Config.Name);
p.Message(" &a{0} &Splayers total. (&a{1} &Sonline, &8{2} banned&S)",
count, PlayerInfo.GetOnlineCanSee(p, data.Rank).Count, Group.BannedRank.Players.Count);
p.Message(" &a{0} &Slevels total (&a{1} &Sloaded). Currency is &3{2}&S.",
LevelInfo.AllMapFiles().Length, LevelInfo.Loaded.Count, Server.Config.Currency);
TimeSpan up = DateTime.UtcNow - Server.StartTime;
p.Message(" Been up for &a{0}&S, running &b{1} &a{2} &f" + Updater.SourceURL,
up.Shorten(true), Server.SoftwareName, Server.Version);
int updateInterval = 1000 / Server.Config.PositionUpdateInterval;
TimeSpan up = DateTime.UtcNow - Server.StartTime;
p.Message(" Been up for &a{0}&S, running &b{1} &a{2} &f" + Updater.SourceURL,
up.Shorten(true), Server.SoftwareName, Server.Version);
int updateInterval = 1000 / Server.Config.PositionUpdateInterval;
p.Message(" Player positions are updated &a{0} &Stimes/second", updateInterval);
string owner = Server.Config.OwnerName;
if (!owner.CaselessEq("Notch") && !owner.CaselessEq("the owner")) {
p.Message(" Owner is &3{0}", owner);
}
if (HasExtraPerm(p, data.Rank, 1)) OutputResourceUsage(p);
}
static PerformanceCounter allPCounter;
static DateTime startTime;
static TimeSpan startCPU;
static void OutputResourceUsage(Player p) {
string owner = Server.Config.OwnerName;
if (!owner.CaselessEq("Notch") && !owner.CaselessEq("the owner")) {
p.Message(" Owner is &3{0}", owner);
}
if (HasExtraPerm(p, data.Rank, 1)) OutputResourceUsage(p);
}
static DateTime startTime;
static TimeSpan startCPU;
static void OutputResourceUsage(Player p) {
Process proc = Process.GetCurrentProcess();
p.Message("Measuring resource usage...one second");
p.Message("Measuring resource usage...one second");
if (startTime == default(DateTime)) {
startTime = DateTime.UtcNow;
startCPU = proc.TotalProcessorTime;
}
TimeSpan begCPU = proc.TotalProcessorTime;
try
{
if (allPCounter == null) {
allPCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
allPCounter.BeginInit();
allPCounter.NextValue();
}
} catch (PlatformNotSupportedException) {
// .NET core throws this error on Linux
}
// measure CPU usage over one second
Thread.Sleep(1000);
TimeSpan endCPU = proc.TotalProcessorTime;
}
p.Message("&a{0}% &SCPU usage now, &a{1}% &Soverall",
MeasureCPU(begCPU, endCPU, TimeSpan.FromSeconds(1)),
CPUTime beg = MeasureAllCPUTime();
TimeSpan begCPU = proc.TotalProcessorTime;
// measure CPU usage over one second
Thread.Sleep(1000);
TimeSpan endCPU = proc.TotalProcessorTime;
CPUTime end = MeasureAllCPUTime();
p.Message("&a{0}% &SCPU usage now, &a{1}% &Soverall",
MeasureCPU(begCPU, endCPU, TimeSpan.FromSeconds(1)),
MeasureCPU(startCPU, endCPU, DateTime.UtcNow - startTime));
p.Message(" &a{0}% &Sby all processes",
allPCounter == null ? "(unknown)" : allPCounter.NextValue().ToString("F2"));
ulong idl = (end.IdleTime - beg.IdleTime);
ulong sys = (end.UserTime - beg.UserTime) + (end.KernelTime - beg.KernelTime);
double cpu = sys * 100.0 / (sys + idl);
p.Message(" &a{0}% &Sby all processes",
double.IsNaN(cpu) ? "(unknown)" : cpu.ToString("F2"));
// Private Bytes = memory the process has reserved just for itself
int memory = (int)Math.Round(proc.PrivateMemorySize64 / 1048576.0);
int memory = (int)Math.Round(proc.PrivateMemorySize64 / 1048576.0);
p.Message("&a{0} &Sthreads, using &a{1} &Smegabytes of memory",
proc.Threads.Count, memory);
}
proc.Threads.Count, memory);
}
static string MeasureCPU(TimeSpan beg, TimeSpan end, TimeSpan interval) {
if (end < beg) return "0.00"; // TODO: Can this ever happen
int cores = Math.Max(1, Environment.ProcessorCount);
@ -105,11 +103,69 @@ namespace MCGalaxy.Commands.Info {
TimeSpan used = end - beg;
double elapsed = 100.0 * (used.TotalSeconds / interval.TotalSeconds);
return (elapsed / cores).ToString("F2");
}
public override void Help(Player p) {
p.Message("&T/ServerInfo");
p.Message("&HDisplays the server information.");
}
}
}
}
static CPUTime MeasureAllCPUTime()
{
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
return MeasureAllWindows();
return MeasureAllLinux();
}
static CPUTime MeasureAllWindows() {
CPUTime all = default(CPUTime);
GetSystemTimes(out all.IdleTime, out all.KernelTime, out all.UserTime);
// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getsystemtimes
// lpKernelTime - "... This time value also includes the amount of time the system has been idle."
all.KernelTime -= all.IdleTime;
return all;
}
static CPUTime MeasureAllLinux() {
// https://stackoverflow.com/questions/15145241/is-there-an-equivalent-to-the-windows-getsystemtimes-function-in-linux
try {
using (StreamReader r = new StreamReader("/proc/stat"))
{
string line = r.ReadLine();
if (line.StartsWith("cpu ")) return ParseCpuLine(line);
}
} catch (FileNotFoundException) { }
return default(CPUTime);
}
static CPUTime ParseCpuLine(string line)
{
string[] bits = line.SplitSpaces();
ulong user = ulong.Parse(bits[2]);
ulong nice = ulong.Parse(bits[3]);
ulong kern = ulong.Parse(bits[4]);
ulong idle = ulong.Parse(bits[5]);
// TODO interrupt time too?
CPUTime all;
all.UserTime = user + nice;
all.KernelTime = kern;
all.IdleTime = idle;
return all;
}
[DllImport("kernel32.dll")]
internal static extern int GetSystemTimes(out ulong idleTime, out ulong kernelTime, out ulong userTime);
struct CPUTime
{
public ulong IdleTime;
public ulong KernelTime;
public ulong UserTime;
}
public override void Help(Player p) {
p.Message("&T/ServerInfo");
p.Message("&HDisplays the server information.");
}
}
}

View File

@ -6,7 +6,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.CodeDom" Version="4.5.0" />
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="4.5.0" />
<PackageReference Include="System.Drawing.Common" Version="4.5.0" />
<PackageReference Include="MySql.Data" Version="6.7.9" />
</ItemGroup>