From e49775a1734abd623f75263b9d99c04c483aa91b Mon Sep 17 00:00:00 2001 From: Jack He Date: Tue, 13 Aug 2024 16:00:59 -0700 Subject: [PATCH] add option to configure TCP execution context --- src/perf/lib/PerfClient.cpp | 3 ++- src/perf/lib/PerfServer.h | 2 +- src/perf/lib/SecNetPerf.h | 6 ++++++ src/perf/lib/SecNetPerfMain.cpp | 7 ++++++- src/perf/lib/Tcp.cpp | 12 ++++++++++-- src/perf/lib/Tcp.h | 4 +++- src/perf/test.cpp | 18 ++++++++++++++++++ src/perf/test.exe | Bin 0 -> 57079 bytes src/perf/test.ps1 | 26 ++++++++++++++++++++++++++ 9 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 src/perf/test.cpp create mode 100644 src/perf/test.exe create mode 100644 src/perf/test.ps1 diff --git a/src/perf/lib/PerfClient.cpp b/src/perf/lib/PerfClient.cpp index 8a01873a35..d70f5c7337 100644 --- a/src/perf/lib/PerfClient.cpp +++ b/src/perf/lib/PerfClient.cpp @@ -229,7 +229,8 @@ PerfClient::Init( nullptr, PerfClientConnection::TcpConnectCallback, PerfClientConnection::TcpReceiveCallback, - PerfClientConnection::TcpSendCompleteCallback)); + PerfClientConnection::TcpSendCompleteCallback, + TCP_EXECUTION_PROFILE_MAX_THROUGHPUT)); // TODO: Client should always be max tput? } else { if (UseSendBuffering || !UsePacing) { // Update settings if non-default MsQuicSettings Settings; diff --git a/src/perf/lib/PerfServer.h b/src/perf/lib/PerfServer.h index d7b17a50bc..af38d3e023 100644 --- a/src/perf/lib/PerfServer.h +++ b/src/perf/lib/PerfServer.h @@ -18,7 +18,7 @@ class PerfServer { public: PerfServer(const QUIC_CREDENTIAL_CONFIG* CredConfig) : - Engine(TcpAcceptCallback, TcpConnectCallback, TcpReceiveCallback, TcpSendCompleteCallback), + Engine(TcpAcceptCallback, TcpConnectCallback, TcpReceiveCallback, TcpSendCompleteCallback, TcpDefaultExecutionProfile), Server(&Engine, CredConfig, this) { CxPlatZeroMemory(&LocalAddr, sizeof(LocalAddr)); QuicAddrSetFamily(&LocalAddr, QUIC_ADDRESS_FAMILY_UNSPEC); diff --git a/src/perf/lib/SecNetPerf.h b/src/perf/lib/SecNetPerf.h index ee7cb28b52..cd2a232d5d 100644 --- a/src/perf/lib/SecNetPerf.h +++ b/src/perf/lib/SecNetPerf.h @@ -42,7 +42,13 @@ #define PERF_MAX_THREAD_COUNT 128 #define PERF_MAX_REQUESTS_PER_SECOND 2000000 // best guess - must increase if we can do better +typedef enum TCP_EXECUTION_PROFILE { + TCP_EXECUTION_PROFILE_LOW_LATENCY, + TCP_EXECUTION_PROFILE_MAX_THROUGHPUT, +} TCP_EXECUTION_PROFILE; + extern QUIC_EXECUTION_PROFILE PerfDefaultExecutionProfile; +extern TCP_EXECUTION_PROFILE TcpDefaultExecutionProfile; extern QUIC_CONGESTION_CONTROL_ALGORITHM PerfDefaultCongestionControl; extern uint8_t PerfDefaultEcnEnabled; extern uint8_t PerfDefaultQeoAllowed; diff --git a/src/perf/lib/SecNetPerfMain.cpp b/src/perf/lib/SecNetPerfMain.cpp index 93b2054256..dd9813fe81 100644 --- a/src/perf/lib/SecNetPerfMain.cpp +++ b/src/perf/lib/SecNetPerfMain.cpp @@ -22,6 +22,7 @@ PerfClient* Client; uint32_t MaxRuntime = 0; QUIC_EXECUTION_PROFILE PerfDefaultExecutionProfile = QUIC_EXECUTION_PROFILE_LOW_LATENCY; +TCP_EXECUTION_PROFILE TcpDefaultExecutionProfile = TCP_EXECUTION_PROFILE_MAX_THROUGHPUT; QUIC_CONGESTION_CONTROL_ALGORITHM PerfDefaultCongestionControl = QUIC_CONGESTION_CONTROL_ALGORITHM_CUBIC; uint8_t PerfDefaultEcnEnabled = false; uint8_t PerfDefaultQeoAllowed = false; @@ -193,14 +194,18 @@ QuicMainStart( if (ExecStr != nullptr) { if (IsValue(ExecStr, "lowlat")) { PerfDefaultExecutionProfile = QUIC_EXECUTION_PROFILE_LOW_LATENCY; + TcpDefaultExecutionProfile = TCP_EXECUTION_PROFILE_LOW_LATENCY; } else if (IsValue(ExecStr, "maxtput")) { PerfDefaultExecutionProfile = QUIC_EXECUTION_PROFILE_TYPE_MAX_THROUGHPUT; + TcpDefaultExecutionProfile = TCP_EXECUTION_PROFILE_MAX_THROUGHPUT; } else if (IsValue(ExecStr, "scavenger")) { PerfDefaultExecutionProfile = QUIC_EXECUTION_PROFILE_TYPE_SCAVENGER; + // TODO: Should we add a scavenger profile for TCP? } else if (IsValue(ExecStr, "realtime")) { PerfDefaultExecutionProfile = QUIC_EXECUTION_PROFILE_TYPE_REAL_TIME; + // TODO: Should we add a realtime profile for TCP? } else { - WriteOutput("Failed to parse execution profile[%s], use lowlat as default\n", ExecStr); + WriteOutput("Failed to parse execution profile[%s], use lowlat as default for QUIC, maxtput as default for TCP.\n", ExecStr); } } diff --git a/src/perf/lib/Tcp.cpp b/src/perf/lib/Tcp.cpp index b92aa76d62..87cb69ab4a 100644 --- a/src/perf/lib/Tcp.cpp +++ b/src/perf/lib/Tcp.cpp @@ -105,10 +105,12 @@ TcpEngine::TcpEngine( TcpAcceptHandler AcceptHandler, TcpConnectHandler ConnectHandler, TcpReceiveHandler ReceiveHandler, - TcpSendCompleteHandler SendCompleteHandler) noexcept : + TcpSendCompleteHandler SendCompleteHandler, + TCP_EXECUTION_PROFILE TcpExecutionProfile) noexcept : ProcCount((uint16_t)CxPlatProcCount()), Workers(new(std::nothrow) TcpWorker[ProcCount]), AcceptHandler(AcceptHandler), ConnectHandler(ConnectHandler), - ReceiveHandler(ReceiveHandler), SendCompleteHandler(SendCompleteHandler) + ReceiveHandler(ReceiveHandler), SendCompleteHandler(SendCompleteHandler), + TcpExecutionProfile(TcpExecutionProfile) { CxPlatListInitializeHead(&Connections); for (uint16_t i = 0; i < ProcCount; ++i) { @@ -117,6 +119,12 @@ TcpEngine::TcpEngine( } } Initialized = true; + if (TcpExecutionProfile == TCP_EXECUTION_PROFILE_LOW_LATENCY) { + WriteOutput("Initialized TCP Engine with Low Latency mode!\n"); + } + if (TcpExecutionProfile == TCP_EXECUTION_PROFILE_MAX_THROUGHPUT) { + WriteOutput("Initialized TCP Engine with Max Throughput mode!\n"); + } } TcpEngine::~TcpEngine() noexcept diff --git a/src/perf/lib/Tcp.h b/src/perf/lib/Tcp.h index 431aa65393..dd6876590d 100644 --- a/src/perf/lib/Tcp.h +++ b/src/perf/lib/Tcp.h @@ -95,12 +95,14 @@ class TcpEngine { const TcpConnectHandler ConnectHandler; const TcpReceiveHandler ReceiveHandler; const TcpSendCompleteHandler SendCompleteHandler; + const TCP_EXECUTION_PROFILE TcpExecutionProfile; public: TcpEngine( TcpAcceptHandler AcceptHandler, TcpConnectHandler ConnectHandler, TcpReceiveHandler ReceiveHandler, - TcpSendCompleteHandler SendCompleteHandler) noexcept; + TcpSendCompleteHandler SendCompleteHandler, + TCP_EXECUTION_PROFILE TcpExecutionProfile) noexcept; ~TcpEngine() noexcept; bool IsInitialized() const { return Initialized; } bool AddConnection(TcpConnection* Connection, uint16_t PartitionIndex); diff --git a/src/perf/test.cpp b/src/perf/test.cpp new file mode 100644 index 0000000000..228267574b --- /dev/null +++ b/src/perf/test.cpp @@ -0,0 +1,18 @@ +#include +using namespace std; + + + +int main() { + + int portfolio = 0; + int years = 50; + + for (int i = 0; i < years; i++) { + portfolio += 30000; + portfolio *= 1.05; + } + + cout << "Portfolio value after " << years << " years: " << portfolio << endl; + return 0; +} diff --git a/src/perf/test.exe b/src/perf/test.exe new file mode 100644 index 0000000000000000000000000000000000000000..82350006591e7bd9fe45c2565adc210469aac09c GIT binary patch literal 57079 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