/* 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 . */ 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 { /// /// StatTracker class for tracking various server metrics /// public class StatTracker { /// /// Calls the GetPhysicallyInstalledSystemMemory Windows API function, returning the amount of RAM installed on the host PC /// /// The amount of memory (in kilobytes) present on the host PC /// [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] static extern bool GetPhysicallyInstalledSystemMemory(out long totalMemInKb); /// /// A provider token set on the command line. Used by the stats server to group providers /// public string ProviderToken = ""; /// /// Whether or not to opt out of stat tracking /// 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}"; /// /// Creates a new instance of /// public StatTracker() { } /// /// Starts the stat tracker /// 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; } private 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; } } /// /// Holding information regarding loaded plugins /// public struct PluginItem { /// /// Plugin name /// public string name; /// /// Assembly version /// public string version; /// /// Api version or UNKNOWN if attribute is missing, which is currently impossible /// public string apiVersion; } /// /// Contains JSON-Serializable information about a server /// public struct JsonData { /// /// The port the server is running on /// public int port; /// /// The number of players currently on the server /// public int currentPlayers; /// /// The maximum number of player slots available on the server /// public int maxPlayers; /// /// The amount of RAM installed on the server's host PC /// public long systemRam; /// /// The TShock version being used by the server /// public string version; /// /// Whether or not server was started with --ignoreversion /// public bool ignorePluginVersion; /// /// List of loaded plugins and version name: /// public PluginItem[] loadedPlugins; /// /// The Terraria version supported by the server /// public string terrariaVersion; /// /// The provider ID set for the server /// public string providerId; /// /// The server ID set for the server /// public string serverId; /// /// Whether or not the server is running with Mono /// public bool mono; } }