Files
UnrealEngineUWP/Engine/Source/Developer/DerivedDataCache/Private/Tests/HttpDerivedDataBackendTest.cpp
danny couture 950c53fc2b Fix thread starvation for request in http backend by using a FIFO for thread waiting for a connection to be freed
Replace the sleep by a wait on an event triggered by the thread freeing the connection
Improve the test to output stats seen below and use the proper amount of threads on low core systems

Automation RunTests System.DerivedDataCache.HttpDerivedDataBackend.ConcurrentCachedDataProbablyExistsBatch

Before
   RPS: 238, AvgLatency: 90.09 ms, MaxLatency: 7.77 s
   RPS: 231, AvgLatency: 87.66 ms, MaxLatency: 7.77 s
   RPS: 245, AvgLatency: 139.07 ms, MaxLatency: 10.08 s

After
   RPS: 634, AvgLatency: 102.97 ms, MaxLatency: 0.26 s
   RPS: 653, AvgLatency: 102.51 ms, MaxLatency: 0.26 s
   RPS: 630, AvgLatency: 102.29 ms, MaxLatency: 0.26 s

#rnx
#rb Zousar.Shaker, Francis.Hurteau
#preflight 60ffe8317f21c90001f6116a

#ROBOMERGE-SOURCE: CL 16968010 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v838-16927207)

[CL 16968015 by danny couture in ue5-release-engine-test branch]
2021-07-27 09:26:42 -04:00

