census update

This commit is contained in:
rootdarkarchon
2023-11-17 02:10:45 +01:00
parent b4105f3d01
commit a1e82bcdb6
7 changed files with 179 additions and 4 deletions

View File

@@ -29,8 +29,10 @@ services:
restart: on-failure
ports:
- 6000:6000/tcp
- 6050:6050/tcp
environment:
MareSynchronos__CdnFullUrl: "${DEV_MARE_CDNURL}"
MareSynchronos__XIVAPIKey: "${DEV_MARE_XIVAPIKEY}"
DOTNET_USE_POLLING_FILE_WATCHER: 1
volumes:
- ../config/standalone/server-standalone.json:/opt/MareSynchronosServer/appsettings.json

View File

@@ -44,7 +44,8 @@
"MaxGroupUserCount": 100,
"PurgeUnusedAccounts": false,
"PurgeUnusedAccountsPeriodInDays": 14,
"ExpectedClientVersion": "0.8.0"
"ExpectedClientVersion": "0.9.0",
"XIVAPIKey": ""
},
"AllowedHosts": "*",
"Kestrel": {

Submodule MareAPI updated: edaa0ddb51...47ff5235d6

View File

@@ -152,7 +152,7 @@ public partial class MareHub
}
[Authorize(Policy = "Identified")]
public async Task<List<OnlineUserIdentDto>> UserGetOnlinePairs()
public async Task<List<OnlineUserIdentDto>> UserGetOnlinePairs(CensusDataDto? censusData)
{
_logger.LogCallInfo();
@@ -161,6 +161,8 @@ public partial class MareHub
await SendOnlineToAllPairedUsers().ConfigureAwait(false);
_mareCensus.PublishStatistics(UserUID, censusData);
return pairs.Select(p => new OnlineUserIdentDto(new UserData(p.Key), p.Value)).ToList();
}
@@ -278,6 +280,8 @@ public partial class MareHub
await Clients.Users(recipientUids).Client_UserReceiveCharacterData(new OnlineUserCharaDataDto(new UserData(UserUID), dto.CharaData)).ConfigureAwait(false);
_mareCensus.PublishStatistics(UserUID, dto.CensusDataDto);
_mareMetrics.IncCounter(MetricsAPI.CounterUserPushData);
_mareMetrics.IncCounter(MetricsAPI.CounterUserPushDataTo, recipientUids.Count);
}

View File

@@ -30,6 +30,7 @@ public partial class MareHub : Hub<IMareHub>, IMareHub
private readonly int _maxGroupUserCount;
private readonly IRedisDatabase _redis;
private readonly OnlineSyncedPairCacheService _onlineSyncedPairCacheService;
private readonly MareCensus _mareCensus;
private readonly Uri _fileServerAddress;
private readonly Version _expectedClientVersion;
private readonly Lazy<MareDbContext> _dbContextLazy;
@@ -38,7 +39,7 @@ public partial class MareHub : Hub<IMareHub>, IMareHub
public MareHub(MareMetrics mareMetrics,
IDbContextFactory<MareDbContext> mareDbContextFactory, ILogger<MareHub> logger, SystemInfoService systemInfoService,
IConfigurationService<ServerConfiguration> configuration, IHttpContextAccessor contextAccessor,
IRedisDatabase redisDb, OnlineSyncedPairCacheService onlineSyncedPairCacheService)
IRedisDatabase redisDb, OnlineSyncedPairCacheService onlineSyncedPairCacheService, MareCensus mareCensus)
{
_mareMetrics = mareMetrics;
_systemInfoService = systemInfoService;
@@ -51,6 +52,7 @@ public partial class MareHub : Hub<IMareHub>, IMareHub
_contextAccessor = contextAccessor;
_redis = redisDb;
_onlineSyncedPairCacheService = onlineSyncedPairCacheService;
_mareCensus = mareCensus;
_logger = new MareHubLogger(this, logger);
_dbContextLazy = new Lazy<MareDbContext>(() => mareDbContextFactory.CreateDbContext());
}
@@ -158,6 +160,8 @@ public partial class MareHub : Hub<IMareHub>, IMareHub
await RemoveUserFromRedis().ConfigureAwait(false);
_mareCensus.ClearStatistics(UserUID);
await SendOfflineToAllPairedUsers().ConfigureAwait(false);
DbContext.RemoveRange(DbContext.Files.Where(f => !f.Uploaded && f.UploaderUID == UserUID));

View File

