optimize index for rating history

update log server to prevent delays or missed information
This commit is contained in:
RaidMax 2019-07-24 19:15:07 -05:00
parent 2542b7de12
commit ac32034910
17 changed files with 904 additions and 97 deletions

View File

@ -6,7 +6,7 @@ namespace IW4MAdmin.Application.API.GameLogServer
[Header("User-Agent", "IW4MAdmin-RestEase")]
public interface IGameLogServer
{
[Get("log/{path}")]
Task<LogInfo> Log([Path] string path);
[Get("log/{path}/{key}")]
Task<LogInfo> Log([Path] string path, [Path] string key);
}
}

View File

@ -13,5 +13,7 @@ namespace IW4MAdmin.Application.API.GameLogServer
public int Length { get; set; }
[JsonProperty("data")]
public string Data { get; set; }
[JsonProperty("next_key")]
public string NextKey { get; set; }
}
}

View File

@ -30,7 +30,8 @@
</ItemGroup>
<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
<ServerGarbageCollection>false</ServerGarbageCollection>
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
<TieredCompilation>true</TieredCompilation>
<AssemblyVersion>2.2.8.1</AssemblyVersion>
<FileVersion>2.2.8.1</FileVersion>

View File

@ -42,8 +42,7 @@ namespace IW4MAdmin.Application.IO
catch (Exception e)
{
_server.Logger.WriteWarning($"Failed to update log event for {_server.EndPoint}");
_server.Logger.WriteDebug($"Exception: {e.Message}");
_server.Logger.WriteDebug($"StackTrace: {e.StackTrace}");
_server.Logger.WriteDebug(e.GetExceptionInfo());
}
}

View File

@ -20,6 +20,7 @@ namespace IW4MAdmin.Application.IO
readonly IGameLogServer Api;
readonly string logPath;
private bool? ignoreBots;
private string lastKey = "next";
public GameLogReaderHttp(Uri gameLogServerUri, string logPath, IEventParser parser)
{
@ -30,12 +31,12 @@ namespace IW4MAdmin.Application.IO
public long Length => -1;
public int UpdateInterval => 550;
public int UpdateInterval => 250;
public async Task<ICollection<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
{
#if DEBUG == true
server.Logger.WriteDebug($"Begin reading from http log");
server.Logger.WriteDebug($"Begin reading from http log at {DateTime.Now.Millisecond}");
#endif
if (!ignoreBots.HasValue)
@ -45,72 +46,79 @@ namespace IW4MAdmin.Application.IO
var events = new List<GameEvent>();
string b64Path = logPath;
var response = await Api.Log(b64Path);
var response = await Api.Log(b64Path, lastKey);
lastKey = response.NextKey;
if (!response.Success)
if (!response.Success && string.IsNullOrEmpty(lastKey))
{
server.Logger.WriteError($"Could not get log server info of {logPath}/{b64Path} ({server.LogPath})");
return events;
}
// parse each line
foreach (string eventLine in response.Data.Split(Environment.NewLine))
else if (response.Data != null)
{
if (eventLine.Length > 0)
// parse each line
foreach (string eventLine in response.Data.Split(Environment.NewLine))
{
try
if (eventLine.Length > 0)
{
var gameEvent = Parser.GenerateGameEvent(eventLine);
// we don't want to add the event if ignoreBots is on and the event comes from a bot
if (!ignoreBots.Value || (ignoreBots.Value && !((gameEvent.Origin?.IsBot ?? false) || (gameEvent.Target?.IsBot ?? false))))
try
{
gameEvent.Owner = server;
if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Origin) == GameEvent.EventRequiredEntity.Origin && gameEvent.Origin.NetworkId != 1)
var gameEvent = Parser.GenerateGameEvent(eventLine);
// we don't want to add the event if ignoreBots is on and the event comes from a bot
if (!ignoreBots.Value || (ignoreBots.Value && !((gameEvent.Origin?.IsBot ?? false) || (gameEvent.Target?.IsBot ?? false))))
{
gameEvent.Origin = server.GetClientsAsList().First(_client => _client.NetworkId == gameEvent.Origin?.NetworkId);
}
gameEvent.Owner = server;
if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Target) == GameEvent.EventRequiredEntity.Target)
{
gameEvent.Target = server.GetClientsAsList().First(_client => _client.NetworkId == gameEvent.Target?.NetworkId);
}
if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Origin) == GameEvent.EventRequiredEntity.Origin && gameEvent.Origin.NetworkId != 1)
{
gameEvent.Origin = server.GetClientsAsList().First(_client => _client.NetworkId == gameEvent.Origin?.NetworkId);
}
if (gameEvent.Origin != null)
{
gameEvent.Origin.CurrentServer = server;
}
if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Target) == GameEvent.EventRequiredEntity.Target)
{
gameEvent.Target = server.GetClientsAsList().First(_client => _client.NetworkId == gameEvent.Target?.NetworkId);
}
if (gameEvent.Target != null)
{
gameEvent.Target.CurrentServer = server;
}
if (gameEvent.Origin != null)
{
gameEvent.Origin.CurrentServer = server;
}
events.Add(gameEvent);
}
if (gameEvent.Target != null)
{
gameEvent.Target.CurrentServer = server;
}
events.Add(gameEvent);
}
#if DEBUG == true
server.Logger.WriteDebug($"Parsed event with id {gameEvent.Id} from http");
server.Logger.WriteDebug($"Parsed event with id {gameEvent.Id} from http");
#endif
}
}
catch (InvalidOperationException)
{
if (!ignoreBots.Value)
catch (InvalidOperationException)
{
server.Logger.WriteWarning("Could not find client in client list when parsing event line");
if (!ignoreBots.Value)
{
server.Logger.WriteWarning("Could not find client in client list when parsing event line");
server.Logger.WriteDebug(eventLine);
}
}
catch (Exception e)
{
server.Logger.WriteWarning("Could not properly parse remote event line");
server.Logger.WriteDebug(e.Message);
server.Logger.WriteDebug(eventLine);
}
}
catch (Exception e)
{
server.Logger.WriteWarning("Could not properly parse remote event line");
server.Logger.WriteDebug(e.Message);
server.Logger.WriteDebug(eventLine);
}
}
}
#if DEBUG == true
server.Logger.WriteDebug($"End reading from http log at {DateTime.Now.Millisecond}");
#endif
return events;
}
}

