census update
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -44,7 +44,8 @@
|
||||
"MaxGroupUserCount": 100,
|
||||
"PurgeUnusedAccounts": false,
|
||||
"PurgeUnusedAccountsPeriodInDays": 14,
|
||||
"ExpectedClientVersion": "0.8.0"
|
||||
"ExpectedClientVersion": "0.9.0",
|
||||
"XIVAPIKey": ""
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Kestrel": {
|
||||
|
||||
2
MareAPI
2
MareAPI
Submodule MareAPI updated: edaa0ddb51...47ff5235d6
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
161
MareSynchronosServer/MareSynchronosServer/Services/MareCensus.cs
Normal file
161
MareSynchronosServer/MareSynchronosServer/Services/MareCensus.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
|
||||
Reference in New Issue
Block a user