mirror of
https://github.com/ClassiCube/MCGalaxy.git
synced 2025-09-15 10:18:43 -04:00
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:
parent
d8da8b6d13
commit
02509ef3b9
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user