diff --git a/TShockAPI/Extensions/ExceptionExt.cs b/TShockAPI/Extensions/ExceptionExt.cs
new file mode 100644
index 00000000..fcae21e4
--- /dev/null
+++ b/TShockAPI/Extensions/ExceptionExt.cs
@@ -0,0 +1,27 @@
+using System;
+namespace TShockAPI.Extensions
+{
+ ///
+ /// Extensions for Exceptions
+ ///
+ public static class ExceptionExt
+ {
+ ///
+ /// Builds a formatted string containing the messages of the given exception, and any inner exceptions it contains
+ ///
+ ///
+ ///
+ public static string BuildExceptionString(this Exception ex)
+ {
+ string msg = ex.Message;
+ Exception inner = ex.InnerException;
+ while (inner != null)
+ {
+ msg += $"\r\n\t-> {inner.Message}";
+ inner = inner.InnerException;
+ }
+
+ return msg;
+ }
+ }
+}
diff --git a/TShockAPI/StatTracker.cs b/TShockAPI/StatTracker.cs
index 24913e83..927986f8 100644
--- a/TShockAPI/StatTracker.cs
+++ b/TShockAPI/StatTracker.cs
@@ -16,67 +16,139 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
+using System;
using System.Net;
using System.Threading;
-using System.IO;
using System.Web;
using TerrariaApi.Server;
using System.Diagnostics;
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 bool failed;
- private bool initialized;
- private long totalMem;
+ 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()
{
-
}
- public void Initialize()
+ ///
+ /// Starts the stat tracker
+ ///
+ public void Start()
{
- if (!initialized && !OptOut)
+ if (OptOut)
{
- initialized = true;
- serverId = Guid.NewGuid().ToString(); // Gets reset every server restart
- // ThreadPool.QueueUserWorkItem(SendUpdate);
- Thread t = new Thread(() => {
- do {
- Thread.Sleep(1000 * 60 * 5);
- SendUpdate(null);
- } while(true);
- });
- t.IsBackground = true;
- t.Name = "TShock Stat Tracker Thread";
- t.Start();
+ //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 void SendUpdate(object info)
+ private JsonData PrepareJsonData()
{
- JsonData data;
-
- if(ServerApi.RunningMono)
+ if (ServerApi.RunningMono)
{
- var pc = new PerformanceCounter("Mono Memory", "Total Physical Memory");
- totalMem = (pc.RawValue / 1024 / 1024 / 1024);
- data = new JsonData
+ if (totalMem == 0)
+ {
+ //Only do this if we haven't already cached the total physicaly memory
+ var pc = new PerformanceCounter("Mono Memory", "Total Physical Memory");
+ totalMem = (pc.RawValue / 1024 / 1024 / 1024);
+ }
+
+ return new JsonData
{
port = Terraria.Netplay.ListenPort,
currentPlayers = TShock.Utils.ActivePlayers(),
@@ -88,62 +160,70 @@ namespace TShockAPI
serverId = serverId,
mono = true
};
- } else
+ }
+
+ if (totalMem == 0)
{
+ //Again, only call this if it hasn't previously been cached
GetPhysicallyInstalledSystemMemory(out totalMem);
totalMem = (totalMem / 1024 / 1024); // Super hardcore maths to convert to Gb from Kb
- data = new JsonData
- {
- port = Terraria.Netplay.ListenPort,
- currentPlayers = TShock.Utils.ActivePlayers(),
- maxPlayers = TShock.Config.MaxSlots,
- systemRam = totalMem,
- version = TShock.VersionNum.ToString(),
- terrariaVersion = Terraria.Main.versionNumber2,
- providerId = ProviderToken,
- serverId = serverId,
- mono = false
- };
}
- var serialized = Newtonsoft.Json.JsonConvert.SerializeObject(data);
- var encoded = HttpUtility.UrlEncode(serialized);
- var uri = String.Format("http://stats.tshock.co/submit/{0}", encoded);
- var client = (HttpWebRequest)WebRequest.Create(uri);
- client.Timeout = 5000;
- try
+ return new JsonData
{
- using (var resp = TShock.Utils.GetResponseNoException(client))
- {
- if (resp.StatusCode != HttpStatusCode.OK)
- {
- throw new IOException("Server did not respond with an OK.");
- }
-
- failed = false;
- }
- }
- catch (Exception e)
- {
- if (!failed)
- {
- TShock.Log.ConsoleError("StatTracker Exception: {0}", e);
- failed = true;
- }
- }
+ port = Terraria.Netplay.ListenPort,
+ currentPlayers = TShock.Utils.ActivePlayers(),
+ maxPlayers = TShock.Config.MaxSlots,
+ systemRam = totalMem,
+ version = TShock.VersionNum.ToString(),
+ terrariaVersion = Terraria.Main.versionNumber2,
+ providerId = ProviderToken,
+ serverId = serverId,
+ mono = false
+ };
}
}
+ ///
+ /// 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;
+ ///
+ /// 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;
}
}
diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs
index 481cb903..dcf3272e 100644
--- a/TShockAPI/TShock.cs
+++ b/TShockAPI/TShock.cs
@@ -118,7 +118,7 @@ namespace TShockAPI
public static RestManager RestManager;
/// Utils - Static reference to the utilities class, which contains a variety of utility functions.
public static Utils Utils = Utils.Instance;
- /// StatTracker - Static reference to the stat tracker, which is created immediately when declared.
+ /// StatTracker - Static reference to the stat tracker, which sends some server metrics every 5 minutes.
public static StatTracker StatTracker = new StatTracker();
/// UpdateManager - Static reference to the update checker, which checks for updates and notifies server admins of updates.
public static UpdateManager UpdateManager;
@@ -768,12 +768,12 @@ namespace TShockAPI
}
case "--provider-token":
{
- TShock.StatTracker.ProviderToken = parms[++i];
+ StatTracker.ProviderToken = parms[++i];
break;
}
case "--stats-optout":
{
- TShock.StatTracker.OptOut = true;
+ StatTracker.OptOut = true;
break;
}
case "--no-restart":
@@ -894,7 +894,7 @@ namespace TShockAPI
}
UpdateManager = new UpdateManager();
- StatTracker.Initialize();
+ StatTracker.Start();
}
/// ComputeMaxStyles - Computes the max styles...
diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj
index a4350b70..aa0d44b4 100644
--- a/TShockAPI/TShockAPI.csproj
+++ b/TShockAPI/TShockAPI.csproj
@@ -86,6 +86,7 @@
+
@@ -200,7 +201,7 @@
-
+