270 lines
No EOL
7.4 KiB
C#
270 lines
No EOL
7.4 KiB
C#
/*
|
|
TShock, a server mod for Terraria
|
|
Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
using System;
|
|
using System.Net;
|
|
using System.Threading;
|
|
using System.Web;
|
|
using TerrariaApi.Server;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Net.Http;
|
|
using System.Threading.Tasks;
|
|
using TShockAPI.Extensions;
|
|
|
|
|
|
namespace TShockAPI
|
|
{
|
|
/// <summary>
|
|
/// StatTracker class for tracking various server metrics
|
|
/// </summary>
|
|
public class StatTracker
|
|
{
|
|
/// <summary>
|
|
/// Calls the GetPhysicallyInstalledSystemMemory Windows API function, returning the amount of RAM installed on the host PC
|
|
/// </summary>
|
|
/// <param name="totalMemInKb">The amount of memory (in kilobytes) present on the host PC</param>
|
|
/// <returns></returns>
|
|
[DllImport("kernel32.dll")]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
static extern bool GetPhysicallyInstalledSystemMemory(out long totalMemInKb);
|
|
|
|
/// <summary>
|
|
/// A provider token set on the command line. Used by the stats server to group providers
|
|
/// </summary>
|
|
public string ProviderToken = "";
|
|
/// <summary>
|
|
/// Whether or not to opt out of stat tracking
|
|
/// </summary>
|
|
public bool OptOut = false;
|
|
|
|
private PluginItem[] plugins;
|
|
|
|
private long totalMem = 0;
|
|
private string serverId = "";
|
|
private HttpClient _client;
|
|
private const string TrackerUrl = "http://stats.tshock.co/submit/{0}";
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of <see cref="StatTracker"/>
|
|
/// </summary>
|
|
public StatTracker()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the stat tracker
|
|
/// </summary>
|
|
public void Start()
|
|
{
|
|
if (OptOut)
|
|
{
|
|
//If opting out, return and do not start stat tracking
|
|
return;
|
|
}
|
|
|
|
//HttpClient with a 5 second timeout
|
|
_client = new HttpClient()
|
|
{
|
|
Timeout = new TimeSpan(0, 0, 5)
|
|
};
|
|
serverId = Guid.NewGuid().ToString();
|
|
|
|
Thread t = new Thread(async () =>
|
|
{
|
|
do
|
|
{
|
|
//Wait 5 minutes
|
|
await Task.Delay(1000 * 60 * 5);
|
|
//Then update again
|
|
await SendUpdateAsync();
|
|
} while (true);
|
|
})
|
|
{
|
|
Name = "TShock Stat Tracker Thread",
|
|
IsBackground = true
|
|
};
|
|
t.Start();
|
|
}
|
|
|
|
private async Task SendUpdateAsync()
|
|
{
|
|
JsonData data = PrepareJsonData();
|
|
var serialized = Newtonsoft.Json.JsonConvert.SerializeObject(data);
|
|
var encoded = HttpUtility.UrlEncode(serialized);
|
|
var uri = string.Format(TrackerUrl, encoded);
|
|
try
|
|
{
|
|
HttpResponseMessage msg = await _client.GetAsync(uri);
|
|
if (msg.StatusCode != HttpStatusCode.OK)
|
|
{
|
|
string reason = msg.ReasonPhrase;
|
|
if (string.IsNullOrWhiteSpace(reason))
|
|
{
|
|
reason = "none";
|
|
}
|
|
throw new WebException("Stats server did not respond with an OK. "
|
|
+ $"Server message: [error {msg.StatusCode}] {reason}");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
string msg = ex.BuildExceptionString();
|
|
|
|
//Give the console a brief
|
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
|
Console.WriteLine($"StatTracker warning: {msg}");
|
|
Console.ForegroundColor = ConsoleColor.Gray;
|
|
|
|
//And log the full exception
|
|
TShock.Log.Warn($"StatTracker warning: {ex.ToString()}");
|
|
TShock.Log.ConsoleError("Retrying in 5 minutes.");
|
|
}
|
|
}
|
|
|
|
private JsonData PrepareJsonData()
|
|
{
|
|
return new JsonData()
|
|
{
|
|
port = Terraria.Netplay.ListenPort,
|
|
currentPlayers = TShock.Utils.ActivePlayers(),
|
|
maxPlayers = TShock.Config.MaxSlots,
|
|
systemRam = GetTotalSystemRam(ServerApi.RunningMono),
|
|
version = TShock.VersionNum.ToString(),
|
|
terrariaVersion = Terraria.Main.versionNumber2,
|
|
providerId = ProviderToken,
|
|
serverId = serverId,
|
|
mono = ServerApi.RunningMono,
|
|
ignorePluginVersion = ServerApi.IgnoreVersion,
|
|
loadedPlugins = GetLoadedPlugins()
|
|
};
|
|
}
|
|
|
|
private PluginItem[] GetLoadedPlugins()
|
|
{
|
|
if (plugins != null)
|
|
{
|
|
return plugins; //Return early
|
|
}
|
|
|
|
plugins = new PluginItem[ServerApi.Plugins.Count]; //Initialize with enough room to store the ammount of plugins loaded.
|
|
for (var i = 0; i < ServerApi.Plugins.Count; i++)
|
|
{
|
|
var pluginItem = new PluginItem();
|
|
var apiAttribute = (ApiVersionAttribute)ServerApi.Plugins[i].Plugin.GetType().GetCustomAttributes(typeof(ApiVersionAttribute), false).FirstOrDefault();
|
|
//The current implementation of loading plugins doesn't allow for a plugin to be loaded without an ApiVersion, the UNKNOWN is there incase a change is made to allow it
|
|
pluginItem.apiVersion = apiAttribute?.ApiVersion.ToString() ?? "UNKNOWN";
|
|
pluginItem.name = ServerApi.Plugins[i].Plugin.Name;
|
|
pluginItem.version = ServerApi.Plugins[i].Plugin.Version.ToString();
|
|
plugins[i] = pluginItem;
|
|
}
|
|
return plugins;
|
|
}
|
|
|
|
public long GetTotalSystemRam(bool isMono)
|
|
{
|
|
if (totalMem != 0)
|
|
{
|
|
return totalMem; //Return early
|
|
}
|
|
|
|
if (isMono) //Set totalMem so it can be returned later
|
|
{
|
|
var pc = new PerformanceCounter("Mono Memory", "Total Physical Memory");
|
|
totalMem = (pc.RawValue / 1024 / 1024 / 1024);
|
|
}
|
|
else
|
|
{
|
|
GetPhysicallyInstalledSystemMemory(out totalMem);
|
|
totalMem = (totalMem / 1024 / 1024); // Super hardcore maths to convert to Gb from Kb
|
|
}
|
|
|
|
return totalMem;
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Holding information regarding loaded plugins
|
|
/// </summary>
|
|
public struct PluginItem
|
|
{
|
|
/// <summary>
|
|
/// Plugin name
|
|
/// </summary>
|
|
public string name;
|
|
/// <summary>
|
|
/// Assembly version
|
|
/// </summary>
|
|
public string version;
|
|
/// <summary>
|
|
/// Api version or UNKNOWN if attribute is missing, which is currently impossible
|
|
/// </summary>
|
|
public string apiVersion;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains JSON-Serializable information about a server
|
|
/// </summary>
|
|
public struct JsonData
|
|
{
|
|
/// <summary>
|
|
/// The port the server is running on
|
|
/// </summary>
|
|
public int port;
|
|
/// <summary>
|
|
/// The number of players currently on the server
|
|
/// </summary>
|
|
public int currentPlayers;
|
|
/// <summary>
|
|
/// The maximum number of player slots available on the server
|
|
/// </summary>
|
|
public int maxPlayers;
|
|
/// <summary>
|
|
/// The amount of RAM installed on the server's host PC
|
|
/// </summary>
|
|
public long systemRam;
|
|
/// <summary>
|
|
/// The TShock version being used by the server
|
|
/// </summary>
|
|
public string version;
|
|
/// <summary>
|
|
/// Whether or not server was started with --ignoreversion
|
|
/// </summary>
|
|
public bool ignorePluginVersion;
|
|
/// <summary>
|
|
/// List of loaded plugins and version name:
|
|
/// </summary>
|
|
public PluginItem[] loadedPlugins;
|
|
/// <summary>
|
|
/// The Terraria version supported by the server
|
|
/// </summary>
|
|
public string terrariaVersion;
|
|
/// <summary>
|
|
/// The provider ID set for the server
|
|
/// </summary>
|
|
public string providerId;
|
|
/// <summary>
|
|
/// The server ID set for the server
|
|
/// </summary>
|
|
public string serverId;
|
|
/// <summary>
|
|
/// Whether or not the server is running with Mono
|
|
/// </summary>
|
|
public bool mono;
|
|
}
|
|
} |