@@ -0,0 +1,161 @@
using MareSynchronos.API.Dto.User;
using Prometheus;
using System.Collections.Concurrent;
using System.Globalization;
using System.Text.Json;
namespace MareSynchronosServer.Services;
public class MareCensus : IHostedService
{
private record CensusEntry(ushort WorldId, short Race, short Subrace, short Gender)
{
public static CensusEntry FromDto(CensusDataDto dto)
{
return new CensusEntry(dto.WorldId, dto.RaceId, dto.TribeId, dto.Gender);
}
}
private readonly ConcurrentDictionary<string, CensusEntry> _censusEntries = new(StringComparer.Ordinal);
private readonly Dictionary<short, string> _dcs = new();
private readonly Dictionary<short, string> _gender = new();
private readonly ILogger<MareCensus> _logger;
private readonly Dictionary<short, string> _races = new();
private readonly Dictionary<short, string> _tribes = new();
private readonly Dictionary<ushort, (string, short)> _worlds = new();
private readonly string _xivApiKey;
private Gauge? _gauge;
public MareCensus(ILogger<MareCensus> logger, string xivApiKey)
{
_logger = logger;
_xivApiKey = xivApiKey;
}
private bool Initialized => _gauge != null;
public void ClearStatistics(string uid)
{
if (!Initialized) return;
if (_censusEntries.Remove(uid, out var censusEntry))
{
ModifyGauge(censusEntry, increase: false);
}
}
public void PublishStatistics(string uid, CensusDataDto? censusDataDto)
{
if (!Initialized || censusDataDto == null) return;
var newEntry = CensusEntry.FromDto(censusDataDto);
if (_censusEntries.TryGetValue(uid, out var entry))
{
if (entry != newEntry)
{
ModifyGauge(entry, increase: false);
ModifyGauge(newEntry, increase: true);
_censusEntries[uid] = newEntry;
}
}
else
{
_censusEntries[uid] = newEntry;
ModifyGauge(newEntry, increase: true);
}
}
public async Task StartAsync(CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(_xivApiKey)) return;
_logger.LogInformation("Loading XIVAPI data");
using HttpClient client = new HttpClient();
Dictionary<ushort, short> worldDcs = new();
var dcs = await client.GetStringAsync("https://xivapi.com/worlddcgrouptype?private_key" + _xivApiKey, cancellationToken).ConfigureAwait(false);
using var dcsJson = JsonSerializer.Deserialize<JsonElement>(dcs).GetProperty("Results").EnumerateArray();
foreach (var dcValue in dcsJson)
{
var id = dcValue.GetProperty("ID").GetInt16();
var name = dcValue.GetProperty("Name").GetString();
_dcs[id] = name;
_logger.LogInformation("DC: ID: {id}, Name: {name}", id, name);
var dcData = await client.GetStringAsync("https://xivapi.com/worlddcgrouptype/" + id.ToString(CultureInfo.InvariantCulture) + "?private_key=" + _xivApiKey, cancellationToken).ConfigureAwait(false);
if (JsonSerializer.Deserialize<JsonElement>(dcData).TryGetProperty("GameContentLinks", out var gameContentLinks))
{
if (gameContentLinks.ValueKind == JsonValueKind.Object && gameContentLinks.TryGetProperty("World", out var worldProp))
{
using var json = worldProp.GetProperty("DataCenter").EnumerateArray();
foreach (var world in json)
{
worldDcs[(ushort)world.GetInt32()] = id;
}
}
}
await Task.Delay(TimeSpan.FromMilliseconds(100)).ConfigureAwait(false);
}
var worlds = await client.GetStringAsync("https://xivapi.com/world?limit=1000&private_key" + _xivApiKey, cancellationToken).ConfigureAwait(false);
using var worldJson = JsonSerializer.Deserialize<JsonElement>(worlds).GetProperty("Results").EnumerateArray();
foreach (var worldValue in worldJson)
{
var id = (ushort)worldValue.GetProperty("ID").GetInt32();
var name = worldValue.GetProperty("Name").GetString();
if (worldDcs.TryGetValue(id, out var dc))
{
_worlds[(ushort)id] = (name, dc);
_logger.LogInformation("World: ID: {id}, Name: {name}, DC: {dc}", id, name, dc);
}
}
var races = await client.GetStringAsync("https://xivapi.com/race?private_key" + _xivApiKey, cancellationToken).ConfigureAwait(false);
using var racesJson = JsonSerializer.Deserialize<JsonElement>(races).GetProperty("Results").EnumerateArray();
foreach (var racesValue in racesJson)
{
var id = racesValue.GetProperty("ID").GetInt16();
var name = racesValue.GetProperty("Name").GetString();
_races[id] = name;
_logger.LogInformation("Race: ID: {id}, Name: {name}", id, name);
}
var tribe = await client.GetStringAsync("https://xivapi.com/tribe?private_key=" + _xivApiKey, cancellationToken).ConfigureAwait(false);
using var tribeJson = JsonSerializer.Deserialize<JsonElement>(tribe).GetProperty("Results").EnumerateArray();
foreach (var tribeValue in tribeJson)
{
var id = tribeValue.GetProperty("ID").GetInt16();
var name = tribeValue.GetProperty("Name").GetString();
_tribes[id] = name;
_logger.LogInformation("Tribe: ID: {id}, Name: {name}", id, name);
}
_gender[0] = "Male";
_gender[1] = "Female";
_gauge = Metrics.CreateGauge("mare_census", "mare informational census data", new[] { "dc", "world", "gender", "race", "subrace" });
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
private void ModifyGauge(CensusEntry censusEntry, bool increase)
{
var subraceSuccess = _tribes.TryGetValue(censusEntry.Subrace, out var subrace);
var raceSuccess = _races.TryGetValue(censusEntry.Race, out var race);
var worldSuccess = _worlds.TryGetValue(censusEntry.WorldId, out var world);
var genderSuccess = _gender.TryGetValue(censusEntry.Gender, out var gender);
if (subraceSuccess && raceSuccess && worldSuccess && genderSuccess && _dcs.TryGetValue(world.Item2, out var dc))
{
if (increase)
_gauge.WithLabels(dc, world.Item1, gender, race, subrace).Inc();
else
_gauge.WithLabels(dc, world.Item1, gender, race, subrace).Dec();
}
}
}

View File

@@ -95,6 +95,9 @@ public class Startup
// configure services based on main server status
ConfigureServicesBasedOnShardType(services, mareConfig, isMainServer);
services.AddSingleton(s => new MareCensus(s.GetRequiredService<ILogger<MareCensus>>(), mareConfig.GetValue("XIVAPIKey", string.Empty)));
services.AddHostedService(p => p.GetRequiredService<MareCensus>());
if (isMainServer)
{
services.AddSingleton<UserCleanupService>();