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:
White 2017-03-06 21:35:14 +10:30
parent 26a5b00567
commit b3a2b24daa
5 changed files with 198 additions and 104 deletions

View file

@ -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;
}
}