204 lines
6.0 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "HttpDerivedDataBackend.h"
#include "Async/ParallelFor.h"
#include "DerivedDataBackendInterface.h"
#include "Misc/AutomationTest.h"
#include "Misc/SecureHash.h"
// Test is targeted at HttpDerivedDataBackend but with some backend test interface it could be generalized
// to function against all backends.
#if WITH_DEV_AUTOMATION_TESTS && WITH_HTTP_DDC_BACKEND
DEFINE_LOG_CATEGORY_STATIC(LogHttpDerivedDataBackendTests, Log, All);
#define TEST_NAME_ROOT TEXT("System.DerivedDataCache.HttpDerivedDataBackend")
#define IMPLEMENT_HTTPDERIVEDDATA_AUTOMATION_TEST( TClass, PrettyName, TFlags ) \
IMPLEMENT_CUSTOM_COMPLEX_AUTOMATION_TEST(TClass, FHttpDerivedDataTestBase, TEST_NAME_ROOT PrettyName, TFlags) \
void TClass::GetTests(TArray<FString>& OutBeautifiedNames, TArray <FString>& OutTestCommands) const \
{ \
if (CheckPrequisites()) \
{ \
OutBeautifiedNames.Add(TEST_NAME_ROOT PrettyName); \
OutTestCommands.Add(FString()); \
} \
}
namespace HttpDerivedDataBackendTest
{
class FHttpDerivedDataTestBase : public FAutomationTestBase
{
public:
FHttpDerivedDataTestBase(const FString& InName, const bool bInComplexTask)
: FAutomationTestBase(InName, bInComplexTask)
{
}
bool CheckPrequisites() const
{
if (UE::DerivedData::Backends::FHttpDerivedDataBackend* Backend = GetTestBackend())
{
if (Backend->IsUsable())
{
return true;
}
}
return false;
}
protected:
void ConcurrentTestWithStats(TFunctionRef<void()> TestFunction, int32 ThreadCount, double Duration)
{
std::atomic<uint64> Requests{ 0 };
std::atomic<uint64> MaxLatency{ 0 };
std::atomic<uint64> TotalMS{ 0 };
std::atomic<uint64> TotalRequests{ 0 };
FEvent* StartEvent = FPlatformProcess::GetSynchEventFromPool(true);
FEvent* LastEvent = FPlatformProcess::GetSynchEventFromPool(true);
std::atomic<double> StopTime{ 0.0 };
std::atomic<uint64> ActiveCount{ 0 };
for (int32 ThreadIndex = 0; ThreadIndex < ThreadCount; ++ThreadIndex)
{
ActiveCount++;
Async(
ThreadIndex < FTaskGraphInterface::Get().GetNumWorkerThreads() ? EAsyncExecution::TaskGraph : EAsyncExecution::Thread,
[&]()
{
// No false start, wait until everyone is ready before starting the test
StartEvent->Wait();
while (FPlatformTime::Seconds() < StopTime.load(std::memory_order_relaxed))
{
uint64 Before = FPlatformTime::Cycles64();
TestFunction();
uint64 Delta = FPlatformTime::Cycles64() - Before;
Requests++;
TotalMS += FPlatformTime::ToMilliseconds64(Delta);
TotalRequests++;
// Compare exchange loop until we either succeed to set the maximum value
// or we bail out because we don't have the maximum value anymore.
while (true)
{
uint64 Snapshot = MaxLatency.load();
if (Delta > Snapshot)
{
// Only do the exchange if the value has not changed since we confirmed
// we had a bigger one.
if (MaxLatency.compare_exchange_strong(Snapshot, Delta))
{
// Exchange succeeded
break;
}
}
else
{
// We don't have the maximum
break;
}
}
}
if (--ActiveCount == 0)
{
LastEvent->Trigger();
}
}
);
}
StopTime = FPlatformTime::Seconds() + Duration;
// GO!
StartEvent->Trigger();
while (FPlatformTime::Seconds() < StopTime)
{
FPlatformProcess::Sleep(1.0f);
if (TotalRequests)
{
UE_LOG(LogHttpDerivedDataBackendTests, Display, TEXT("RPS: %llu, AvgLatency: %.02f ms, MaxLatency: %.02f s"), Requests.exchange(0), double(TotalMS) / TotalRequests, FPlatformTime::ToSeconds(MaxLatency));
}
else
{
UE_LOG(LogHttpDerivedDataBackendTests, Display, TEXT("RPS: %llu, AvgLatency: N/A, MaxLatency: %.02f s"), Requests.exchange(0), FPlatformTime::ToSeconds(MaxLatency));
}
}
LastEvent->Wait();
FPlatformProcess::ReturnSynchEventToPool(StartEvent);
FPlatformProcess::ReturnSynchEventToPool(LastEvent);
}
UE::DerivedData::Backends::FHttpDerivedDataBackend* GetTestBackend() const
{
static UE::DerivedData::Backends::FHttpDerivedDataBackend* CachedBackend = FetchTestBackend_Internal();
return CachedBackend;
}
private:
UE::DerivedData::Backends::FHttpDerivedDataBackend* FetchTestBackend_Internal() const
{
return UE::DerivedData::Backends::FHttpDerivedDataBackend::GetAny();
}
};
IMPLEMENT_HTTPDERIVEDDATA_AUTOMATION_TEST(FConcurrentCachedDataProbablyExistsBatch, TEXT(".ConcurrentCachedDataProbablyExistsBatch"), EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter)
bool FConcurrentCachedDataProbablyExistsBatch::RunTest(const FString& Parameters)
{
UE::DerivedData::Backends::FHttpDerivedDataBackend* TestBackend = GetTestBackend();
const int32 ThreadCount = 64;
const double Duration = 10;
const uint32 KeysInBatch = 4;
TArray<FString> Keys;
TArray<uint8> KeyContents;
KeyContents.Add(42);
FSHA1 HashState;
HashState.Update(KeyContents.GetData(), KeyContents.Num());
HashState.Final();
uint8 Hash[FSHA1::DigestSize];
HashState.GetHash(Hash);
const FString HashString = BytesToHex(Hash, FSHA1::DigestSize);
for (uint32 KeyIndex = 0; KeyIndex < KeysInBatch; ++KeyIndex)
{
FString NewKey = FString::Printf(TEXT("__AutoTest_Dummy_%u__%s"), KeyIndex, *HashString);
Keys.Add(NewKey);
TestBackend->PutCachedData(*NewKey, KeyContents, false);
}
std::atomic<uint32> MismatchedResults = 0;
ConcurrentTestWithStats(
[&]()
{
TConstArrayView<FString> BatchView = MakeArrayView(Keys.GetData(), KeysInBatch);
TBitArray<> Result = TestBackend->CachedDataProbablyExistsBatch(BatchView);
if (Result.CountSetBits() != BatchView.Num())
{
MismatchedResults.fetch_add(BatchView.Num() - Result.CountSetBits(), std::memory_order_relaxed);
}
},
ThreadCount,
Duration
);
TestEqual(TEXT("Concurrent calls to CachedDataProbablyExistsBatch for a batch of keys that were put are not reliably found"), MismatchedResults, 0);
return true;
}
}
#endif // #if WITH_DEV_AUTOMATION_TESTS && WITH_HTTP_DDC_BACKEND