View File

@ -4,6 +4,7 @@ using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace IW4MAdmin.Application
{
@ -54,9 +55,9 @@ namespace IW4MAdmin.Application
}
}
void Write(string msg, LogType type)
async Task Write(string msg, LogType type)
{
OnLogWriting.Wait();
await OnLogWriting.WaitAsync();
string stringType = type.ToString();
@ -73,14 +74,14 @@ namespace IW4MAdmin.Application
#if DEBUG
// lets keep it simple and dispose of everything quickly as logging wont be that much (relatively)
Console.WriteLine(LogLine);
File.AppendAllText(FileName, LogLine + Environment.NewLine);
Debug.WriteLine(msg);
await File.AppendAllTextAsync(FileName, $"{LogLine}{Environment.NewLine}");
//Debug.WriteLine(msg);
#else
if (type == LogType.Error || type == LogType.Verbose)
{
Console.WriteLine(LogLine);
}
File.AppendAllText(FileName, $"{LogLine}{Environment.NewLine}");
await File.AppendAllTextAsync(FileName, $"{LogLine}{Environment.NewLine}");
#endif
}

View File

@ -1,6 +1,8 @@
import re
import os
import time
import random
import string
class LogReader(object):
def __init__(self):
@ -8,7 +10,7 @@ class LogReader(object):
# (if the time between checks is greater, ignore ) - in seconds
self.max_file_time_change = 60
def read_file(self, path):
def read_file(self, path, retrieval_key):
# this removes old entries that are no longer valid
try:
self._clear_old_logs()
@ -22,57 +24,95 @@ class LogReader(object):
# prevent traversing directories
if re.search('r^.+\.\.\\.+$', path):
return False
return self._generate_bad_response()
# must be a valid log path and log file
if not re.search(r'^.+[\\|\/](.+)[\\|\/].+.log$', path):
return False
return self._generate_bad_response()
# get the new file size
new_file_size = self.file_length(path)
# the log size was unable to be read (probably the wrong path)
if new_file_size < 0:
return False
return self._generate_bad_response()
# this is the first time the log has been requested
if path not in self.log_file_sizes:
self.log_file_sizes[path] = {
'length' : new_file_size,
'read': time.time()
next_retrieval_key = self._generate_key()
#print('next key is %s' % next_retrieval_key)
# this is the first time the key has been requested, so we need to the next one
if retrieval_key not in self.log_file_sizes:
print ('requested key %s does not exist' % retrieval_key)
self.log_file_sizes[next_retrieval_key] = {
'size': new_file_size,
'length': 0,
'read': time.time(),
'next_key': next_retrieval_key, # the key that IW4MAdmin should request next
'previous_key': retrieval_key # the old key that can be deleted next
}
return ''
return {
'content': None,
'next_key': next_retrieval_key
}
else:
current_log_info = self.log_file_sizes[retrieval_key]
# grab the previous values
last_length = self.log_file_sizes[path]['length']
file_size_difference = new_file_size - last_length
last_length = current_log_info['length']
last_size = current_log_info['size']
file_size_difference = new_file_size - last_size
#print('generating info for next key %s' % next_retrieval_key)
# update the new size and actually read the data
self.log_file_sizes[path] = {
'length': new_file_size,
'read': time.time()
self.log_file_sizes[next_retrieval_key] = {
'size' : new_file_size,
'length': last_length,
'read': time.time(),
'next_key': next_retrieval_key,
'previous_key': retrieval_key
}
new_log_info = self.get_file_lines(path, file_size_difference)
return new_log_info
if current_log_info['previous_key'] in self.log_file_sizes.keys():
#print('deleting old key %s' % current_log_info['previous_key'])
del self.log_file_sizes[current_log_info['previous_key']]
def get_file_lines(self, path, length):
#print('reading %i bytes starting at %i' % (file_size_difference, last_size))
new_log_info = self.get_file_lines(path, last_size, file_size_difference)
return {
'content': new_log_info,
'next_key': next_retrieval_key
}
def get_file_lines(self, path, start_position, length_to_read):
try:
file_handle = open(path, 'rb')
file_handle.seek(-length, 2)
file_data = file_handle.read(length)
file_handle.seek(start_position)
file_data = file_handle.read(length_to_read)
file_handle.close()
# using ignore errors omits the pesky 0xb2 bytes we're reading in for some reason
return file_data.decode('utf-8', errors='ignore')
except Exception as e:
print('could not read the log file at {0}, wanted to read {1} bytes'.format(path, length))
print('could not read the log file at {0}, wanted to read {1} bytes'.format(path, length_to_read))
print(e)
return False
def _clear_old_logs(self):
expired_logs = [path for path in self.log_file_sizes if int(time.time() - self.log_file_sizes[path]['read']) > self.max_file_time_change]
for log in expired_logs:
print('removing expired log {0}'.format(log))
del self.log_file_sizes[log]
for key in expired_logs:
print('removing expired log with key {0}'.format(key))
del self.log_file_sizes[key]
def _generate_bad_response(self):
return {
'content': None,
'next_key': None
}
def _generate_key(self):
return ''.join(random.choices(string.ascii_uppercase + string.digits, k=8))
def file_length(self, path):
try:

View File

@ -3,12 +3,14 @@ from GameLogServer.log_reader import reader
from base64 import urlsafe_b64decode
class LogResource(Resource):
def get(self, path):
def get(self, path, retrieval_key):
path = urlsafe_b64decode(path).decode('utf-8')
log_info = reader.read_file(path)
log_info = reader.read_file(path, retrieval_key)
content = log_info['content']
return {
'success' : log_info is not False,
'length': 0 if log_info is False else len(log_info),
'data': log_info
'success' : content is not None,
'length': 0 if content is None else len(content),
'data': content,
'next_key': log_info['next_key']
}

View File

@ -10,5 +10,5 @@ def init():
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
api = Api(app)
api.add_resource(LogResource, '/log/<string:path>')
api.add_resource(LogResource, '/log/<string:path>/<string:retrieval_key>')
#api.add_resource(RestartResource, '/restart')

View File

@ -858,7 +858,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
#region INDIVIDUAL_SERVER_PERFORMANCE
// get the client ranking for the current server
int individualClientRanking = await ctx.Set<EFRating>()
int individualClientRanking = await ctx.Set<EFRating>()
.Where(GetRankingFunc(clientStats.ServerId))
// ignore themselves in the query
.Where(c => c.RatingHistory.ClientId != client.ClientId)

View File

@ -21,13 +21,7 @@ namespace Stats.Models
.HasColumnName("EFClientStatistics_ServerId");
builder.Entity<EFRating>()
.HasIndex(p => p.Performance);
builder.Entity<EFRating>()
.HasIndex(p => p.Ranking);
builder.Entity<EFRating>()
.HasIndex(p => p.When);
.HasIndex(p => new { p.Performance, p.Ranking, p.When });
builder.Entity<EFClientMessage>()
.HasIndex(p => p.TimeSent);

View File

@ -1,5 +1,7 @@
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Interfaces;
using System;
@ -20,6 +22,11 @@ namespace SharedLibraryCore.Database
public DbSet<EFMeta> EFMeta { get; set; }
public DbSet<EFChangeHistory> EFChangeHistory { get; set; }
[Obsolete]
private static readonly ILoggerFactory _loggerFactory = new LoggerFactory(new[] {
new ConsoleLoggerProvider((category, level) => level == LogLevel.Information, true)
});
static string _ConnectionString;
static string _provider;
private static readonly string _migrationPluginDirectory = @"X:\IW4MAdmin\BUILD\Plugins";
@ -103,6 +110,13 @@ namespace SharedLibraryCore.Database
break;
}
}
#if DEBUG
#pragma warning disable CS0612 // Type or member is obsolete
optionsBuilder.UseLoggerFactory(_loggerFactory)
#pragma warning restore CS0612 // Type or member is obsolete
.EnableSensitiveDataLogging();
#endif
}
protected override void OnModelCreating(ModelBuilder modelBuilder)

View File

@ -0,0 +1,699 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SharedLibraryCore.Database;
namespace SharedLibraryCore.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20190725000309_AlterEFRatingIndex")]
partial class AlterEFRatingIndex
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.2.4-servicing-10062");
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
b.Property<int>("SnapshotId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<int>("CurrentSessionLength");
b.Property<double>("CurrentStrain");
b.Property<int>("CurrentViewAngleId");
b.Property<int>("Deaths");
b.Property<double>("Distance");
b.Property<double>("EloRating");
b.Property<int>("HitDestinationId");
b.Property<int>("HitLocation");
b.Property<int>("HitOriginId");
b.Property<int>("HitType");
b.Property<int>("Hits");
b.Property<int>("Kills");
b.Property<int>("LastStrainAngleId");
b.Property<double>("RecoilOffset");
b.Property<double>("SessionAngleOffset");
b.Property<double>("SessionSPM");
b.Property<int>("SessionScore");
b.Property<double>("StrainAngleBetween");
b.Property<int>("TimeSinceLastEvent");
b.Property<int>("WeaponId");
b.Property<DateTime>("When");
b.HasKey("SnapshotId");
b.HasIndex("ClientId");
b.HasIndex("CurrentViewAngleId");
b.HasIndex("HitDestinationId");
b.HasIndex("HitOriginId");
b.HasIndex("LastStrainAngleId");
b.ToTable("EFACSnapshot");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{
b.Property<long>("KillId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("AttackerId");
b.Property<int>("Damage");
b.Property<int?>("DeathOriginVector3Id");
b.Property<int>("DeathType");
b.Property<double>("Fraction");
b.Property<int>("HitLoc");
b.Property<bool>("IsKill");
b.Property<int?>("KillOriginVector3Id");
b.Property<int>("Map");
b.Property<long>("ServerId");
b.Property<int>("VictimId");
b.Property<int?>("ViewAnglesVector3Id");
b.Property<double>("VisibilityPercentage");
b.Property<int>("Weapon");
b.Property<DateTime>("When");
b.HasKey("KillId");
b.HasIndex("AttackerId");
b.HasIndex("DeathOriginVector3Id");
b.HasIndex("KillOriginVector3Id");
b.HasIndex("ServerId");
b.HasIndex("VictimId");
b.HasIndex("ViewAnglesVector3Id");
b.ToTable("EFClientKills");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
{
b.Property<long>("MessageId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<string>("Message");
b.Property<long>("ServerId");
b.Property<DateTime>("TimeSent");
b.HasKey("MessageId");
b.HasIndex("ClientId");
b.HasIndex("ServerId");
b.HasIndex("TimeSent");
b.ToTable("EFClientMessages");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
{
b.Property<int>("RatingHistoryId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.HasKey("RatingHistoryId");
b.HasIndex("ClientId");
b.ToTable("EFClientRatingHistory");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
{
b.Property<int>("ClientId");
b.Property<long>("ServerId");
b.Property<bool>("Active");
b.Property<double>("AverageRecoilOffset");
b.Property<int>("Deaths");
b.Property<double>("EloRating");
b.Property<int>("Kills");
b.Property<double>("MaxStrain");
b.Property<double>("RollingWeightedKDR");
b.Property<double>("SPM");
b.Property<double>("Skill");
b.Property<int>("TimePlayed");
b.Property<double>("VisionAverage");
b.HasKey("ClientId", "ServerId");
b.HasIndex("ServerId");
b.ToTable("EFClientStatistics");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
{
b.Property<int>("HitLocationCountId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId")
.HasColumnName("EFClientStatistics_ClientId");
b.Property<int>("HitCount");
b.Property<float>("HitOffsetAverage");
b.Property<int>("Location");
b.Property<float>("MaxAngleDistance");
b.Property<long>("ServerId")
.HasColumnName("EFClientStatistics_ServerId");
b.HasKey("HitLocationCountId");
b.HasIndex("ServerId");
b.HasIndex("ClientId", "ServerId");
b.ToTable("EFHitLocationCounts");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
{
b.Property<int>("RatingId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ActivityAmount");
b.Property<bool>("Newest");
b.Property<double>("Performance");
b.Property<int>("Ranking");
b.Property<int>("RatingHistoryId");
b.Property<long?>("ServerId");
b.Property<DateTime>("When");
b.HasKey("RatingId");
b.HasIndex("RatingHistoryId");
b.HasIndex("ServerId");
b.HasIndex("Performance", "Ranking", "When");
b.ToTable("EFRating");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b =>
{
b.Property<long>("ServerId");
b.Property<bool>("Active");
b.Property<string>("EndPoint");
b.Property<int?>("GameName");
b.Property<int>("Port");
b.HasKey("ServerId");
b.ToTable("EFServers");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
{
b.Property<int>("StatisticId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<long>("ServerId");
b.Property<long>("TotalKills");
b.Property<long>("TotalPlayTime");
b.HasKey("StatisticId");
b.HasIndex("ServerId");
b.ToTable("EFServerStatistics");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
{
b.Property<int>("AliasId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<DateTime>("DateAdded");
b.Property<int?>("IPAddress");
b.Property<int>("LinkId");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(24);
b.HasKey("AliasId");
b.HasIndex("IPAddress");
b.HasIndex("LinkId");
b.HasIndex("Name");
b.ToTable("EFAlias");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b =>
{
b.Property<int>("AliasLinkId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.HasKey("AliasLinkId");
b.ToTable("EFAliasLinks");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b =>
{
b.Property<int>("ChangeHistoryId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<string>("Comment")
.HasMaxLength(128);
b.Property<string>("CurrentValue");
b.Property<int>("OriginEntityId");
b.Property<string>("PreviousValue");
b.Property<int>("TargetEntityId");
b.Property<DateTime>("TimeChanged");
b.Property<int>("TypeOfChange");
b.HasKey("ChangeHistoryId");
b.ToTable("EFChangeHistory");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
{
b.Property<int>("ClientId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("AliasLinkId");
b.Property<int>("Connections");
b.Property<int>("CurrentAliasId");
b.Property<DateTime>("FirstConnection");
b.Property<DateTime>("LastConnection");
b.Property<int>("Level");
b.Property<bool>("Masked");
b.Property<long>("NetworkId");
b.Property<string>("Password");
b.Property<string>("PasswordSalt");
b.Property<int>("TotalConnectionTime");
b.HasKey("ClientId");
b.HasIndex("AliasLinkId");
b.HasIndex("CurrentAliasId");
b.HasIndex("NetworkId")
.IsUnique();
b.ToTable("EFClients");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
{
b.Property<int>("MetaId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<DateTime>("Created");
b.Property<string>("Extra");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(32);
b.Property<DateTime>("Updated");
b.Property<string>("Value")
.IsRequired();
b.HasKey("MetaId");
b.HasIndex("ClientId");
b.HasIndex("Key");
b.ToTable("EFMeta");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
{
b.Property<int>("PenaltyId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<string>("AutomatedOffense");
b.Property<DateTime?>("Expires");
b.Property<bool>("IsEvadedOffense");
b.Property<int>("LinkId");
b.Property<int>("OffenderId");
b.Property<string>("Offense")
.IsRequired();
b.Property<int>("PunisherId");
b.Property<int>("Type");
b.Property<DateTime>("When");
b.HasKey("PenaltyId");
b.HasIndex("LinkId");
b.HasIndex("OffenderId");
b.HasIndex("PunisherId");
b.ToTable("EFPenalties");
});
modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b =>
{
b.Property<int>("Vector3Id")
.ValueGeneratedOnAdd();
b.Property<int?>("EFACSnapshotSnapshotId");
b.Property<float>("X");
b.Property<float>("Y");
b.Property<float>("Z");
b.HasKey("Vector3Id");
b.HasIndex("EFACSnapshotSnapshotId");
b.ToTable("Vector3");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "CurrentViewAngle")
.WithMany()
.HasForeignKey("CurrentViewAngleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitDestination")
.WithMany()
.HasForeignKey("HitDestinationId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitOrigin")
.WithMany()
.HasForeignKey("HitOriginId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "LastStrainAngle")
.WithMany()
.HasForeignKey("LastStrainAngleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker")
.WithMany()
.HasForeignKey("AttackerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "DeathOrigin")
.WithMany()
.HasForeignKey("DeathOriginVector3Id");
b.HasOne("SharedLibraryCore.Helpers.Vector3", "KillOrigin")
.WithMany()
.HasForeignKey("KillOriginVector3Id");
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Victim")
.WithMany()
.HasForeignKey("VictimId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "ViewAngles")
.WithMany()
.HasForeignKey("ViewAnglesVector3Id");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics")
.WithMany("HitLocations")
.HasForeignKey("ClientId", "ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", "RatingHistory")
.WithMany("Ratings")
.HasForeignKey("RatingHistoryId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
.WithMany("Children")
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "AliasLink")
.WithMany()
.HasForeignKey("AliasLinkId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFAlias", "CurrentAlias")
.WithMany()
.HasForeignKey("CurrentAliasId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany("Meta")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
.WithMany("ReceivedPenalties")
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Offender")
.WithMany("ReceivedPenalties")
.HasForeignKey("OffenderId")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Punisher")
.WithMany("AdministeredPenalties")
.HasForeignKey("PunisherId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot")
.WithMany("PredictedViewAngles")
.HasForeignKey("EFACSnapshotSnapshotId");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,49 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace SharedLibraryCore.Migrations
{
public partial class AlterEFRatingIndex : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_EFRating_Performance",
table: "EFRating");
migrationBuilder.DropIndex(
name: "IX_EFRating_Ranking",
table: "EFRating");
migrationBuilder.DropIndex(
name: "IX_EFRating_When",
table: "EFRating");
migrationBuilder.CreateIndex(
name: "IX_EFRating_Performance_Ranking_When",
table: "EFRating",
columns: new[] { "Performance", "Ranking", "When" });
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_EFRating_Performance_Ranking_When",
table: "EFRating");
migrationBuilder.CreateIndex(
name: "IX_EFRating_Performance",
table: "EFRating",
column: "Performance");
migrationBuilder.CreateIndex(
name: "IX_EFRating_Ranking",
table: "EFRating",
column: "Ranking");
migrationBuilder.CreateIndex(
name: "IX_EFRating_When",
table: "EFRating",
column: "When");
}
}
}

View File

@ -266,15 +266,11 @@ namespace SharedLibraryCore.Migrations
b.HasKey("RatingId");
b.HasIndex("Performance");
b.HasIndex("Ranking");
b.HasIndex("RatingHistoryId");
b.HasIndex("ServerId");
b.HasIndex("When");
b.HasIndex("Performance", "Ranking", "When");
b.ToTable("EFRating");
});

View File

@ -42,6 +42,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />

View File

@ -25,7 +25,8 @@
</PropertyGroup>
<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
<ServerGarbageCollection>false</ServerGarbageCollection>
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
<TieredCompilation>true</TieredCompilation>
<LangVersion>7.1</LangVersion>
</PropertyGroup>