Files
UnrealEngine/Engine/Source/Programs/AutoRTFMTests/Private/OpenHashThrottlerTests.cpp
2025-05-18 13:04:45 +08:00

271 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "WriteLog.h"
#include "Catch2Includes.h"
#include "OpenHashThrottler.h"
TEST_CASE("OpenHashThrottler")
{
using FSeconds = AutoRTFM::FOpenHashThrottler::FSeconds;
AutoRTFM::FOpenHashThrottler Throttler{
/* LogInterval */ 1e10, // Not testing logging.
/* AdjustThrottleInterval */ 0.1, // Adjust probabilities every 100ms.
/* TargetFractionHashing */ 0.1 // At most we want to spent 10% of the time hashing.
};
const void* OpenReturnAddressA = reinterpret_cast<const void*>(static_cast<uintptr_t>(0x10000));
const void* OpenReturnAddressB = reinterpret_cast<const void*>(static_cast<uintptr_t>(0x20000));
const void* OpenReturnAddressC = reinterpret_cast<const void*>(static_cast<uintptr_t>(0x30000));
AutoRTFM::FWriteLog WriteLog;
// The current time for the tests.
FSeconds Time = 0;
auto Hash = [&](FSeconds DurationNotHashing, FSeconds DurationHashing, const void* OpenReturnAddress)
{
Time += DurationNotHashing;
Throttler.OnHash(Time, Time + DurationHashing, OpenReturnAddress, WriteLog);
Time += DurationHashing;
};
FSeconds LastUpdate = 0;
auto Update = [&](FSeconds Duration = 0)
{
Time += Duration;
Throttler.Update(Time - LastUpdate);
LastUpdate = Time;
};
SECTION("Initial state")
{
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressA) == 1.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressB) == 1.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressC) == 1.0);
}
SECTION("Before adjustment")
{
Hash(/* DurationNotHashing */ 0, /* DurationHashing */ 0.01, OpenReturnAddressA);
Hash(/* DurationNotHashing */ 0, /* DurationHashing */ 0.01, OpenReturnAddressB);
Hash(/* DurationNotHashing */ 0, /* DurationHashing */ 0.01, OpenReturnAddressC);
Update();
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressA) == 1.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressB) == 1.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressC) == 1.0);
}
SECTION("Within TargetFractionHashing limits")
{
for (int I = 0; I < 100; I++)
{
// 1% of the time spent hashing (less than the 10% threshold to throttle)
Hash(/* DurationNotHashing */ 0.099, /* DurationHashing */ 0.001, OpenReturnAddressA);
Hash(/* DurationNotHashing */ 0.099, /* DurationHashing */ 0.001, OpenReturnAddressB);
Hash(/* DurationNotHashing */ 0.099, /* DurationHashing */ 0.001, OpenReturnAddressC);
Update();
}
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressA) == 1.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressB) == 1.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressC) == 1.0);
}
SECTION("Exceeds TargetFractionHashing limits")
{
SECTION("Small Margin") // limit exceeded by typical margin (1x - 2x)
{
for (int I = 0; I < 100; I++)
{
// 15% of the time spent hashing (greater than the 10% threshold to throttle)
Hash(/* DurationNotHashing */ 0.85, /* DurationHashing */ 0.15, OpenReturnAddressA);
Hash(/* DurationNotHashing */ 0.85, /* DurationHashing */ 0.15, OpenReturnAddressB);
Hash(/* DurationNotHashing */ 0.85, /* DurationHashing */ 0.15, OpenReturnAddressC);
Update();
}
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressA) < 1.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressA) > 0.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressB) < 1.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressB) > 0.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressC) < 1.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressC) > 0.0);
}
SECTION("Excessive") // limit exceeded by large margin (2x+)
{
const double OriginalProbabilityC = Throttler.HashProbabilityFor(OpenReturnAddressC);
for (int I = 0; I < 100; I++)
{
// 1% of the time hashing (below throttling level)
Hash(/* DurationNotHashing */ 0.99, /* DurationHashing */ 0.01, OpenReturnAddressC);
Update();
}
// Below throttling threshold, should not have been updated.
REQUIRE(OriginalProbabilityC == Throttler.HashProbabilityFor(OpenReturnAddressC));
for (int I = 0; I < 100; I++)
{
// 30% of the time spent hashing (greater than the 10% threshold to throttle and
// more then 2x the budget)
Hash(/* DurationNotHashing */ 0.60, /* DurationHashing */ 0.40, OpenReturnAddressA);
Hash(/* DurationNotHashing */ 0.70, /* DurationHashing */ 0.30, OpenReturnAddressB);
Update();
}
// When excessively exceeding the budget, all open probabilities are
// uniformly reduced, including inactive opens.
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressA) < 1.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressA) > 0.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressB) == Throttler.HashProbabilityFor(OpenReturnAddressA));
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressC) < OriginalProbabilityC);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressC) > 0.0);
}
}
SECTION("Normalize by duration")
{
for (int I = 0; I < 100; I++)
{
// Time spent hashing ratios:
// OpenReturnAddressA:OpenReturnAddressB:OpenReturnAddressC - 5:3:1
Hash(/* DurationNotHashing */ 0.25, /* DurationHashing */ 0.05, OpenReturnAddressA);
Hash(/* DurationNotHashing */ 0.25, /* DurationHashing */ 0.03, OpenReturnAddressB);
Hash(/* DurationNotHashing */ 0.25, /* DurationHashing */ 0.01, OpenReturnAddressC);
Update();
}
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressA) < 1.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressA) > 0.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressB) < 1.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressB) > 0.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressC) <= 1.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressC) > 0.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressA) < Throttler.HashProbabilityFor(OpenReturnAddressB));
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressB) < Throttler.HashProbabilityFor(OpenReturnAddressC));
}
SECTION("Normalize by frequency")
{
for (int I = 0; I < 100; I++)
{
// Hash count ratios:
// OpenReturnAddressA:OpenReturnAddressB:OpenReturnAddressC - 5:3:1
Hash(/* DurationNotHashing */ 0.25, /* DurationHashing */ 0.05, OpenReturnAddressA);
Hash(/* DurationNotHashing */ 0.25, /* DurationHashing */ 0.05, OpenReturnAddressB);
Hash(/* DurationNotHashing */ 0.25, /* DurationHashing */ 0.05, OpenReturnAddressA);
Hash(/* DurationNotHashing */ 0.25, /* DurationHashing */ 0.05, OpenReturnAddressC);
Hash(/* DurationNotHashing */ 0.25, /* DurationHashing */ 0.05, OpenReturnAddressA);
Hash(/* DurationNotHashing */ 0.25, /* DurationHashing */ 0.05, OpenReturnAddressB);
Hash(/* DurationNotHashing */ 0.25, /* DurationHashing */ 0.05, OpenReturnAddressA);
Hash(/* DurationNotHashing */ 0.25, /* DurationHashing */ 0.05, OpenReturnAddressB);
Hash(/* DurationNotHashing */ 0.25, /* DurationHashing */ 0.05, OpenReturnAddressA);
Update();
}
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressA) < 1.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressA) > 0.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressB) < 1.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressB) > 0.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressC) <= 1.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressC) > 0.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressA) < Throttler.HashProbabilityFor(OpenReturnAddressB));
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressB) < Throttler.HashProbabilityFor(OpenReturnAddressC));
}
SECTION("New opens inherit lowest probability")
{
for (int I = 0; I < 100; I++)
{
Hash(/* DurationNotHashing */ 0.25, /* DurationHashing */ 0.05, OpenReturnAddressA);
Hash(/* DurationNotHashing */ 0.25, /* DurationHashing */ 0.03, OpenReturnAddressB);
Update();
}
Hash(/* DurationNotHashing */ 0.20, /* DurationHashing */ 0.01, OpenReturnAddressC);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressA) < Throttler.HashProbabilityFor(OpenReturnAddressB));
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressC) == Throttler.HashProbabilityFor(OpenReturnAddressA));
}
SECTION("Preserve probabilities for old open return addresses")
{
// Throttle OpenReturnAddressA
for (int I = 0; I < 10; I++)
{
Hash(/* DurationNotHashing */ 0.05, /* DurationHashing */ 0.02, OpenReturnAddressA);
Update();
}
Update();
const double ProbabilityA = Throttler.HashProbabilityFor(OpenReturnAddressA);
REQUIRE(ProbabilityA < 1.0);
// Now throttle OpenReturnAddressB and OpenReturnAddressC
for (int I = 0; I < 10; I++)
{
Hash(/* DurationNotHashing */ 0.05, /* DurationHashing */ 0.01, OpenReturnAddressB);
Hash(/* DurationNotHashing */ 0.05, /* DurationHashing */ 0.01, OpenReturnAddressC);
Update();
}
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressB) < 1.0);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressC) < 1.0);
// Even though OpenReturnAddressA wasn't used in the last 100 hashes, its probability remains untouched.
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressA) == ProbabilityA);
}
SECTION("Raise probabilities by reduced duration hashing")
{
for (int I = 0; I < 100; I++)
{
Hash(/* DurationNotHashing */ 0.5, /* DurationHashing */ 0.2, OpenReturnAddressA);
Hash(/* DurationNotHashing */ 0.5, /* DurationHashing */ 0.2, OpenReturnAddressB);
Hash(/* DurationNotHashing */ 0.5, /* DurationHashing */ 0.2, OpenReturnAddressC);
Update(1.0);
}
const double ProbabilityA = Throttler.HashProbabilityFor(OpenReturnAddressA);
const double ProbabilityB = Throttler.HashProbabilityFor(OpenReturnAddressB);
const double ProbabilityC = Throttler.HashProbabilityFor(OpenReturnAddressC);
for (int I = 0; I < 100; I++)
{
Hash(/* DurationNotHashing */ 0.5, /* DurationHashing */ 0.1, OpenReturnAddressA);
Hash(/* DurationNotHashing */ 0.5, /* DurationHashing */ 0.1, OpenReturnAddressB);
Hash(/* DurationNotHashing */ 0.5, /* DurationHashing */ 0.1, OpenReturnAddressC);
Update(1.0);
}
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressA) > ProbabilityA);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressB) > ProbabilityB);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressC) > ProbabilityC);
}
SECTION("Raise probabilities by reduced frequency")
{
for (int I = 0; I < 100; I++)
{
Hash(/* DurationNotHashing */ 0.25, /* DurationHashing */ 0.10, OpenReturnAddressA);
Hash(/* DurationNotHashing */ 0.25, /* DurationHashing */ 0.10, OpenReturnAddressB);
Hash(/* DurationNotHashing */ 0.25, /* DurationHashing */ 0.10, OpenReturnAddressC);
Hash(/* DurationNotHashing */ 0.25, /* DurationHashing */ 0.10, OpenReturnAddressA);
Hash(/* DurationNotHashing */ 0.25, /* DurationHashing */ 0.10, OpenReturnAddressB);
Hash(/* DurationNotHashing */ 0.25, /* DurationHashing */ 0.10, OpenReturnAddressC);
Update(1.0);
}
const double ProbabilityA = Throttler.HashProbabilityFor(OpenReturnAddressA);
const double ProbabilityB = Throttler.HashProbabilityFor(OpenReturnAddressB);
const double ProbabilityC = Throttler.HashProbabilityFor(OpenReturnAddressC);
for (int I = 0; I < 100; I++)
{
Hash(/* DurationNotHashing */ 0.35, /* DurationHashing */ 0.10, OpenReturnAddressA);
Hash(/* DurationNotHashing */ 0.35, /* DurationHashing */ 0.10, OpenReturnAddressB);
Hash(/* DurationNotHashing */ 0.35, /* DurationHashing */ 0.10, OpenReturnAddressC);
Update(1.0);
}
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressA) > ProbabilityA);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressB) > ProbabilityB);
REQUIRE(Throttler.HashProbabilityFor(OpenReturnAddressC) > ProbabilityC);
}
}