1384 lines
47 KiB
C++
1384 lines
47 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "QosRegionManager.h"
|
|
|
|
#include "Algo/Transform.h"
|
|
#include "Misc/CommandLine.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "Misc/TrackedActivity.h"
|
|
#include "QosInterface.h"
|
|
#include "TimerManager.h"
|
|
#include "Engine/World.h"
|
|
#include "QosEvaluator.h"
|
|
#include "QosModule.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(QosRegionManager)
|
|
|
|
#define LAST_REGION_EVALUATION 3
|
|
|
|
namespace QosCvars
|
|
{
|
|
static bool bIncludeAllSubregionPreferences = true;
|
|
static FAutoConsoleVariableRef CVarbIncludeAllSubregionPreferences(
|
|
TEXT("QOS.IncludeAllSubregionPreferences"),
|
|
bIncludeAllSubregionPreferences,
|
|
TEXT("Whether requesting subregions includes all subregions regardless of parent region.\n")
|
|
TEXT("true Enables. false disables."),
|
|
ECVF_Default);
|
|
}
|
|
|
|
bool FDatacenterQosInstance::IsLessWhenBiasedTowardsNonSubspace(
|
|
const FDatacenterQosInstance& A,
|
|
const FDatacenterQosInstance& B,
|
|
const FQosSubspaceComparisonParams& ComparisonParams,
|
|
const TCHAR* const SubspaceDelimiter)
|
|
{
|
|
if (A.Definition.IsSubspace(SubspaceDelimiter) == B.Definition.IsSubspace(SubspaceDelimiter))
|
|
{
|
|
// Comparing like-for-like, so use normal ordering by average ping.
|
|
return A.AvgPingMs < B.AvgPingMs;
|
|
}
|
|
|
|
// Comparing subspace vs non-subspace, or vice-versa.
|
|
const bool bAIsSubspace = A.Definition.IsSubspace(SubspaceDelimiter);
|
|
const FDatacenterQosInstance& Subspace = bAIsSubspace ? A : B;
|
|
const FDatacenterQosInstance& NonSubspace = bAIsSubspace ? B : A;
|
|
const bool bIsNonSubspaceRecommended = IsNonSubspaceRecommended(NonSubspace, Subspace, ComparisonParams);
|
|
|
|
const bool bResult = bIsNonSubspaceRecommended != bAIsSubspace;
|
|
return bResult;
|
|
}
|
|
|
|
bool FDatacenterQosInstance::IsNonSubspaceRecommended(
|
|
const FDatacenterQosInstance& NonSubspace,
|
|
const FDatacenterQosInstance& Subspace,
|
|
const FQosSubspaceComparisonParams& ComparisonParams)
|
|
{
|
|
const int32 NonSubspacePingMs = NonSubspace.AvgPingMs;
|
|
const int32 SubspacePingMs = Subspace.AvgPingMs;
|
|
|
|
if (NonSubspacePingMs < SubspacePingMs)
|
|
{
|
|
// Edge case where non-subspace is faster, recommend it immediately.
|
|
return true;
|
|
}
|
|
|
|
const int32 PingMsDelta = NonSubspacePingMs - SubspacePingMs;
|
|
|
|
const float ScaledMaxToleranceMs = ComparisonParams.CalcScaledMaxToleranceMs(NonSubspacePingMs);
|
|
|
|
// Test if non-subspace qualifies over subspace, via rules-based comparison that biases towards non-subspace.
|
|
bool bDisqualify = (ComparisonParams.MaxNonSubspacePingMs > 0) && (NonSubspacePingMs > ComparisonParams.MaxNonSubspacePingMs);
|
|
bDisqualify = bDisqualify || ((ComparisonParams.MinSubspacePingMs > 0) && (SubspacePingMs < ComparisonParams.MinSubspacePingMs));
|
|
bDisqualify = bDisqualify || ((ComparisonParams.ConstantMaxToleranceMs > 0) && (PingMsDelta > ComparisonParams.ConstantMaxToleranceMs));
|
|
bDisqualify = bDisqualify || ((ComparisonParams.ScaledMaxTolerancePct > 0.0f) && (static_cast<float>(PingMsDelta) > ScaledMaxToleranceMs));
|
|
|
|
return !bDisqualify;
|
|
}
|
|
|
|
const FDatacenterQosInstance& FDatacenterQosInstance::CompareBiasedTowardsNonSubspace(
|
|
const FDatacenterQosInstance& NonSubspace, const FDatacenterQosInstance& Subspace,
|
|
const FQosSubspaceComparisonParams& ComparisonParams)
|
|
{
|
|
const bool bPreferNonSubspace = IsNonSubspaceRecommended(NonSubspace, Subspace, ComparisonParams);
|
|
return bPreferNonSubspace ? NonSubspace : Subspace;
|
|
}
|
|
|
|
const FDatacenterQosInstance* const FDatacenterQosInstance::CompareBiasedTowardsNonSubspace(
|
|
const FDatacenterQosInstance* const NonSubspace, const FDatacenterQosInstance* const Subspace,
|
|
const FQosSubspaceComparisonParams& ComparisonParams)
|
|
{
|
|
check(NonSubspace);
|
|
check(Subspace);
|
|
const bool bPreferNonSubspace = IsNonSubspaceRecommended(*NonSubspace, *Subspace, ComparisonParams);
|
|
return bPreferNonSubspace ? NonSubspace : Subspace;
|
|
}
|
|
|
|
EQosDatacenterResult FRegionQosInstance::GetRegionResult() const
|
|
{
|
|
EQosDatacenterResult Result = EQosDatacenterResult::Success;
|
|
for (const FDatacenterQosInstance& Datacenter : DatacenterOptions)
|
|
{
|
|
if (Datacenter.Result == EQosDatacenterResult::Invalid)
|
|
{
|
|
Result = EQosDatacenterResult::Invalid;
|
|
break;
|
|
}
|
|
if (Datacenter.Result == EQosDatacenterResult::Incomplete)
|
|
{
|
|
Result = EQosDatacenterResult::Incomplete;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
int32 FRegionQosInstance::GetBestAvgPing() const
|
|
{
|
|
int32 BestPing = UNREACHABLE_PING;
|
|
if (DatacenterOptions.Num() > 0)
|
|
{
|
|
// Presorted for best result first
|
|
BestPing = DatacenterOptions[0].AvgPingMs;
|
|
}
|
|
|
|
return BestPing;
|
|
}
|
|
|
|
FString FRegionQosInstance::GetBestSubregion() const
|
|
{
|
|
FString BestDatacenterId;
|
|
if (DatacenterOptions.Num() > 0)
|
|
{
|
|
// Presorted for best/recommended result first
|
|
BestDatacenterId = DatacenterOptions[0].Definition.Id;
|
|
}
|
|
|
|
return BestDatacenterId;
|
|
}
|
|
|
|
void FRegionQosInstance::GetSubregionPreferences(TArray<FString>& OutSubregions) const
|
|
{
|
|
// Presorted for best/recommended result first
|
|
for (const FDatacenterQosInstance& Option : DatacenterOptions)
|
|
{
|
|
OutSubregions.Add(Option.Definition.Id);
|
|
}
|
|
}
|
|
|
|
void FRegionQosInstance::SortDatacenterOptionsByAvgPingAsc()
|
|
{
|
|
UE_LOG(LogQos, VeryVerbose, TEXT("[FRegionQosInstance::SortDatacenterOptionsByAvgPingAsc] sorting datacenter results for region %s"), *GetRegionId());
|
|
|
|
// Sort ping best to worst (ascending ping ms)
|
|
DatacenterOptions.Sort(
|
|
[](const FDatacenterQosInstance& A, const FDatacenterQosInstance& B) {
|
|
return A.AvgPingMs < B.AvgPingMs;
|
|
}
|
|
);
|
|
}
|
|
|
|
void FRegionQosInstance::SortDatacenterSubspacesByRecommended(const FQosSubspaceComparisonParams& ComparisonParams, const TCHAR* const SubspaceDelimiter)
|
|
{
|
|
UE_LOG(LogQos, VeryVerbose, TEXT("[FRegionQosInstance::SortDatacenterSubspacesByRecommended] sorting datacenter results for region %s"), *GetRegionId());
|
|
|
|
// Sort by subspace recommendation rules
|
|
DatacenterOptions.Sort(
|
|
[&ComparisonParams, &SubspaceDelimiter](const FDatacenterQosInstance& A, const FDatacenterQosInstance& B) {
|
|
return FDatacenterQosInstance::IsLessWhenBiasedTowardsNonSubspace(A, B, ComparisonParams, SubspaceDelimiter);
|
|
}
|
|
);
|
|
}
|
|
|
|
void FRegionQosInstance::LogDatacenterResults() const
|
|
{
|
|
UE_LOG(LogQos, VeryVerbose, TEXT("Region %s:"), *GetRegionId());
|
|
|
|
for (const FDatacenterQosInstance& Datacenter : DatacenterOptions)
|
|
{
|
|
UE_LOG(LogQos, VeryVerbose, TEXT(" %s avg ping: %dms, num responses: %d"), *Datacenter.Definition.Id, Datacenter.AvgPingMs, Datacenter.NumResponses);
|
|
}
|
|
}
|
|
|
|
|
|
const TCHAR* UQosRegionManager::SubspaceDelimiterDefault = TEXT("_");
|
|
|
|
UQosRegionManager::UQosRegionManager(const FObjectInitializer& ObjectInitializer)
|
|
: NumTestsPerRegion(3)
|
|
, PingTimeout(5.0f)
|
|
, bEnableSubspaceBiasOrder(false)
|
|
, SubspaceDelimiter(SubspaceDelimiterDefault)
|
|
, LastCheckTimestamp(0)
|
|
, Evaluator(nullptr)
|
|
, QosEvalResult(EQosCompletionResult::Invalid)
|
|
{
|
|
check(GConfig);
|
|
GConfig->GetString(TEXT("Qos"), TEXT("ForceRegionId"), ForceRegionId, GEngineIni);
|
|
|
|
// get a forced region id from the command line as an override
|
|
bRegionForcedViaCommandline = FParse::Value(FCommandLine::Get(), TEXT("McpRegion="), ForceRegionId);
|
|
if (!ForceRegionId.IsEmpty())
|
|
{
|
|
ForceRegionId.ToUpperInline();
|
|
}
|
|
|
|
// Will add an info entry to the console
|
|
static FTrackedActivity Ta(TEXT("McpRegion"), *ForceRegionId, FTrackedActivity::ELight::None, FTrackedActivity::EType::Info);
|
|
}
|
|
|
|
void UQosRegionManager::PostReloadConfig(FProperty* PropertyThatWasLoaded)
|
|
{
|
|
UE_LOG(LogQos, Log, TEXT("[UQosRegionManager::PostReloadConfig] begin"));
|
|
if (!HasAnyFlags(RF_ClassDefaultObject))
|
|
{
|
|
for (int32 RegionIdx = RegionOptions.Num() - 1; RegionIdx >= 0; RegionIdx--)
|
|
{
|
|
FRegionQosInstance& RegionOption = RegionOptions[RegionIdx];
|
|
|
|
bool bFound = false;
|
|
for (FQosRegionInfo& RegionDef : RegionDefinitions)
|
|
{
|
|
if (RegionDef.RegionId == RegionOption.Definition.RegionId)
|
|
{
|
|
bFound = true;
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogQos, Log, TEXT("[UQosRegionManager::PostReloadConfig] RegionId '%s' found in RegionDefinitions? %d"), *RegionOption.Definition.RegionId, bFound);
|
|
if (!bFound)
|
|
{
|
|
// Old value needs to be removed, preserve order
|
|
RegionOptions.RemoveAt(RegionIdx);
|
|
}
|
|
}
|
|
|
|
for (int32 RegionIdx = 0; RegionIdx < RegionDefinitions.Num(); RegionIdx++)
|
|
{
|
|
FQosRegionInfo& RegionDef = RegionDefinitions[RegionIdx];
|
|
|
|
bool bFound = false;
|
|
for (FRegionQosInstance& RegionOption : RegionOptions)
|
|
{
|
|
if (RegionDef.RegionId == RegionOption.Definition.RegionId)
|
|
{
|
|
// Overwrite the metadata
|
|
RegionOption.Definition = RegionDef;
|
|
bFound = true;
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogQos, Log, TEXT("[UQosRegionManager::PostReloadConfig] RegionId '%s' found in RegionOptions? %d"), *RegionDef.RegionId, bFound);
|
|
|
|
if (!bFound)
|
|
{
|
|
// Add new value not in old list
|
|
FRegionQosInstance NewRegion(RegionDef);
|
|
RegionOptions.Insert(NewRegion, RegionIdx);
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogQos, Log, TEXT("[UQosRegionManager::PostReloadConfig] firing OnQoSSettingsChanged delegate"));
|
|
OnQoSSettingsChangedDelegate.ExecuteIfBound();
|
|
|
|
// Validate the current region selection (skipped if a selection has never been attempted)
|
|
if (QosEvalResult != EQosCompletionResult::Invalid)
|
|
{
|
|
TrySetDefaultRegion();
|
|
}
|
|
|
|
RefreshUsableSubregions();
|
|
SanityCheckDefinitions();
|
|
}
|
|
}
|
|
|
|
int32 UQosRegionManager::GetMaxPingMs() const
|
|
{
|
|
int32 MaxPing = -1;
|
|
if (GConfig->GetInt(TEXT("Qos"), TEXT("MaximumPingMs"), MaxPing, GEngineIni) && MaxPing > 0)
|
|
{
|
|
return MaxPing;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool UQosRegionManager::IsSubspaceBiasOrderEnabled() const
|
|
{
|
|
// Command-line override, if specified.
|
|
bool bEnabledCommandLine = false;
|
|
if (FParse::Bool(FCommandLine::Get(), TEXT("qossubspacebias="), bEnabledCommandLine))
|
|
{
|
|
UE_LOG(LogQos, VeryVerbose, TEXT("[UQosRegionManager::IsSubspaceBiasOrderEnabled] overridden by command-line to: %s"), *LexToString(bEnabledCommandLine));
|
|
return bEnabledCommandLine;
|
|
}
|
|
|
|
UE_LOG(LogQos, VeryVerbose, TEXT("[UQosRegionManager::IsSubspaceBiasOrderEnabled] set in ini or defaulted to: %s"), *LexToString(bEnableSubspaceBiasOrder));
|
|
|
|
// Use config value (or internal code default)
|
|
return bEnableSubspaceBiasOrder;
|
|
}
|
|
|
|
bool UQosRegionManager::IsSubspaceBiasOrderEnabled(const FQosRegionInfo& RegionDefinition) const
|
|
{
|
|
return IsSubspaceBiasOrderEnabled() && RegionDefinition.bAllowSubspaceBias;
|
|
}
|
|
|
|
void UQosRegionManager::RefreshUsableSubregions()
|
|
{
|
|
TArray<const FDatacenterQosInstance*> UsableDatacentersByPing;
|
|
UsableDatacentersByPing.Reserve(DatacenterDefinitions.Num());
|
|
for (const FRegionQosInstance& RegionInstance : RegionOptions)
|
|
{
|
|
if (RegionInstance.IsUsable())
|
|
{
|
|
for (const FDatacenterQosInstance& DatacenterInstance : RegionInstance.DatacenterOptions)
|
|
{
|
|
UsableDatacentersByPing.Add(&DatacenterInstance);
|
|
}
|
|
}
|
|
}
|
|
|
|
UsableDatacentersByPing.Sort(
|
|
[&SubspaceBiasParams = SubspaceBiasParams, SubspaceDelimiter = GetSubspaceDelimiter()](const FDatacenterQosInstance& A, const FDatacenterQosInstance& B) {
|
|
return FDatacenterQosInstance::IsLessWhenBiasedTowardsNonSubspace(A, B, SubspaceBiasParams, SubspaceDelimiter);
|
|
});
|
|
|
|
UsableSubregions.Empty(UsableDatacentersByPing.Num());
|
|
Algo::Transform(UsableDatacentersByPing, UsableSubregions,
|
|
[](const FDatacenterQosInstance* Datacenter)
|
|
{
|
|
return Datacenter->Definition.Id;
|
|
});
|
|
|
|
UE_LOG(LogQos, Log, TEXT("[UQosRegionManager::RefreshUsableSubregions] Refreshed subreion list. [%s]"), *FString::Join(UsableSubregions, TEXT(",")));
|
|
}
|
|
|
|
// static
|
|
FString UQosRegionManager::GetDatacenterId()
|
|
{
|
|
struct FDcidInfo
|
|
{
|
|
FDcidInfo()
|
|
{
|
|
FString OverrideDCID;
|
|
if (FParse::Value(FCommandLine::Get(), TEXT("DCID="), OverrideDCID))
|
|
{
|
|
// DCID specified on command line
|
|
DCIDString = OverrideDCID.ToUpper();
|
|
}
|
|
else
|
|
{
|
|
FString DefaultDCID;
|
|
check(GConfig);
|
|
if (GConfig->GetString(TEXT("Qos"), TEXT("DCID"), DefaultDCID, GEngineIni))
|
|
{
|
|
// DCID specified in ini file
|
|
DCIDString = DefaultDCID.ToUpper();
|
|
}
|
|
}
|
|
}
|
|
|
|
FString DCIDString;
|
|
};
|
|
|
|
// TODO (EvanK): making this not static (and thus removing the only-once optimization) due to forked servers
|
|
// not being able to change their DCID after the fork point. will add it back in after we can
|
|
// deprecate the static versions of the getters.
|
|
FDcidInfo DCID;
|
|
return DCID.DCIDString;
|
|
}
|
|
|
|
FString UQosRegionManager::GetAdvertisedSubregionId()
|
|
{
|
|
struct FSubregion
|
|
{
|
|
FSubregion()
|
|
{
|
|
FString OverrideSubregion;
|
|
if (FParse::Value(FCommandLine::Get(), TEXT("McpSubregion="), OverrideSubregion))
|
|
{
|
|
// Subregion specified on command line
|
|
SubregionString = OverrideSubregion.ToUpper();
|
|
}
|
|
else
|
|
{
|
|
FString DefaultSubregion;
|
|
check(GConfig);
|
|
if (GConfig->GetString(TEXT("Qos"), TEXT("McpSubregion"), DefaultSubregion, GEngineIni))
|
|
{
|
|
// DCID specified in ini file
|
|
SubregionString = DefaultSubregion.ToUpper();
|
|
}
|
|
}
|
|
}
|
|
|
|
FString SubregionString;
|
|
};
|
|
|
|
// TODO (EvanK): making this not static (and thus removing the only-once optimization) due to forked servers
|
|
// not being able to change their subregion after the fork point. will add it back in after we can
|
|
// deprecate the static versions of the getters.
|
|
FSubregion Subregion;
|
|
return Subregion.SubregionString;
|
|
}
|
|
|
|
void UQosRegionManager::BeginQosEvaluation(UWorld* World, const TSharedPtr<IAnalyticsProvider>& AnalyticsProvider, const FSimpleDelegate& OnComplete)
|
|
{
|
|
check(World);
|
|
|
|
UE_LOG(LogQos, Log, TEXT("[UQosRegionManager::BeginQosEvaluation] starting!"));
|
|
// There are valid cached results, use them
|
|
if ((RegionOptions.Num() > 0) &&
|
|
(QosEvalResult == EQosCompletionResult::Success) &&
|
|
(FDateTime::UtcNow() - LastCheckTimestamp).GetTotalSeconds() <= LAST_REGION_EVALUATION)
|
|
{
|
|
UE_LOG(LogQos, Log, TEXT("[UQosRegionManager::BeginQosEvaluation] cached results are valid (from %.2f sec ago), using those"), (FDateTime::UtcNow() - LastCheckTimestamp).GetTotalSeconds());
|
|
World->GetTimerManager().SetTimerForNextTick(FTimerDelegate::CreateLambda([OnComplete]()
|
|
{
|
|
OnComplete.ExecuteIfBound();
|
|
}));
|
|
return;
|
|
}
|
|
|
|
// add to the completion delegate
|
|
OnQosEvalCompleteDelegate.Add(OnComplete);
|
|
|
|
// if we're already evaluating, simply return
|
|
if (Evaluator == nullptr)
|
|
{
|
|
UE_LOG(LogQos, Log, TEXT("[UQosRegionManager::BeginQosEvaluation] no eval in progress, creating new evaluator"));
|
|
// create a new evaluator and start the process of running
|
|
Evaluator = NewObject<UQosEvaluator>();
|
|
Evaluator->AddToRoot();
|
|
Evaluator->SetWorld(World);
|
|
Evaluator->SetAnalyticsProvider(AnalyticsProvider);
|
|
|
|
FQosParams Params;
|
|
Params.NumTestsPerRegion = NumTestsPerRegion;
|
|
Params.Timeout = PingTimeout;
|
|
Evaluator->FindDatacenters(Params, RegionDefinitions, DatacenterDefinitions, FOnQosSearchComplete::CreateUObject(this, &UQosRegionManager::OnQosEvaluationComplete));
|
|
}
|
|
}
|
|
|
|
bool UQosRegionManager::IsQosEvaluationInProgress() const
|
|
{
|
|
return Evaluator != nullptr;
|
|
}
|
|
|
|
void UQosRegionManager::OnQosEvaluationComplete(EQosCompletionResult Result, const TArray<FDatacenterQosInstance>& DatacenterInstances, FString* OutSelectedRegion, FString* OutSelectedSubRegion)
|
|
{
|
|
// toss the evaluator
|
|
if (Evaluator != nullptr)
|
|
{
|
|
Evaluator->RemoveFromRoot();
|
|
Evaluator->MarkAsGarbage();
|
|
Evaluator = nullptr;
|
|
}
|
|
QosEvalResult = Result;
|
|
RegionOptions.Empty(RegionDefinitions.Num());
|
|
|
|
TMultiMap<FString, FDatacenterQosInstance> DatacenterMap;
|
|
for (const FDatacenterQosInstance& Datacenter : DatacenterInstances)
|
|
{
|
|
DatacenterMap.Add(Datacenter.Definition.RegionId, Datacenter);
|
|
}
|
|
|
|
const bool bSubspaceBiasEnabled = IsSubspaceBiasOrderEnabled();
|
|
const TCHAR* const Delim = GetSubspaceDelimiter();
|
|
|
|
UE_LOG(LogQos, Verbose, TEXT("[UQosRegionManager::OnQosEvaluationComplete] bSubspaceBiasEnabled=%s"), *LexToString(bSubspaceBiasEnabled));
|
|
|
|
#if DEBUG_SUBCOMPARE_BY_SUBSPACE
|
|
// Test example regions (indepedent of real obtained QoS data)
|
|
{
|
|
TestCompareDatacentersBySubspace();
|
|
TestSortDatacenterSubspacesByRecommended();
|
|
|
|
FRegionQosInstance TestRegion = TestCreateExampleRegionResult();
|
|
TestRegion.SortDatacenterSubspacesByRecommended(TestRegion.Definition.SubspaceBiasParams, TEXT("_"));
|
|
}
|
|
#endif // DEBUG_SUBCOMPARE_BY_SUBSPACE
|
|
|
|
for (const FQosRegionInfo& RegionInfo : RegionDefinitions)
|
|
{
|
|
if (RegionInfo.IsPingable())
|
|
{
|
|
if (DatacenterMap.Num(RegionInfo.RegionId))
|
|
{
|
|
// Build region options from datacenter details
|
|
FRegionQosInstance* NewRegion = new (RegionOptions) FRegionQosInstance(RegionInfo);
|
|
DatacenterMap.MultiFind(RegionInfo.RegionId, NewRegion->DatacenterOptions);
|
|
|
|
const bool bSubspaceBias = bSubspaceBiasEnabled && RegionInfo.bAllowSubspaceBias;
|
|
|
|
UE_LOG(LogQos, VeryVerbose, TEXT("[UQosRegionManager::OnQosEvaluationComplete] pre-sort results for region=%s, bias=%s"),
|
|
*RegionInfo.RegionId, *LexToString(bSubspaceBias));
|
|
NewRegion->LogDatacenterResults();
|
|
|
|
if (bSubspaceBias)
|
|
{
|
|
// Use ordering that applies rules-based comparison for subspace vs non-subspace,
|
|
// and avg ping when comparing like-for-like.
|
|
const FQosSubspaceComparisonParams ComparisonParams = RegionInfo.SubspaceBiasParams;
|
|
NewRegion->SortDatacenterSubspacesByRecommended(ComparisonParams, Delim);
|
|
}
|
|
else
|
|
{
|
|
// Use regular sorting of subregions, by ascendering order of avg ping.
|
|
NewRegion->SortDatacenterOptionsByAvgPingAsc();
|
|
}
|
|
|
|
UE_LOG(LogQos, VeryVerbose, TEXT("[UQosRegionManager::OnQosEvaluationComplete] post-sort results for region=%s, bias=%s"),
|
|
*RegionInfo.RegionId, *LexToString(bSubspaceBias));
|
|
NewRegion->LogDatacenterResults();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogQos, Warning, TEXT("No datacenters for region %s"), *RegionInfo.RegionId);
|
|
}
|
|
}
|
|
}
|
|
|
|
RefreshUsableSubregions();
|
|
LastCheckTimestamp = FDateTime::UtcNow();
|
|
|
|
if (!SelectedRegionId.IsEmpty() && SelectedRegionId == NO_REGION)
|
|
{
|
|
// Put the dev region back into the list and select it
|
|
ForceSelectRegion(SelectedRegionId);
|
|
}
|
|
|
|
// treat lack of any regions as a failure
|
|
if (RegionOptions.Num() <= 0)
|
|
{
|
|
QosEvalResult = EQosCompletionResult::Failure;
|
|
}
|
|
|
|
if (QosEvalResult == EQosCompletionResult::Success ||
|
|
QosEvalResult == EQosCompletionResult::Failure)
|
|
{
|
|
if (RegionOptions.Num() > 0)
|
|
{
|
|
// Try to set something regardless of Qos result
|
|
TrySetDefaultRegion();
|
|
}
|
|
}
|
|
FString BestRegion = GetBestRegion();
|
|
TArray<FString> BestRegionSubregions;
|
|
GetSubregionPreferences(BestRegion, BestRegionSubregions);
|
|
UE_LOG(LogQos, Log, TEXT("[UQosRegionManager::OnQosEvaluationComplete] ping eval has completed. Best region is '%s', recommended subregion is '%s'"),
|
|
*BestRegion, BestRegionSubregions.Num() ? *BestRegionSubregions[0] : TEXT("NONE"));
|
|
|
|
if (OutSelectedRegion)
|
|
{
|
|
*OutSelectedRegion = BestRegion;
|
|
}
|
|
|
|
if (OutSelectedSubRegion)
|
|
{
|
|
*OutSelectedSubRegion = BestRegionSubregions.Num() ? *BestRegionSubregions[0] : FString();
|
|
}
|
|
|
|
#if DEBUG_SUBCOMPARE_BY_SUBSPACE
|
|
DumpRegionStats();
|
|
#endif // DEBUG_SUBCOMPARE_BY_SUBSPACE
|
|
|
|
OnQosEvalCompleteDelegate.Broadcast();
|
|
OnQosEvalCompleteDelegate.Clear();
|
|
}
|
|
|
|
FString UQosRegionManager::GetRegionId() const
|
|
{
|
|
if (!ForceRegionId.IsEmpty())
|
|
{
|
|
//UE_LOG(LogQos, VeryVerbose, TEXT("[UQosRegionManager::GetRegionId] Force region: \"%s\""), *ForceRegionId);
|
|
|
|
// we may have updated INI to bypass this process
|
|
return ForceRegionId;
|
|
}
|
|
|
|
if (QosEvalResult == EQosCompletionResult::Invalid)
|
|
{
|
|
UE_LOG(LogQos, VeryVerbose, TEXT("[UQosRegionManager::GetRegionId] No QoS result: \"%s\""), NO_REGION);
|
|
|
|
// if we haven't run the evaluator just use the region from settings
|
|
// development dedicated server will come here, live services should use -mcpregion
|
|
return NO_REGION;
|
|
}
|
|
|
|
if (SelectedRegionId.IsEmpty())
|
|
{
|
|
// Always set some kind of region, empty implies "wildcard" to the matchmaking code
|
|
UE_LOG(LogQos, Verbose, TEXT("No region currently set."));
|
|
return NO_REGION;
|
|
}
|
|
|
|
UE_LOG(LogQos, VeryVerbose, TEXT("[UQosRegionManager::GetRegionId] Selected region: \"%s\""), *SelectedRegionId);
|
|
return SelectedRegionId;
|
|
}
|
|
|
|
const TCHAR* UQosRegionManager::GetSubspaceDelimiter() const
|
|
{
|
|
return SubspaceDelimiter.IsEmpty() ? SubspaceDelimiterDefault : *SubspaceDelimiter;
|
|
}
|
|
|
|
const FRegionQosInstance* UQosRegionManager::FindQosRegionById(const TArray<FRegionQosInstance>& Regions, const FString& RegionId)
|
|
{
|
|
const FRegionQosInstance* Found = Regions.FindByPredicate(
|
|
[&RegionId](const FRegionQosInstance& Elem) {
|
|
return Elem.Definition.RegionId == RegionId;
|
|
}
|
|
);
|
|
|
|
return Found;
|
|
}
|
|
|
|
const FRegionQosInstance* UQosRegionManager::FindBestQosRegion(const TArray<FRegionQosInstance>& Regions)
|
|
{
|
|
int32 LowestRegionPing = UNREACHABLE_PING;
|
|
const FRegionQosInstance* BestRegion = nullptr;
|
|
|
|
// "Best" average ping is the avg ping of the first datacenter in each region's list of
|
|
// datacenter options. Assumes that the datacenter options list is pre-sorted as needed.
|
|
for (const FRegionQosInstance& Region : Regions)
|
|
{
|
|
if (Region.IsAutoAssignable() && Region.GetBestAvgPing() < LowestRegionPing)
|
|
{
|
|
LowestRegionPing = Region.GetBestAvgPing();
|
|
BestRegion = &Region;
|
|
}
|
|
}
|
|
|
|
return BestRegion;
|
|
}
|
|
|
|
FString UQosRegionManager::GetBestRegion() const
|
|
{
|
|
if (!ForceRegionId.IsEmpty())
|
|
{
|
|
UE_LOG(LogQos, Verbose, TEXT("[UQosRegionManager::GetBestRegion] Force region: %s"), *ForceRegionId);
|
|
|
|
return ForceRegionId;
|
|
}
|
|
|
|
const TArray<FRegionQosInstance>& LocalRegionOptions = GetRegionOptions();
|
|
const FRegionQosInstance* BestRegion = FindBestQosRegion(LocalRegionOptions);
|
|
const FString BestRegionId = BestRegion ? BestRegion->Definition.RegionId : FString();
|
|
|
|
UE_LOG(LogQos, Verbose, TEXT("[UQosRegionManager::GetBestRegion] Best region: \"%s\" (Current selected: \"%s\")"),
|
|
*BestRegionId, *SelectedRegionId);
|
|
|
|
return BestRegionId;
|
|
}
|
|
|
|
void UQosRegionManager::GetSubregionPreferences(const FString& RegionId, TArray<FString>& OutSubregions) const
|
|
{
|
|
if (QosCvars::bIncludeAllSubregionPreferences && !bRegionForcedViaCommandline)
|
|
{
|
|
OutSubregions = UsableSubregions;
|
|
return;
|
|
}
|
|
|
|
const TArray<FRegionQosInstance>& LocalRegionOptions = GetRegionOptions();
|
|
for (const FRegionQosInstance& Region : LocalRegionOptions)
|
|
{
|
|
if (Region.Definition.RegionId == RegionId)
|
|
{
|
|
Region.GetSubregionPreferences(OutSubregions);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const TArray<FRegionQosInstance>& UQosRegionManager::GetRegionOptions() const
|
|
{
|
|
if (ForceRegionId.IsEmpty())
|
|
{
|
|
return RegionOptions;
|
|
}
|
|
|
|
static TArray<FRegionQosInstance> ForcedRegionOptions;
|
|
ForcedRegionOptions.Empty(1);
|
|
for (const FRegionQosInstance& RegionOption : RegionOptions)
|
|
{
|
|
if (RegionOption.Definition.RegionId == ForceRegionId)
|
|
{
|
|
ForcedRegionOptions.Add(RegionOption);
|
|
}
|
|
}
|
|
#if !UE_BUILD_SHIPPING
|
|
if (ForcedRegionOptions.Num() == 0)
|
|
{
|
|
FRegionQosInstance FakeRegionInfo;
|
|
FakeRegionInfo.Definition.DisplayName = NSLOCTEXT("MMRegion", "DevRegion", "Development");
|
|
FakeRegionInfo.Definition.RegionId = ForceRegionId;
|
|
FakeRegionInfo.Definition.bEnabled = true;
|
|
FakeRegionInfo.Definition.bVisible = true;
|
|
FakeRegionInfo.Definition.bAutoAssignable = false;
|
|
FDatacenterQosInstance FakeDatacenterInfo;
|
|
FakeDatacenterInfo.Result = EQosDatacenterResult::Success;
|
|
FakeDatacenterInfo.AvgPingMs = 0;
|
|
FakeRegionInfo.DatacenterOptions.Add(MoveTemp(FakeDatacenterInfo));
|
|
ForcedRegionOptions.Add(MoveTemp(FakeRegionInfo));
|
|
}
|
|
#endif
|
|
return ForcedRegionOptions;
|
|
}
|
|
|
|
void UQosRegionManager::ForceSelectRegion(const FString& InRegionId)
|
|
{
|
|
if (!bRegionForcedViaCommandline)
|
|
{
|
|
QosEvalResult = EQosCompletionResult::Success;
|
|
ForceRegionId = InRegionId.ToUpper();
|
|
|
|
UE_LOG(LogQos, Verbose, TEXT("[UQosRegionManager::ForceSelectRegion] Force region: \"%s\""), *ForceRegionId);
|
|
|
|
// make sure we can select this region
|
|
if (!SetSelectedRegion(ForceRegionId, true))
|
|
{
|
|
UE_LOG(LogQos, Log, TEXT("Failed to force set region id %s"), *ForceRegionId);
|
|
ForceRegionId.Empty();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogQos, Log, TEXT("Forcing region %s skipped because commandline override used %s"), *InRegionId, *ForceRegionId);
|
|
}
|
|
}
|
|
|
|
void UQosRegionManager::TrySetDefaultRegion()
|
|
{
|
|
if (!IsRunningDedicatedServer())
|
|
{
|
|
const FString& RegionId = GetRegionId();
|
|
UE_LOG(LogQos, Verbose, TEXT("[UQosRegionManager::TrySetDefaultRegion] setting default from GetRegionId() (%s)"), *RegionId);
|
|
|
|
// Try to set a default region if one hasn't already been selected
|
|
if (!SetSelectedRegion(RegionId))
|
|
{
|
|
FString BestRegionId = GetBestRegion();
|
|
UE_LOG(LogQos, Verbose, TEXT("[UQosRegionManager::TrySetDefaultRegion] setting default from best (%s)"), *BestRegionId);
|
|
|
|
if (!SetSelectedRegion(BestRegionId))
|
|
{
|
|
UE_LOG(LogQos, Warning, TEXT("Unable to set a good region! Wanted to set '%s'; fall back to '%s' failed; current selected: '%s'"),
|
|
*GetRegionId(), *BestRegionId, *SelectedRegionId);
|
|
DumpRegionStats();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UQosRegionManager::IsUsableRegion(const FString& InRegionId) const
|
|
{
|
|
const TArray<FRegionQosInstance>& LocalRegionOptions = GetRegionOptions();
|
|
for (const FRegionQosInstance& RegionInfo : LocalRegionOptions)
|
|
{
|
|
if (RegionInfo.Definition.RegionId == InRegionId)
|
|
{
|
|
return RegionInfo.IsUsable();
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogQos, Log, TEXT("IsUsableRegion: failed to find region id %s"), *InRegionId);
|
|
return false;
|
|
}
|
|
|
|
bool UQosRegionManager::SetSelectedRegion(const FString& InRegionId, bool bForce)
|
|
{
|
|
// make sure we've enumerated
|
|
if (bForce || QosEvalResult == EQosCompletionResult::Success)
|
|
{
|
|
// make sure it's in the option list
|
|
FString RegionId = InRegionId.ToUpper();
|
|
|
|
const TArray<FRegionQosInstance>& LocalRegionOptions = GetRegionOptions();
|
|
for (const FRegionQosInstance& RegionInfo : LocalRegionOptions)
|
|
{
|
|
if (RegionInfo.Definition.RegionId == RegionId)
|
|
{
|
|
if (RegionInfo.IsUsable())
|
|
{
|
|
FString OldRegionId(SelectedRegionId);
|
|
SelectedRegionId = MoveTemp(RegionId);
|
|
|
|
UE_LOG(LogQos, Verbose, TEXT("[UQosRegionManager::SetSelectedRegion] Old: \"%s\" New: \"%s\" (force? %s)"),
|
|
*OldRegionId, *SelectedRegionId, *LexToString(bForce));
|
|
|
|
OnQosRegionIdChanged().Broadcast(OldRegionId, SelectedRegionId);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// can't select a region not in the options list (NONE is special, it means pick best)
|
|
if (!InRegionId.IsEmpty() && (InRegionId != NO_REGION))
|
|
{
|
|
UE_LOG(LogQos, Log, TEXT("SetSelectedRegion: failed to find region id %s"), *InRegionId);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void UQosRegionManager::ClearSelectedRegion()
|
|
{
|
|
UE_LOG(LogQos, Verbose, TEXT("[UQosRegionManager::ClearSelectedRegion] Selected region: \"%s\" Forced region: \"%s\" bRegionForcedViaCommandline=%s"),
|
|
*SelectedRegionId, *ForceRegionId, *LexToString(bRegionForcedViaCommandline));
|
|
|
|
// Do not default to NO_REGION
|
|
SelectedRegionId.Empty();
|
|
if (!bRegionForcedViaCommandline)
|
|
{
|
|
ForceRegionId.Empty();
|
|
}
|
|
}
|
|
|
|
bool UQosRegionManager::AllRegionsFound() const
|
|
{
|
|
int32 NumDatacenters = 0;
|
|
for (const FQosDatacenterInfo& Datacenter : DatacenterDefinitions)
|
|
{
|
|
if (Datacenter.IsPingable())
|
|
{
|
|
++NumDatacenters;
|
|
}
|
|
}
|
|
|
|
int32 NumDatacentersWithGoodResponses = 0;
|
|
for (const FRegionQosInstance& Region : RegionOptions)
|
|
{
|
|
for (const FDatacenterQosInstance& Datacenter : Region.DatacenterOptions)
|
|
{
|
|
const bool bGoodPercentage = (((float)Datacenter.NumResponses / (float)NumTestsPerRegion) >= 0.5f);
|
|
NumDatacentersWithGoodResponses += bGoodPercentage ? 1 : 0;
|
|
}
|
|
}
|
|
|
|
return (NumDatacenters > 0) && (NumDatacentersWithGoodResponses > 0) && (NumDatacenters == NumDatacentersWithGoodResponses);
|
|
}
|
|
|
|
void UQosRegionManager::SanityCheckDefinitions() const
|
|
{
|
|
// Check data syntax
|
|
for (const FQosRegionInfo& Region : RegionDefinitions)
|
|
{
|
|
UE_CLOG(!Region.IsValid(), LogQos, Warning, TEXT("Invalid QOS region entry!"));
|
|
}
|
|
|
|
// Check data syntax
|
|
for (const FQosDatacenterInfo& Datacenter : DatacenterDefinitions)
|
|
{
|
|
UE_CLOG(!Datacenter.IsValid(), LogQos, Warning, TEXT("Invalid QOS datacenter entry!"));
|
|
}
|
|
|
|
// Every datacenter maps to a parent region
|
|
for (const FQosDatacenterInfo& Datacenter : DatacenterDefinitions)
|
|
{
|
|
bool bFoundParentRegion = false;
|
|
for (const FQosRegionInfo& Region : RegionDefinitions)
|
|
{
|
|
if (Datacenter.RegionId == Region.RegionId)
|
|
{
|
|
bFoundParentRegion = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bFoundParentRegion)
|
|
{
|
|
UE_LOG(LogQos, Warning, TEXT("Datacenter %s has undefined parent region %s"), *Datacenter.Id, *Datacenter.RegionId);
|
|
}
|
|
}
|
|
|
|
// Regions with no available datacenters
|
|
for (const FQosRegionInfo& Region : RegionDefinitions)
|
|
{
|
|
int32 NumDatacenters = 0;
|
|
int32 NumPingableDatacenters = 0;
|
|
for (const FQosDatacenterInfo& Datacenter : DatacenterDefinitions)
|
|
{
|
|
if (Datacenter.RegionId == Region.RegionId)
|
|
{
|
|
NumDatacenters++;
|
|
if (Datacenter.IsPingable())
|
|
{
|
|
NumPingableDatacenters++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NumDatacenters == 0)
|
|
{
|
|
UE_LOG(LogQos, Warning, TEXT("Region %s has no datacenters"), *Region.RegionId);
|
|
}
|
|
|
|
if (NumDatacenters > 0 && NumPingableDatacenters == 0)
|
|
{
|
|
UE_LOG(LogQos, Warning, TEXT("Region %s has %d datacenters, all disabled"), *Region.RegionId, NumDatacenters);
|
|
}
|
|
}
|
|
|
|
// Every auto assignable region has at least one auto assignable datacenter
|
|
int32 NumAutoAssignableRegions = 0;
|
|
for (const FQosRegionInfo& Region : RegionDefinitions)
|
|
{
|
|
if (Region.IsAutoAssignable())
|
|
{
|
|
int32 NumPingableDatacenters = 0;
|
|
for (const FQosDatacenterInfo& Datacenter : DatacenterDefinitions)
|
|
{
|
|
if (Datacenter.RegionId == Region.RegionId)
|
|
{
|
|
if (Datacenter.IsPingable())
|
|
{
|
|
NumPingableDatacenters++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NumPingableDatacenters)
|
|
{
|
|
NumAutoAssignableRegions++;
|
|
}
|
|
|
|
UE_LOG(LogQos, Display, TEXT("AutoRegion %s: %d datacenters available"), *Region.RegionId, NumPingableDatacenters);
|
|
}
|
|
}
|
|
|
|
// At least one region is auto assignable
|
|
if (NumAutoAssignableRegions == 0)
|
|
{
|
|
UE_LOG(LogQos, Warning, TEXT("No auto assignable regions available!"));
|
|
}
|
|
}
|
|
|
|
void UQosRegionManager::DumpRegionStats() const
|
|
{
|
|
UE_LOG(LogQos, Display, TEXT("Region Info:"));
|
|
UE_LOG(LogQos, Display, TEXT("Current: %s "), *SelectedRegionId);
|
|
if (!ForceRegionId.IsEmpty())
|
|
{
|
|
UE_LOG(LogQos, Display, TEXT("Forced: %s "), *ForceRegionId);
|
|
}
|
|
|
|
TMultiMap<FString, const FQosDatacenterInfo*> DatacentersByRegion;
|
|
for (const FQosDatacenterInfo& DatacenterDef : DatacenterDefinitions)
|
|
{
|
|
DatacentersByRegion.Emplace(DatacenterDef.RegionId, &DatacenterDef);
|
|
}
|
|
|
|
TMap<FString, const FRegionQosInstance* const> RegionInstanceByRegion;
|
|
for (const FRegionQosInstance& Region : RegionOptions)
|
|
{
|
|
RegionInstanceByRegion.Emplace(Region.Definition.RegionId, &Region);
|
|
}
|
|
|
|
// Look at real region options here
|
|
UE_LOG(LogQos, Display, TEXT("Definitions:"));
|
|
for (const FQosRegionInfo& RegionDef : RegionDefinitions)
|
|
{
|
|
const FRegionQosInstance* const* RegionInst = RegionInstanceByRegion.Find(RegionDef.RegionId);
|
|
|
|
TArray<const FQosDatacenterInfo*> OutValues;
|
|
DatacentersByRegion.MultiFind(RegionDef.RegionId, OutValues);
|
|
|
|
UE_LOG(LogQos, Display, TEXT("\tRegion: %s [%s] (%d datacenters)"), *RegionDef.DisplayName.ToString(), *RegionDef.RegionId, OutValues.Num());
|
|
UE_LOG(LogQos, Display, TEXT("\t Enabled: %d Visible: %d Beta: %d"), RegionDef.bEnabled, RegionDef.bVisible, RegionDef.bAutoAssignable);
|
|
|
|
TSet<FString> FoundSubregions;
|
|
if (RegionInst)
|
|
{
|
|
for (const FDatacenterQosInstance& Datacenter : (*RegionInst)->DatacenterOptions)
|
|
{
|
|
for (const FQosDatacenterInfo* DatacenterDef : OutValues)
|
|
{
|
|
if (DatacenterDef->Id == Datacenter.Definition.Id)
|
|
{
|
|
FoundSubregions.Add(DatacenterDef->Id);
|
|
float ResponsePercent = (static_cast<float>(Datacenter.NumResponses) / static_cast<float>(NumTestsPerRegion)) * 100.0f;
|
|
UE_LOG(LogQos, Display, TEXT("\t Datacenter: %s%s %dms (%0.2f%%) %s"),
|
|
*DatacenterDef->Id, !DatacenterDef->bEnabled ? TEXT(" Disabled") : TEXT(""),
|
|
Datacenter.AvgPingMs, ResponsePercent, ToString(Datacenter.Result)
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const FQosDatacenterInfo* DatacenterDef : OutValues)
|
|
{
|
|
UE_CLOG(!FoundSubregions.Contains(DatacenterDef->Id), LogQos, Display, TEXT("\t Datacenter: %s%s"), *DatacenterDef->Id, !DatacenterDef->bEnabled ? TEXT(" Disabled") : TEXT(""));
|
|
}
|
|
|
|
if (!RegionInst)
|
|
{
|
|
UE_LOG(LogQos, Display, TEXT("No instances for region"));
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogQos, Display, TEXT("Results: %s"), ToString(QosEvalResult));
|
|
|
|
SanityCheckDefinitions();
|
|
}
|
|
|
|
void UQosRegionManager::RegisterQoSSettingsChangedDelegate(const FSimpleDelegate& OnQoSSettingsChanged)
|
|
{
|
|
UE_LOG(LogQos, Log, TEXT("[UQosRegionManager::RegisterQoSSettingsChangedDelegate] delegate was replaced"));
|
|
// add to the completion delegate
|
|
OnQoSSettingsChangedDelegate = OnQoSSettingsChanged;
|
|
}
|
|
|
|
#if DEBUG_SUBCOMPARE_BY_SUBSPACE
|
|
|
|
bool UQosRegionManager::TestCompareDatacentersBySubspace()
|
|
{
|
|
const FQosSubspaceComparisonParams Invariants(300, 8, 25, 50.0f);
|
|
|
|
// Parent is a non-subspace datacenter.
|
|
FQosDatacenterInfo ParentDatacenter;
|
|
ParentDatacenter.Id = TEXT("DE");
|
|
ParentDatacenter.RegionId = TEXT("EU");
|
|
ParentDatacenter.Servers.Add(FQosPingServerInfo{ "100.1.1.1", 2345 });
|
|
ParentDatacenter.Servers.Add(FQosPingServerInfo{ "100.1.1.2", 2345 });
|
|
ParentDatacenter.Servers.Add(FQosPingServerInfo{ "100.1.1.3", 2345 });
|
|
|
|
// Child is a subspcae datacenter.
|
|
FQosDatacenterInfo ChildDatacenter;
|
|
ChildDatacenter.Id = TEXT("DE_S");
|
|
ChildDatacenter.RegionId = TEXT("EU");
|
|
ChildDatacenter.Servers.Add(FQosPingServerInfo{ "100.1.2.11", 2345 });
|
|
ChildDatacenter.Servers.Add(FQosPingServerInfo{ "100.1.2.12", 2345 });
|
|
ChildDatacenter.Servers.Add(FQosPingServerInfo{ "100.1.2.13", 2345 });
|
|
|
|
// Test 1
|
|
// Parent is faster, somehow (potential edge case)
|
|
// Expected result: Pick parent. (override succeeds)
|
|
{
|
|
FDatacenterQosInstance ChildSubregion;
|
|
ChildSubregion.Definition = ChildDatacenter;
|
|
ChildSubregion.AvgPingMs = 68;
|
|
|
|
FDatacenterQosInstance ParentSubregion;
|
|
ParentSubregion.Definition = ParentDatacenter;
|
|
ParentSubregion.AvgPingMs = 24;
|
|
|
|
const FDatacenterQosInstance& RecommendedSubregion = FDatacenterQosInstance::CompareBiasedTowardsNonSubspace(
|
|
ParentSubregion, ChildSubregion, Invariants);
|
|
check(&RecommendedSubregion == &ParentSubregion);
|
|
}
|
|
|
|
// Test 2
|
|
// Parent is too slow.
|
|
// Expected result: Pick child. (override fails)
|
|
{
|
|
FDatacenterQosInstance ChildSubregion;
|
|
ChildSubregion.Definition = ChildDatacenter;
|
|
ChildSubregion.AvgPingMs = 48;
|
|
|
|
FDatacenterQosInstance ParentSubregion;
|
|
ParentSubregion.Definition = ParentDatacenter;
|
|
ParentSubregion.AvgPingMs = 360;
|
|
|
|
const FDatacenterQosInstance& RecommendedSubregion = FDatacenterQosInstance::CompareBiasedTowardsNonSubspace(
|
|
ParentSubregion, ChildSubregion, Invariants);
|
|
check(&RecommendedSubregion == &ChildSubregion);
|
|
}
|
|
|
|
// Test 3
|
|
// Parent is fast, but child is faster than minimum ping
|
|
// Expected result: Pick child. (override fails)
|
|
{
|
|
FDatacenterQosInstance ChildSubregion;
|
|
ChildSubregion.Definition = ChildDatacenter;
|
|
ChildSubregion.AvgPingMs = 6;
|
|
|
|
FDatacenterQosInstance ParentSubregion;
|
|
ParentSubregion.Definition = ParentDatacenter;
|
|
ParentSubregion.AvgPingMs = 34;
|
|
|
|
const FDatacenterQosInstance& RecommendedSubregion = FDatacenterQosInstance::CompareBiasedTowardsNonSubspace(
|
|
ParentSubregion, ChildSubregion, Invariants);
|
|
check(&RecommendedSubregion == &ChildSubregion);
|
|
}
|
|
|
|
// Test 4
|
|
// Parent is fast enough for consideration, child is slower than minimum ping.
|
|
// Difference in ping is GREATER THAN allowed constant tolerance in milliseconds.
|
|
// Expected result: Pick child. (override fails)
|
|
{
|
|
FDatacenterQosInstance ChildSubregion;
|
|
ChildSubregion.Definition = ChildDatacenter;
|
|
ChildSubregion.AvgPingMs = 48;
|
|
|
|
FDatacenterQosInstance ParentSubregion;
|
|
ParentSubregion.Definition = ParentDatacenter;
|
|
ParentSubregion.AvgPingMs = 78; // (78 - 48 = 30) > 25
|
|
|
|
const FDatacenterQosInstance& RecommendedSubregion = FDatacenterQosInstance::CompareBiasedTowardsNonSubspace(
|
|
ParentSubregion, ChildSubregion, Invariants);
|
|
check(&RecommendedSubregion == &ChildSubregion);
|
|
}
|
|
|
|
// If the difference in ping is LESS THAN or EQUAL TO allowed constant tolerance
|
|
// in milliseconds, then we test against proportional tolerance (below).
|
|
|
|
// Test 5.1
|
|
// Parent is fast enough for consideration, child is slower than minimum ping.
|
|
// Difference in ping is within constant tolerance (i.e. would pass test 4).
|
|
// Different in ping is GREATER THAN allowed proportional tolerance, as a %age of parent's ping.
|
|
// Expected result: Pick child. (override fails)
|
|
{
|
|
FDatacenterQosInstance ChildSubregion;
|
|
ChildSubregion.Definition = ChildDatacenter;
|
|
ChildSubregion.AvgPingMs = 10;
|
|
|
|
FDatacenterQosInstance ParentSubregion;
|
|
ParentSubregion.Definition = ParentDatacenter;
|
|
ParentSubregion.AvgPingMs = 24; // (24 - 10 = 14) > (0.5 * 24 = 12)
|
|
|
|
const FDatacenterQosInstance& RecommendedSubregion = FDatacenterQosInstance::CompareBiasedTowardsNonSubspace(
|
|
ParentSubregion, ChildSubregion, Invariants);
|
|
check(&RecommendedSubregion == &ChildSubregion);
|
|
}
|
|
|
|
// Test 5.2
|
|
// Parent is fast enough for consideration, child is slower than minimum ping.
|
|
// Difference in ping is within constant tolerance (i.e. would pass test 4).
|
|
// Different in ping is LESS THAN than allowed proportional tolerance, as a %age of parent's ping.
|
|
// Expected result: Pick parent. (override succeeds)
|
|
{
|
|
FDatacenterQosInstance ChildSubregion;
|
|
ChildSubregion.Definition = ChildDatacenter;
|
|
ChildSubregion.AvgPingMs = 10;
|
|
|
|
FDatacenterQosInstance ParentSubregion;
|
|
ParentSubregion.Definition = ParentDatacenter;
|
|
ParentSubregion.AvgPingMs = 16; // (16 - 10 = 6) < (0.5 * 16 = 8)
|
|
|
|
const FDatacenterQosInstance& RecommendedSubregion = FDatacenterQosInstance::CompareBiasedTowardsNonSubspace(
|
|
ParentSubregion, ChildSubregion, Invariants);
|
|
check(&RecommendedSubregion == &ParentSubregion);
|
|
}
|
|
|
|
// Test 5.3
|
|
// Parent is fast enough for consideration, child is slower than minimum ping.
|
|
// Difference in ping is within constant tolerance (i.e. would pass test 4).
|
|
// Different in ping is EQUAL TO allowed proportional tolerance, as a %age of parent's ping.
|
|
// Expected result: Pick parent. (override succeeds)
|
|
{
|
|
FDatacenterQosInstance ChildSubregion;
|
|
ChildSubregion.Definition = ChildDatacenter;
|
|
ChildSubregion.AvgPingMs = 12;
|
|
|
|
FDatacenterQosInstance ParentSubregion;
|
|
ParentSubregion.Definition = ParentDatacenter;
|
|
ParentSubregion.AvgPingMs = 24; // (24 - 12 = 12) == (0.5 * 24 = 12)
|
|
|
|
const FDatacenterQosInstance& RecommendedSubregion = FDatacenterQosInstance::CompareBiasedTowardsNonSubspace(
|
|
ParentSubregion, ChildSubregion, Invariants);
|
|
check(&RecommendedSubregion == &ParentSubregion);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UQosRegionManager::TestSortDatacenterSubspacesByRecommended()
|
|
{
|
|
const FQosSubspaceComparisonParams Invariants(300, 8, 25, 50.0f);
|
|
|
|
// Example region
|
|
|
|
FQosRegionInfo ExampleRegion;
|
|
ExampleRegion.DisplayName = NSLOCTEXT("MMRegion", "Europe", "Europe");
|
|
ExampleRegion.RegionId = TEXT("EU");
|
|
|
|
FQosDatacenterInfo ParentDatacenter_DE;
|
|
ParentDatacenter_DE.Id = TEXT("DE");
|
|
ParentDatacenter_DE.RegionId = TEXT("EU");
|
|
ParentDatacenter_DE.Servers.Add(FQosPingServerInfo{ "100.1.1.1", 2345 });
|
|
ParentDatacenter_DE.Servers.Add(FQosPingServerInfo{ "100.1.1.2", 2345 });
|
|
ParentDatacenter_DE.Servers.Add(FQosPingServerInfo{ "100.1.1.3", 2345 });
|
|
|
|
FQosDatacenterInfo ChildDatacenter_DE_S1;
|
|
ChildDatacenter_DE_S1.Id = TEXT("DE_S1");
|
|
ChildDatacenter_DE_S1.RegionId = TEXT("EU");
|
|
ChildDatacenter_DE_S1.Servers.Add(FQosPingServerInfo{ "100.2.1.11", 2345 });
|
|
ChildDatacenter_DE_S1.Servers.Add(FQosPingServerInfo{ "100.2.1.12", 2345 });
|
|
ChildDatacenter_DE_S1.Servers.Add(FQosPingServerInfo{ "100.2.1.13", 2345 });
|
|
|
|
FQosDatacenterInfo ChildDatacenter_DE_S2;
|
|
ChildDatacenter_DE_S2.Id = TEXT("DE_S2");
|
|
ChildDatacenter_DE_S2.RegionId = TEXT("EU");
|
|
ChildDatacenter_DE_S2.Servers.Add(FQosPingServerInfo{ "100.3.1.11", 2345 });
|
|
ChildDatacenter_DE_S2.Servers.Add(FQosPingServerInfo{ "100.3.1.12", 2345 });
|
|
ChildDatacenter_DE_S2.Servers.Add(FQosPingServerInfo{ "100.3.1.13", 2345 });
|
|
|
|
FQosDatacenterInfo ParentDatacenter_FR;
|
|
ParentDatacenter_FR.Id = TEXT("FR");
|
|
ParentDatacenter_FR.RegionId = TEXT("EU");
|
|
ParentDatacenter_FR.Servers.Add(FQosPingServerInfo{ "105.1.1.1", 2345 });
|
|
ParentDatacenter_FR.Servers.Add(FQosPingServerInfo{ "105.1.1.2", 2345 });
|
|
ParentDatacenter_FR.Servers.Add(FQosPingServerInfo{ "105.1.1.3", 2345 });
|
|
|
|
FQosDatacenterInfo ParentDatacenter_GB;
|
|
ParentDatacenter_GB.Id = TEXT("GB");
|
|
ParentDatacenter_GB.RegionId = TEXT("EU");
|
|
ParentDatacenter_GB.Servers.Add(FQosPingServerInfo{ "120.1.1.1", 2345 });
|
|
ParentDatacenter_GB.Servers.Add(FQosPingServerInfo{ "120.1.1.2", 2345 });
|
|
ParentDatacenter_GB.Servers.Add(FQosPingServerInfo{ "120.1.1.3", 2345 });
|
|
|
|
FQosDatacenterInfo ChildDatacenter_GB_S;
|
|
ChildDatacenter_GB_S.Id = TEXT("GB_S");
|
|
ChildDatacenter_GB_S.RegionId = TEXT("EU");
|
|
ChildDatacenter_GB_S.Servers.Add(FQosPingServerInfo{ "120.2.1.11", 2345 });
|
|
ChildDatacenter_GB_S.Servers.Add(FQosPingServerInfo{ "120.2.1.12", 2345 });
|
|
ChildDatacenter_GB_S.Servers.Add(FQosPingServerInfo{ "120.2.1.13", 2345 });
|
|
|
|
const TCHAR* const SubspaceDelimiter = TEXT("_");
|
|
|
|
// Test cases.
|
|
|
|
FRegionQosInstance TestRegionQosResult;
|
|
TestRegionQosResult.Definition = ExampleRegion;
|
|
|
|
{
|
|
FDatacenterQosInstance SubregionResult_DE;
|
|
SubregionResult_DE.Definition = ParentDatacenter_DE;
|
|
SubregionResult_DE.AvgPingMs = 56;
|
|
|
|
FDatacenterQosInstance SubregionResult_DE_S1;
|
|
SubregionResult_DE_S1.Definition = ChildDatacenter_DE_S1;
|
|
SubregionResult_DE_S1.AvgPingMs = 48;
|
|
|
|
FDatacenterQosInstance SubregionResult_DE_S2;
|
|
SubregionResult_DE_S2.Definition = ChildDatacenter_DE_S2;
|
|
SubregionResult_DE_S2.AvgPingMs = 36;
|
|
|
|
FDatacenterQosInstance SubregionResult_FR;
|
|
SubregionResult_FR.Definition = ParentDatacenter_FR;
|
|
SubregionResult_FR.AvgPingMs = 74;
|
|
|
|
FDatacenterQosInstance SubregionResult_GB;
|
|
SubregionResult_GB.Definition = ParentDatacenter_GB;
|
|
SubregionResult_GB.AvgPingMs = 92;
|
|
|
|
FDatacenterQosInstance SubregionResult_GB_S;
|
|
SubregionResult_GB_S.Definition = ChildDatacenter_GB_S;
|
|
SubregionResult_GB_S.AvgPingMs = 84;
|
|
|
|
TestRegionQosResult.DatacenterOptions.Empty();
|
|
TestRegionQosResult.DatacenterOptions.Add(SubregionResult_DE);
|
|
TestRegionQosResult.DatacenterOptions.Add(SubregionResult_DE_S1);
|
|
TestRegionQosResult.DatacenterOptions.Add(SubregionResult_DE_S2);
|
|
TestRegionQosResult.DatacenterOptions.Add(SubregionResult_FR);
|
|
TestRegionQosResult.DatacenterOptions.Add(SubregionResult_GB);
|
|
TestRegionQosResult.DatacenterOptions.Add(SubregionResult_GB_S);
|
|
|
|
TestRegionQosResult.SortDatacenterSubspacesByRecommended(Invariants, SubspaceDelimiter);
|
|
|
|
check(TestRegionQosResult.DatacenterOptions[0].Definition.Id == SubregionResult_DE.Definition.Id); // avg ping = 56
|
|
check(TestRegionQosResult.DatacenterOptions[1].Definition.Id == SubregionResult_DE_S2.Definition.Id); // avg ping = 36
|
|
check(TestRegionQosResult.DatacenterOptions[2].Definition.Id == SubregionResult_DE_S1.Definition.Id); // avg ping = 48
|
|
check(TestRegionQosResult.DatacenterOptions[3].Definition.Id == SubregionResult_FR.Definition.Id); // avg ping = 74
|
|
check(TestRegionQosResult.DatacenterOptions[4].Definition.Id == SubregionResult_GB.Definition.Id); // avg ping = 92
|
|
check(TestRegionQosResult.DatacenterOptions[5].Definition.Id == SubregionResult_GB_S.Definition.Id); // avg ping = 84
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
FRegionQosInstance UQosRegionManager::TestCreateExampleRegionResult()
|
|
{
|
|
// Definitions.
|
|
|
|
static const FString RegionId = TEXT("NAE");
|
|
|
|
FQosRegionInfo ExampleRegion;
|
|
ExampleRegion.DisplayName = NSLOCTEXT("MMRegion", "NA-East", "NA-East");
|
|
ExampleRegion.RegionId = RegionId;
|
|
ExampleRegion.RegionId = RegionId;
|
|
ExampleRegion.bEnabled = true;
|
|
ExampleRegion.bVisible = true;
|
|
ExampleRegion.bAutoAssignable = true;
|
|
|
|
ExampleRegion.bAllowSubspaceBias = true;
|
|
ExampleRegion.SubspaceBiasParams = FQosSubspaceComparisonParams(300, 8, 25, 50.0f);
|
|
|
|
FQosDatacenterInfo ParentDatacenter_VA;
|
|
ParentDatacenter_VA.Id = TEXT("VA");
|
|
ParentDatacenter_VA.RegionId = RegionId;
|
|
ParentDatacenter_VA.bEnabled = true;
|
|
ParentDatacenter_VA.Servers.Add(FQosPingServerInfo{ "334.193.154.39", 22222 });
|
|
ParentDatacenter_VA.Servers.Add(FQosPingServerInfo{ "352.203.3.55", 22222 });
|
|
ParentDatacenter_VA.Servers.Add(FQosPingServerInfo{ "354.82.195.216", 22222 });
|
|
ParentDatacenter_VA.Servers.Add(FQosPingServerInfo{ "334.194.116.183", 22222 });
|
|
|
|
FQosDatacenterInfo ChildDatacenter_VA_S1;
|
|
ChildDatacenter_VA_S1.Id = TEXT("VA_S1");
|
|
ChildDatacenter_VA_S1.RegionId = RegionId;
|
|
ChildDatacenter_VA_S1.bEnabled = true;
|
|
ChildDatacenter_VA_S1.Servers.Add(FQosPingServerInfo{ "352.203.34.155", 22222 });
|
|
ChildDatacenter_VA_S1.Servers.Add(FQosPingServerInfo{ "352.203.34.156", 22222 });
|
|
ChildDatacenter_VA_S1.Servers.Add(FQosPingServerInfo{ "352.203.34.157", 22222 });
|
|
|
|
FQosDatacenterInfo ChildDatacenter_VA_S2;
|
|
ChildDatacenter_VA_S2.Id = TEXT("VA_S2");
|
|
ChildDatacenter_VA_S2.RegionId = RegionId;
|
|
ChildDatacenter_VA_S2.bEnabled = true;
|
|
ChildDatacenter_VA_S2.Servers.Add(FQosPingServerInfo{ "354.82.199.216", 22222 });
|
|
ChildDatacenter_VA_S2.Servers.Add(FQosPingServerInfo{ "354.82.199.217", 22222 });
|
|
ChildDatacenter_VA_S2.Servers.Add(FQosPingServerInfo{ "354.82.199.218", 22222 });
|
|
|
|
FQosDatacenterInfo ChildDatacenter_VA_S3;
|
|
ChildDatacenter_VA_S3.Id = TEXT("VA_S3");
|
|
ChildDatacenter_VA_S3.RegionId = RegionId;
|
|
ChildDatacenter_VA_S3.bEnabled = true;
|
|
ChildDatacenter_VA_S3.Servers.Add(FQosPingServerInfo{ "334.194.111.183", 22222 });
|
|
ChildDatacenter_VA_S3.Servers.Add(FQosPingServerInfo{ "334.194.111.184", 22222 });
|
|
ChildDatacenter_VA_S3.Servers.Add(FQosPingServerInfo{ "334.194.111.185", 22222 });
|
|
|
|
FQosDatacenterInfo ParentDatacenter_OH;
|
|
ParentDatacenter_OH.Id = TEXT("OH");
|
|
ParentDatacenter_OH.RegionId = RegionId;
|
|
ParentDatacenter_OH.bEnabled = true;
|
|
ParentDatacenter_OH.Servers.Add(FQosPingServerInfo{ "313.59.18.131", 22222 });
|
|
ParentDatacenter_OH.Servers.Add(FQosPingServerInfo{ "318.216.47.148", 22222 });
|
|
ParentDatacenter_OH.Servers.Add(FQosPingServerInfo{ "318.221.198.242", 22222 });
|
|
ParentDatacenter_OH.Servers.Add(FQosPingServerInfo{ "352.15.144.157", 22222 });
|
|
|
|
FQosDatacenterInfo ChildDatacenter_OH_S;
|
|
ChildDatacenter_OH_S.Id = TEXT("OH_S");
|
|
ChildDatacenter_OH_S.RegionId = RegionId;
|
|
ChildDatacenter_OH_S.bEnabled = true;
|
|
ChildDatacenter_OH_S.Servers.Add(FQosPingServerInfo{ "318.216.49.148", 22222 });
|
|
ChildDatacenter_OH_S.Servers.Add(FQosPingServerInfo{ "318.216.49.149", 22222 });
|
|
ChildDatacenter_OH_S.Servers.Add(FQosPingServerInfo{ "318.216.49.150", 22222 });
|
|
|
|
// Results
|
|
|
|
FRegionQosInstance OutRegionResult;
|
|
OutRegionResult.Definition = ExampleRegion;
|
|
OutRegionResult.DatacenterOptions.Empty();
|
|
|
|
FDatacenterQosInstance SubregionResult_VA;
|
|
SubregionResult_VA.Definition = ParentDatacenter_VA;
|
|
SubregionResult_VA.AvgPingMs = 96;
|
|
OutRegionResult.DatacenterOptions.Add(SubregionResult_VA);
|
|
|
|
FDatacenterQosInstance SubregionResult_VA_S1;
|
|
SubregionResult_VA_S1.Definition = ChildDatacenter_VA_S1;
|
|
SubregionResult_VA_S1.AvgPingMs = 91;
|
|
OutRegionResult.DatacenterOptions.Add(SubregionResult_VA_S1);
|
|
|
|
FDatacenterQosInstance SubregionResult_VA_S2;
|
|
SubregionResult_VA_S2.Definition = ChildDatacenter_VA_S2;
|
|
SubregionResult_VA_S2.AvgPingMs = 88;
|
|
OutRegionResult.DatacenterOptions.Add(SubregionResult_VA_S2);
|
|
|
|
FDatacenterQosInstance SubregionResult_VA_S3;
|
|
SubregionResult_VA_S3.Definition = ChildDatacenter_VA_S3;
|
|
SubregionResult_VA_S3.AvgPingMs = 90;
|
|
OutRegionResult.DatacenterOptions.Add(SubregionResult_VA_S3);
|
|
|
|
FDatacenterQosInstance SubregionResult_OH;
|
|
SubregionResult_OH.Definition = ParentDatacenter_OH;
|
|
SubregionResult_OH.AvgPingMs = 117;
|
|
OutRegionResult.DatacenterOptions.Add(SubregionResult_OH);
|
|
|
|
FDatacenterQosInstance SubregionResult_OH_S;
|
|
SubregionResult_OH_S.Definition = ChildDatacenter_OH_S;
|
|
SubregionResult_OH_S.AvgPingMs = 97;
|
|
OutRegionResult.DatacenterOptions.Add(SubregionResult_OH_S);
|
|
|
|
return OutRegionResult;
|
|
}
|
|
|
|
#endif // DEBUG_SUBCOMPARE_BY_SUBSPACE
|
|
|