Updated the Stat Tracker to use System.Net.Http types.
Very similar to the previous Update Manager changes. The stat tracker now uses asynchronous threaded calls and manages exceptions better
This commit is contained in:
parent
26a5b00567
commit
b3a2b24daa
5 changed files with 198 additions and 104 deletions
27
TShockAPI/Extensions/ExceptionExt.cs
Normal file
27
TShockAPI/Extensions/ExceptionExt.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
namespace TShockAPI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for Exceptions
|
||||
/// </summary>
|
||||
public static class ExceptionExt
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds a formatted string containing the messages of the given exception, and any inner exceptions it contains
|
||||
/// </summary>
|
||||
/// <param name="ex"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,67 +16,139 @@ 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.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
|
||||
{
|
||||
/// <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 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}";
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="StatTracker"/>
|
||||
/// </summary>
|
||||
public StatTracker()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
/// <summary>
|
||||
/// Starts the stat tracker
|
||||
/// </summary>
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ namespace TShockAPI
|
|||
public static RestManager RestManager;
|
||||
/// <summary>Utils - Static reference to the utilities class, which contains a variety of utility functions.</summary>
|
||||
public static Utils Utils = Utils.Instance;
|
||||
/// <summary>StatTracker - Static reference to the stat tracker, which is created immediately when declared.</summary>
|
||||
/// <summary>StatTracker - Static reference to the stat tracker, which sends some server metrics every 5 minutes.</summary>
|
||||
public static StatTracker StatTracker = new StatTracker();
|
||||
/// <summary>UpdateManager - Static reference to the update checker, which checks for updates and notifies server admins of updates.</summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>ComputeMaxStyles - Computes the max styles...</summary>
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@
|
|||
<Compile Include="DB\ProjectileManager.cs" />
|
||||
<Compile Include="DB\RegionManager.cs" />
|
||||
<Compile Include="DB\TileManager.cs" />
|
||||
<Compile Include="Extensions\ExceptionExt.cs" />
|
||||
<Compile Include="Hooks\AccountHooks.cs" />
|
||||
<Compile Include="Hooks\GeneralHooks.cs" />
|
||||
<Compile Include="Hooks\PlayerHooks.cs" />
|
||||
|
|
@ -200,7 +201,7 @@
|
|||
</PropertyGroup>
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<UserProperties BuildVersion_UpdateAssemblyVersion="True" BuildVersion_UpdateFileVersion="True" BuildVersion_BuildAction="Both" BuildVersion_BuildVersioningStyle="None.None.None.MonthAndDayStamp" BuildVersion_StartDate="2011/6/17" BuildVersion_IncrementBeforeBuild="False" />
|
||||
<UserProperties BuildVersion_IncrementBeforeBuild="False" BuildVersion_StartDate="2011/6/17" BuildVersion_BuildVersioningStyle="None.None.None.MonthAndDayStamp" BuildVersion_BuildAction="Both" BuildVersion_UpdateFileVersion="True" BuildVersion_UpdateAssemblyVersion="True" />
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ using Newtonsoft.Json;
|
|||
using Microsoft.Xna.Framework;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using TShockAPI.Extensions;
|
||||
|
||||
namespace TShockAPI
|
||||
{
|
||||
|
|
@ -32,7 +33,7 @@ namespace TShockAPI
|
|||
/// </summary>
|
||||
public class UpdateManager
|
||||
{
|
||||
private string updateUrl = "http://update.tshock.co/latest/";
|
||||
private const string UpdateUrl = "http://update.tshock.co/latest/";
|
||||
private HttpClient _client = new HttpClient();
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -48,15 +49,20 @@ namespace TShockAPI
|
|||
//5 second timeout
|
||||
_client.Timeout = new TimeSpan(0, 0, 5);
|
||||
|
||||
Thread t = new Thread(async () => {
|
||||
do {
|
||||
await CheckForUpdatesAsync(null);
|
||||
Thread t = new Thread(async () =>
|
||||
{
|
||||
do
|
||||
{
|
||||
//Sleep for X minutes
|
||||
await Task.Delay(1000 * 60 * CheckXMinutes);
|
||||
//Then check again
|
||||
await CheckForUpdatesAsync(null);
|
||||
} while (true);
|
||||
});
|
||||
|
||||
t.Name = "TShock Update Thread";
|
||||
t.IsBackground = true;
|
||||
|
||||
})
|
||||
{
|
||||
Name = "TShock Update Thread",
|
||||
IsBackground = true
|
||||
};
|
||||
t.Start();
|
||||
}
|
||||
|
||||
|
|
@ -64,14 +70,14 @@ namespace TShockAPI
|
|||
{
|
||||
try
|
||||
{
|
||||
await UpdateCheckAsync(state);
|
||||
CheckXMinutes = 30;
|
||||
await UpdateCheckAsync(state);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Skip this run and check more frequently...
|
||||
|
||||
string msg = BuildExceptionString(ex);
|
||||
string msg = ex.BuildExceptionString();
|
||||
//Give the console a brief
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"UpdateManager warning: {msg}");
|
||||
|
|
@ -81,8 +87,6 @@ namespace TShockAPI
|
|||
TShock.Log.ConsoleError("Retrying in 5 minutes.");
|
||||
CheckXMinutes = 5;
|
||||
}
|
||||
|
||||
Thread.Sleep(CheckXMinutes * 60 * 1000);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -105,7 +109,7 @@ namespace TShockAPI
|
|||
/// <returns></returns>
|
||||
private async Task<Dictionary<string, string>> ServerIsOutOfDateAsync()
|
||||
{
|
||||
var resp = await _client.GetAsync(updateUrl);
|
||||
var resp = await _client.GetAsync(UpdateUrl);
|
||||
if (resp.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
string reason = resp.ReasonPhrase;
|
||||
|
|
@ -150,23 +154,5 @@ namespace TShockAPI
|
|||
player.SendMessage(changes[j], Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Produces a string containing all exception messages in an exception chain.
|
||||
/// </summary>
|
||||
/// <param name="ex"></param>
|
||||
/// <returns></returns>
|
||||
private string BuildExceptionString(Exception ex)
|
||||
{
|
||||
string msg = ex.Message;
|
||||
Exception inner = ex.InnerException;
|
||||
while (inner != null)
|
||||
{
|
||||
msg += $"\r\n\t-> {inner.Message}";
|
||||
inner = inner.InnerException;
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue