This commit is contained in:
Nick Banks 2020-08-28 12:43:34 -07:00 коммит произвёл GitHub
Родитель f8e42f0d31
Коммит 1f4bfd765b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
15 изменённых файлов: 473 добавлений и 640 удалений

Просмотреть файл

@ -7,7 +7,7 @@
"Arch": ["x64", "x86", "arm", "arm64"],
"Exe": "quicperf",
"Arguments": {
"All": "-TestName:Throughput -ServerMode:1 -port:4433 -peer_uni:1 -connections:10",
"All": "-ServerMode:1",
"Loopback": "-selfsign:1",
"Remote": "-thumbprint:$Thumbprint -machine_cert:1 -cert_store:My"
}
@ -18,7 +18,7 @@
"Arch": ["x64", "x86", "arm", "arm64"],
"Exe": "quicperf",
"Arguments": {
"All": "-TestName:Throughput -target:$RemoteAddress -port:4433 -bind:$LocalAddress:4434 -ip:4 -core:0 -uni:1 -length:2000000000",
"All": "-ServerMode:0 -TestName:Throughput -target:$RemoteAddress -bind:$LocalAddress:4434 -ip:4 -core:0 -uni:1 -length:2000000000",
"Loopback": "",
"Remote": ""
}
@ -32,7 +32,7 @@
},
"Remote": {
"On": "",
"Off": "-encrypt:0"
"Off": ""
},
"Default": "On"
},
@ -44,7 +44,7 @@
},
"Remote": {
"On": "",
"Off": "-sendbuf:0"
"Off": ""
},
"Default": "Off"
}
@ -63,7 +63,7 @@
"Arch": ["x64", "arm"],
"Exe": "quicperf",
"Arguments": {
"All": "-TestName:Throughput -ServerMode:1 -port:4433 -selfsign:1 -peer_uni:1 -connections:10",
"All": "-ServerMode:1 -selfsign:1",
"Loopback": "",
"Remote": ""
}
@ -74,7 +74,7 @@
"Arch": ["x64", "arm"],
"Exe": "quicperf",
"Arguments": {
"All": "-TestName:Throughput -target:$RemoteAddress -port:4433 -uni:1 -length:2000000000",
"All": "-ServerMode:0 -TestName:Throughput -target:$RemoteAddress -uni:1 -length:2000000000",
"Loopback": "",
"Remote": ""
}
@ -88,7 +88,7 @@
},
"Remote": {
"On": "",
"Off": "-encrypt:0"
"Off": ""
},
"Default": "On"
},
@ -100,7 +100,7 @@
},
"Remote": {
"On": "",
"Off": "-sendbuf:0"
"Off": ""
},
"Default": "Off"
}
@ -119,7 +119,7 @@
"Arch": ["x64", "x86", "arm", "arm64"],
"Exe": "quicperf",
"Arguments": {
"All": "-TestName:RPS -ServerMode:1 -iter:10",
"All": "-ServerMode:1",
"Loopback": "-selfsign:1",
"Remote": "-thumbprint:$Thumbprint -machine_cert:1 -cert_store:My"
}
@ -130,7 +130,7 @@
"Arch": ["x64", "x86", "arm", "arm64"],
"Exe": "quicperf",
"Arguments": {
"All": "-TestName:RPS -target:$RemoteAddress",
"All": "-ServerMode:0 -TestName:RPS -target:$RemoteAddress",
"Loopback": "",
"Remote": ""
}
@ -160,17 +160,17 @@
{
"Name": "ResponseSize",
"Local": {
"0": "",
"512": "",
"4096": "",
"16384": ""
},
"Remote": {
"0": "-response:0",
"512": "-response:512",
"4096": "-response:4096",
"16384": "-response:16384"
},
"Remote": {
"0": "",
"512": "",
"4096": "",
"16384": ""
},
"Default": "4096"
}
],

Просмотреть файл

@ -2,11 +2,10 @@
# Licensed under the MIT License.
set(SOURCES
PerfServer.cpp
quicmain.cpp
ThroughputServer.cpp
ThroughputClient.cpp
RpsServer.cpp
RpsClient.cpp
ThroughputClient.cpp
)
# Allow CLOG to preprocess all the source files.

Просмотреть файл

@ -12,28 +12,24 @@ Abstract:
#pragma once
#define THROUGHPUT_DEFAULT_PORT 4433
#define THROUGHPUT_ALPN "tput"
#define THROUGHPUT_DEFAULT_DISCONNECT_TIMEOUT (10 * 1000)
#define THROUGHPUT_DEFAULT_IDLE_TIMEOUT 1000
#define THROUGHPUT_SERVER_PEER_UNI 1
#define THROUGHPUT_CLIENT_UNI 1
#define THROUGHPUT_DEFAULT_IO_SIZE_BUFFERED 0x10000
#define THROUGHPUT_DEFAULT_IO_SIZE_NONBUFFERED 0x100000
#define THROGHTPUT_DEFAULT_SEND_COUNT_BUFFERED 1
#define THROUGHPUT_DEFAULT_SEND_COUNT_NONBUFFERED 8
#define PERF_ALPN "perf"
#define PERF_DEFAULT_PORT 4433
#define PERF_DEFAULT_DISCONNECT_TIMEOUT (10 * 1000)
#define PERF_DEFAULT_IDLE_TIMEOUT (30 * 1000)
#define PERF_DEFAULT_STREAM_COUNT 100
#define RPS_ALPN "rps"
#define RPS_MAX_CLIENT_PORT_COUNT 256
#define RPS_MAX_BIDI_STREAM_COUNT 100
#define RPS_DEFAULT_PORT 4433
#define RPS_DEFAULT_DISCONNECT_TIMEOUT (10 * 1000)
#define RPS_DEFAULT_IDLE_TIMEOUT 30000
#define RPS_DEFAULT_ITERATIONS 1
#define RPS_DEFAULT_RUN_TIME 10000
#define RPS_DEFAULT_CONNECTION_COUNT 1000
#define RPS_DEFAULT_PARALLEL_REQUEST_COUNT 2
#define RPS_DEFAULT_REQUEST_LENGTH 0
#define RPS_DEFAULT_RESPONSE_LENGTH 0
#define RPS_ALL_CONNECT_TIMEOUT 10000
#define RPS_IDLE_WAIT 2000
#define PERF_DEFAULT_IO_SIZE_BUFFERED 0x10000
#define PERF_DEFAULT_IO_SIZE_NONBUFFERED 0x100000
#define PERF_DEFAULT_SEND_COUNT_BUFFERED 1
#define PERF_DEFAULT_SEND_COUNT_NONBUFFERED 8
#define TPUT_DEFAULT_IDLE_TIMEOUT (1 * 1000)
#define RPS_MAX_CLIENT_PORT_COUNT 256
#define RPS_DEFAULT_RUN_TIME (10 * 1000)
#define RPS_DEFAULT_CONNECTION_COUNT 1000
#define RPS_DEFAULT_PARALLEL_REQUEST_COUNT 2
#define RPS_DEFAULT_REQUEST_LENGTH 0
#define RPS_DEFAULT_RESPONSE_LENGTH 0
#define RPS_ALL_CONNECT_TIMEOUT 10000
#define RPS_IDLE_WAIT 2000

281
src/perf/lib/PerfServer.cpp Normal file
Просмотреть файл

@ -0,0 +1,281 @@
/*++
Copyright (c) Microsoft Corporation.
Licensed under the MIT License.
Abstract:
QUIC Perf Server Implementation.
--*/
#include "PerfServer.h"
#ifdef QUIC_CLOG
#include "PerfServer.cpp.clog.h"
#endif
static
void
PrintHelp(
) {
WriteOutput(
"\n"
"Perf Server options:\n"
"\n"
" -port:<####> The UDP port of the server. (def:%u)\n"
" -selfsign:<0/1> Uses a self-signed server certificate.\n"
" -thumbprint:<cert_hash> The hash or thumbprint of the certificate to use.\n"
" -cert_store:<store name> The certificate store to search for the thumbprint in.\n"
" -machine_cert:<0/1> Use the machine, or current user's, certificate store. (def:0)\n"
"\n",
PERF_DEFAULT_PORT
);
}
PerfServer::PerfServer(
_In_ PerfSelfSignedConfiguration* SelfSignedConfig
) : SelfSignedConfig{SelfSignedConfig} {
if (Session.IsValid()) {
Session.SetAutoCleanup();
Session.SetPeerBidiStreamCount(PERF_DEFAULT_STREAM_COUNT);
Session.SetPeerUnidiStreamCount(PERF_DEFAULT_STREAM_COUNT);
Session.SetDisconnectTimeout(PERF_DEFAULT_DISCONNECT_TIMEOUT);
Session.SetIdleTimeout(PERF_DEFAULT_IDLE_TIMEOUT);
}
}
PerfServer::~PerfServer() {
if (DataBufferBuffered) {
QUIC_FREE(DataBufferBuffered);
}
if (DataBufferNonBuffered) {
QUIC_FREE(DataBufferNonBuffered);
}
}
QUIC_STATUS
PerfServer::Init(
_In_ int argc,
_In_reads_(argc) _Null_terminated_ char* argv[]
) {
if (argc > 0 && (IsArg(argv[0], "?") || IsArg(argv[0], "help"))) {
PrintHelp();
return QUIC_STATUS_INVALID_PARAMETER;
}
if (!Listener.IsValid()) {
return Listener.GetInitStatus();
}
TryGetValue(argc, argv, "port", &Port);
QUIC_STATUS Status = SecurityConfig.Initialize(argc, argv, Registration, SelfSignedConfig);
if (QUIC_FAILED(Status)) {
PrintHelp();
return Status;
}
DataBufferBuffered = (QUIC_BUFFER*)QUIC_ALLOC_NONPAGED(sizeof(QUIC_BUFFER) + PERF_DEFAULT_IO_SIZE_BUFFERED);
if (!DataBufferBuffered) {
return QUIC_STATUS_OUT_OF_MEMORY;
}
DataBufferBuffered->Length = PERF_DEFAULT_IO_SIZE_BUFFERED;
DataBufferBuffered->Buffer = (uint8_t*)(DataBufferBuffered + 1);
for (uint32_t i = 0; i < PERF_DEFAULT_IO_SIZE_BUFFERED; ++i) {
DataBufferBuffered->Buffer[i] = (uint8_t)i;
}
DataBufferNonBuffered = (QUIC_BUFFER*)QUIC_ALLOC_NONPAGED(sizeof(QUIC_BUFFER) + PERF_DEFAULT_IO_SIZE_NONBUFFERED);
if (!DataBufferNonBuffered) {
return QUIC_STATUS_OUT_OF_MEMORY;
}
DataBufferNonBuffered->Length = PERF_DEFAULT_IO_SIZE_BUFFERED;
DataBufferNonBuffered->Buffer = (uint8_t*)(DataBufferNonBuffered + 1);
for (uint32_t i = 0; i < PERF_DEFAULT_IO_SIZE_BUFFERED; ++i) {
DataBufferNonBuffered->Buffer[i] = (uint8_t)i;
}
return QUIC_STATUS_SUCCESS;
}
QUIC_STATUS
PerfServer::Start(
_In_ QUIC_EVENT* _StopEvent
) {
QUIC_ADDR Address;
QuicAddrSetFamily(&Address, AF_UNSPEC);
QuicAddrSetPort(&Address, Port);
StopEvent = _StopEvent;
return
Listener.Start(
&Address,
[](HQUIC Handle, void* Context, QUIC_LISTENER_EVENT* Event) -> QUIC_STATUS {
return ((PerfServer*)Context)->ListenerCallback(Handle, Event);
},
this);
}
QUIC_STATUS
PerfServer::Wait(
_In_ int Timeout
) {
if (Timeout > 0) {
QuicEventWaitWithTimeout(*StopEvent, Timeout);
} else {
QuicEventWaitForever(*StopEvent);
}
Session.Shutdown(QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0);
return QUIC_STATUS_SUCCESS;
}
QUIC_STATUS
PerfServer::ListenerCallback(
_In_ HQUIC /*ListenerHandle*/,
_Inout_ QUIC_LISTENER_EVENT* Event
) {
switch (Event->Type) {
case QUIC_LISTENER_EVENT_NEW_CONNECTION: {
BOOLEAN value = TRUE;
if (QUIC_FAILED(
MsQuic->SetParam(
Event->NEW_CONNECTION.Connection,
QUIC_PARAM_LEVEL_CONNECTION,
QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION,
sizeof(value),
&value))) {
WriteOutput("MsQuic->SetParam (CONN_DISABLE_1RTT_ENCRYPTION) failed!\n");
}
QUIC_CONNECTION_CALLBACK_HANDLER Handler =
[](HQUIC Conn, void* Context, QUIC_CONNECTION_EVENT* Event) -> QUIC_STATUS {
return ((PerfServer*)Context)->
ConnectionCallback(
Conn,
Event);
};
MsQuic->SetCallbackHandler(Event->NEW_CONNECTION.Connection, (void*)Handler, this);
Event->NEW_CONNECTION.SecurityConfig = SecurityConfig;
break;
}
}
return QUIC_STATUS_SUCCESS;
}
QUIC_STATUS
PerfServer::ConnectionCallback(
_In_ HQUIC ConnectionHandle,
_Inout_ QUIC_CONNECTION_EVENT* Event
) {
switch (Event->Type) {
case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE:
MsQuic->ConnectionClose(ConnectionHandle);
break;
case QUIC_CONNECTION_EVENT_PEER_STREAM_STARTED: {
auto Context = StreamContextAllocator.Alloc(this, Event->PEER_STREAM_STARTED.Flags & QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL);
if (!Context) {
return QUIC_STATUS_OUT_OF_MEMORY;
}
QUIC_STREAM_CALLBACK_HANDLER Handler =
[](HQUIC Stream, void* Context, QUIC_STREAM_EVENT* Event) -> QUIC_STATUS {
return ((PerfServer::StreamContext*)Context)->Server->
StreamCallback(
(PerfServer::StreamContext*)Context,
Stream,
Event);
};
MsQuic->SetCallbackHandler(Event->PEER_STREAM_STARTED.Stream, (void*)Handler, Context);
break;
}
default:
break;
}
return QUIC_STATUS_SUCCESS;
}
QUIC_STATUS
PerfServer::StreamCallback(
_In_ PerfServer::StreamContext* Context,
_In_ HQUIC StreamHandle,
_Inout_ QUIC_STREAM_EVENT* Event
) {
switch (Event->Type) {
case QUIC_STREAM_EVENT_RECEIVE:
if (!Context->ResponseSizeSet) {
uint8_t* Dest = (uint8_t*)&Context->ResponseSize;
uint64_t Offset = Event->RECEIVE.AbsoluteOffset;
for (uint32_t i = 0; Offset < sizeof(uint64_t) && i < Event->RECEIVE.BufferCount; ++i) {
uint32_t Length = min((uint32_t)(sizeof(uint64_t) - Offset), Event->RECEIVE.Buffers[i].Length);
memcpy(Dest + Offset, Event->RECEIVE.Buffers[i].Buffer, Length);
Offset += Length;
}
if (Offset == sizeof(uint64_t)) {
Context->ResponseSize = QuicByteSwapUint64(Context->ResponseSize);
Context->ResponseSizeSet = true;
}
}
break;
case QUIC_STREAM_EVENT_SEND_COMPLETE:
Context->OutstandingSends--;
if (!Event->SEND_COMPLETE.Canceled) {
SendResponse(Context, StreamHandle);
}
break;
case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN:
if (!Context->ResponseSizeSet) {
MsQuic->StreamShutdown(StreamHandle, QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0);
} else if (Context->ResponseSize != 0) {
if (Context->Unidirectional) {
// TODO - Not supported right now
MsQuic->StreamShutdown(StreamHandle, QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0);
} else {
SendResponse(Context, StreamHandle);
}
} else if (!Context->Unidirectional) {
MsQuic->StreamShutdown(StreamHandle, QUIC_STREAM_SHUTDOWN_FLAG_GRACEFUL, 0);
}
break;
case QUIC_STREAM_EVENT_PEER_SEND_ABORTED:
case QUIC_STREAM_EVENT_PEER_RECEIVE_ABORTED:
MsQuic->StreamShutdown(StreamHandle, QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0);
break;
case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE: {
MsQuic->StreamClose(StreamHandle);
StreamContextAllocator.Free(Context);
break;
default:
break;
}
}
return QUIC_STATUS_SUCCESS;
}
void
PerfServer::SendResponse(
_In_ PerfServer::StreamContext* Context,
_In_ HQUIC StreamHandle
)
{
while (Context->BytesSent < Context->ResponseSize &&
Context->OutstandingSends < Context->MaxOutstandingSends) {
uint64_t BytesLeftToSend = Context->ResponseSize - Context->BytesSent;
uint32_t IoSize = Context->IoSize;
QUIC_BUFFER* Buffer = DataBufferNonBuffered; // TODO - Support buffered
QUIC_SEND_FLAGS Flags = QUIC_SEND_FLAG_NONE;
if ((uint64_t)IoSize >= BytesLeftToSend) {
IoSize = (uint32_t)BytesLeftToSend;
Context->LastBuffer.Buffer = Buffer->Buffer;
Context->LastBuffer.Length = IoSize;
Buffer = &Context->LastBuffer;
Flags = QUIC_SEND_FLAG_FIN;
}
Context->BytesSent += IoSize;
Context->OutstandingSends++;
MsQuic->StreamSend(StreamHandle, Buffer, 1, Flags, nullptr);
}
}

96
src/perf/lib/PerfServer.h Normal file
Просмотреть файл

@ -0,0 +1,96 @@
/*++
Copyright (c) Microsoft Corporation.
Licensed under the MIT License.
Abstract:
QUIC Perf Server declaration. Defines the functions and
variables used in the PerfServer class.
--*/
#pragma once
#include "PerfHelpers.h"
#include "PerfBase.h"
#include "PerfCommon.h"
class PerfServer : public PerfBase {
public:
PerfServer(
_In_ PerfSelfSignedConfiguration* SelfSignedConfig
);
~PerfServer() override;
QUIC_STATUS
Init(
_In_ int argc,
_In_reads_(argc) _Null_terminated_ char* argv[]
) override;
QUIC_STATUS
Start(
_In_ QUIC_EVENT* StopEvent
) override;
QUIC_STATUS
Wait(
int Timeout
) override;
private:
struct StreamContext {
StreamContext(
PerfServer* Server, bool Unidirectional) :
Server{Server}, Unidirectional{Unidirectional} { }
PerfServer* Server;
bool Unidirectional;
bool BufferedIo{false};
bool ResponseSizeSet{false};
uint64_t ResponseSize{0};
uint64_t BytesSent{0};
uint32_t OutstandingSends{0};
uint32_t MaxOutstandingSends{PERF_DEFAULT_SEND_COUNT_NONBUFFERED};
uint32_t IoSize{PERF_DEFAULT_IO_SIZE_NONBUFFERED};
QUIC_BUFFER LastBuffer;
};
QUIC_STATUS
ListenerCallback(
_In_ HQUIC ListenerHandle,
_Inout_ QUIC_LISTENER_EVENT* Event
);
QUIC_STATUS
ConnectionCallback(
_In_ HQUIC ConnectionHandle,
_Inout_ QUIC_CONNECTION_EVENT* Event
);
QUIC_STATUS
StreamCallback(
_In_ StreamContext* Context,
_In_ HQUIC StreamHandle,
_Inout_ QUIC_STREAM_EVENT* Event
);
void
SendResponse(
_In_ StreamContext* Context,
_In_ HQUIC StreamHandle
);
MsQuicRegistration Registration;
MsQuicSession Session {Registration, PERF_ALPN};
MsQuicListener Listener {Session};
PerfSelfSignedConfiguration* SelfSignedConfig;
PerfSecurityConfig SecurityConfig;
uint16_t Port {PERF_DEFAULT_PORT};
QUIC_EVENT* StopEvent {nullptr};
QUIC_BUFFER* DataBufferBuffered {nullptr};
QUIC_BUFFER* DataBufferNonBuffered {nullptr};
QuicPoolAllocator<StreamContext> StreamContextAllocator;
};

Просмотреть файл

@ -23,17 +23,20 @@ PrintHelp(
"\n"
"RPS Client options:\n"
"\n"
" -target:<####> The target server to connect to.\n"
" -runtime:<####> The total runtime (in ms). (def:%u)\n"
" -port:<####> The UDP port of the server. (def:%u)\n"
" -conns:<####> The number of connections to use. (def:%u)\n"
" -parallel:<####> The number of parallel requests per connection. (def:%u)\n"
" -request:<####> The length of request payloads. (def:%u)\n"
" -response:<####> The length of request payloads. (def:%u)\n"
"\n",
RPS_DEFAULT_RUN_TIME,
THROUGHPUT_DEFAULT_PORT,
PERF_DEFAULT_PORT,
RPS_DEFAULT_CONNECTION_COUNT,
RPS_DEFAULT_PARALLEL_REQUEST_COUNT,
RPS_DEFAULT_REQUEST_LENGTH
RPS_DEFAULT_REQUEST_LENGTH,
RPS_DEFAULT_RESPONSE_LENGTH
);
}
@ -41,8 +44,8 @@ RpsClient::RpsClient() {
QuicEventInitialize(&AllConnected, TRUE, FALSE);
if (Session.IsValid()) {
Session.SetAutoCleanup();
Session.SetDisconnectTimeout(RPS_DEFAULT_DISCONNECT_TIMEOUT);
Session.SetIdleTimeout(RPS_DEFAULT_IDLE_TIMEOUT);
Session.SetDisconnectTimeout(PERF_DEFAULT_DISCONNECT_TIMEOUT);
Session.SetIdleTimeout(PERF_DEFAULT_IDLE_TIMEOUT);
}
}
@ -86,15 +89,17 @@ RpsClient::Init(
TryGetValue(argc, argv, "port", &Port);
TryGetValue(argc, argv, "conns", &ConnectionCount);
TryGetValue(argc, argv, "request", &RequestLength);
TryGetValue(argc, argv, "response", &ResponseLength);
RequestBuffer = (QUIC_BUFFER*)QUIC_ALLOC_NONPAGED(sizeof(QUIC_BUFFER) + RequestLength);
RequestBuffer = (QUIC_BUFFER*)QUIC_ALLOC_NONPAGED(sizeof(QUIC_BUFFER) + sizeof(uint64_t) + RequestLength);
if (!RequestBuffer) {
return QUIC_STATUS_OUT_OF_MEMORY;
}
RequestBuffer->Length = RequestLength;
RequestBuffer->Length = sizeof(uint64_t) + RequestLength;
RequestBuffer->Buffer = (uint8_t*)(RequestBuffer + 1);
*(uint64_t*)(RequestBuffer->Buffer) = QuicByteSwapUint64(ResponseLength);
for (uint32_t i = 0; i < RequestLength; ++i) {
RequestBuffer->Buffer[i] = (uint8_t)i;
RequestBuffer->Buffer[sizeof(uint64_t) + i] = (uint8_t)i;
}
return QUIC_STATUS_SUCCESS;

Просмотреть файл

@ -59,13 +59,14 @@ private:
);
MsQuicRegistration Registration;
MsQuicSession Session{Registration, RPS_ALPN};
uint16_t Port {RPS_DEFAULT_PORT};
MsQuicSession Session{Registration, PERF_ALPN};
uint16_t Port {PERF_DEFAULT_PORT};
UniquePtr<char[]> Target;
uint32_t RunTime {RPS_DEFAULT_RUN_TIME};
uint32_t ConnectionCount {RPS_DEFAULT_CONNECTION_COUNT};
uint32_t ParallelRequests {RPS_DEFAULT_PARALLEL_REQUEST_COUNT};
uint32_t RequestLength {RPS_DEFAULT_REQUEST_LENGTH};
uint32_t ResponseLength {RPS_DEFAULT_RESPONSE_LENGTH};
QUIC_BUFFER* RequestBuffer {nullptr};
QUIC_EVENT* CompletionEvent {nullptr};
QUIC_ADDR LocalAddresses[RPS_MAX_CLIENT_PORT_COUNT];

Просмотреть файл

@ -1,205 +0,0 @@
/*++
Copyright (c) Microsoft Corporation.
Licensed under the MIT License.
Abstract:
QUIC Perf RPS Server Implementation.
--*/
#include "RpsServer.h"
#ifdef QUIC_CLOG
#include "RpsServer.cpp.clog.h"
#endif
static
void
PrintHelp(
) {
WriteOutput(
"\n"
"RPS Server options:\n"
"\n"
" -iter:<####> The number of client iterations run. (def:%u)\n"
" -port:<####> The UDP port of the server. (def:%u)\n"
" -thumbprint:<cert_hash> The hash or thumbprint of the certificate to use.\n"
" -cert_store:<store name> The certificate store to search for the thumbprint in.\n"
" -machine_cert:<0/1> Use the machine, or current user's, certificate store. (def:0)\n"
" -response:<####> The length of response payloads. (def:%u)\n"
"\n",
RPS_DEFAULT_ITERATIONS,
RPS_DEFAULT_PORT,
RPS_DEFAULT_RESPONSE_LENGTH
);
}
RpsServer::RpsServer(
_In_ PerfSelfSignedConfiguration* SelfSignedConfig
) : SelfSignedConfig(SelfSignedConfig) {
if (Session.IsValid()) {
Session.SetAutoCleanup();
Session.SetPeerBidiStreamCount(RPS_MAX_BIDI_STREAM_COUNT);
Session.SetDisconnectTimeout(RPS_DEFAULT_DISCONNECT_TIMEOUT);
Session.SetIdleTimeout(RPS_DEFAULT_IDLE_TIMEOUT);
}
}
RpsServer::~RpsServer() {
if (ResponseBuffer) {
QUIC_FREE(ResponseBuffer);
}
}
QUIC_STATUS
RpsServer::Init(
_In_ int argc,
_In_reads_(argc) _Null_terminated_ char* argv[]
) {
if (argc > 0 && (IsArg(argv[0], "?") || IsArg(argv[0], "help"))) {
PrintHelp();
return QUIC_STATUS_INVALID_PARAMETER;
}
if (!Listener.IsValid()) {
return Listener.GetInitStatus();
}
TryGetValue(argc, argv, "iter", &Iterations);
TryGetValue(argc, argv, "port", &Port);
TryGetValue(argc, argv, "response", &ResponseLength);
ResponseBuffer = (QUIC_BUFFER*)QUIC_ALLOC_NONPAGED(sizeof(QUIC_BUFFER) + ResponseLength);
if (!ResponseBuffer) {
return QUIC_STATUS_OUT_OF_MEMORY;
}
ResponseBuffer->Length = ResponseLength;
ResponseBuffer->Buffer = (uint8_t*)(ResponseBuffer + 1);
for (uint32_t i = 0; i < ResponseLength; ++i) {
ResponseBuffer->Buffer[i] = (uint8_t)i;
}
return SecurityConfig.Initialize(argc, argv, Registration, SelfSignedConfig);
}
QUIC_STATUS
RpsServer::Start(
_In_ QUIC_EVENT* StopEvent
) {
QUIC_ADDR Address;
QuicAddrSetFamily(&Address, AF_UNSPEC);
QuicAddrSetPort(&Address, Port);
CompletionEvent = StopEvent;
return
Listener.Start(
&Address,
[](HQUIC Handle, void* Context, QUIC_LISTENER_EVENT* Event) -> QUIC_STATUS {
return ((RpsServer*)Context)->ListenerCallback(Handle, Event);
},
this);
}
QUIC_STATUS
RpsServer::Wait(
_In_ int Timeout
) {
if (Timeout > 0) {
QuicEventWaitWithTimeout(*CompletionEvent, Timeout);
} else {
QuicEventWaitForever(*CompletionEvent);
}
Session.Shutdown(QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0);
return QUIC_STATUS_SUCCESS;
}
QUIC_STATUS
RpsServer::ListenerCallback(
_In_ HQUIC /* ListenerHandle */,
_Inout_ QUIC_LISTENER_EVENT* Event
) {
switch (Event->Type) {
case QUIC_LISTENER_EVENT_NEW_CONNECTION: {
Event->NEW_CONNECTION.SecurityConfig = SecurityConfig;
QUIC_CONNECTION_CALLBACK_HANDLER Handler =
[](HQUIC Conn, void* Context, QUIC_CONNECTION_EVENT* Event) -> QUIC_STATUS {
return ((RpsServer*)Context)->
ConnectionCallback(
Conn,
Event);
};
BOOLEAN Opt = FALSE;
MsQuic->SetParam(
Event->NEW_CONNECTION.Connection,
QUIC_PARAM_LEVEL_CONNECTION,
QUIC_PARAM_CONN_SEND_BUFFERING,
sizeof(Opt),
&Opt);
MsQuic->SetCallbackHandler(
Event->NEW_CONNECTION.Connection,
(void*)Handler,
this);
InterlockedIncrement((volatile long*)&ActiveConnectionCount);
break;
}
default:
break;
}
return QUIC_STATUS_SUCCESS;
}
QUIC_STATUS
RpsServer::ConnectionCallback(
_In_ HQUIC ConnectionHandle,
_Inout_ QUIC_CONNECTION_EVENT* Event
) {
switch (Event->Type) {
case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE:
MsQuic->ConnectionClose(ConnectionHandle);
if (InterlockedDecrement((volatile long*)&ActiveConnectionCount) == 0) {
if (InterlockedDecrement((volatile long*)&Iterations) == 0) {
QuicEventSet(*CompletionEvent);
}
}
break;
case QUIC_CONNECTION_EVENT_PEER_STREAM_STARTED: {
QUIC_STREAM_CALLBACK_HANDLER Handler =
[](HQUIC Stream, void* Context, QUIC_STREAM_EVENT* Event) -> QUIC_STATUS {
return ((RpsServer*)Context)->
StreamCallback(
Stream,
Event);
};
MsQuic->SetCallbackHandler(Event->PEER_STREAM_STARTED.Stream, (void*)Handler, this);
break;
}
default:
break;
}
return QUIC_STATUS_SUCCESS;
}
QUIC_STATUS
RpsServer::StreamCallback(
_In_ HQUIC StreamHandle,
_Inout_ QUIC_STREAM_EVENT* Event
) {
switch (Event->Type) {
case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN:
MsQuic->StreamSend(StreamHandle, ResponseBuffer, 1, QUIC_SEND_FLAG_FIN, nullptr);
break;
case QUIC_STREAM_EVENT_PEER_SEND_ABORTED:
case QUIC_STREAM_EVENT_PEER_RECEIVE_ABORTED:
MsQuic->StreamShutdown(StreamHandle, QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0);
break;
case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE:
MsQuic->StreamClose(StreamHandle);
break;
default:
break;
}
return QUIC_STATUS_SUCCESS;
}

Просмотреть файл

@ -1,74 +0,0 @@
/*++
Copyright (c) Microsoft Corporation.
Licensed under the MIT License.
Abstract:
QUIC Perf RPS Server declaration. Defines the functions and variables used
in the RpsServer class.
--*/
#pragma once
#include "PerfHelpers.h"
#include "PerfBase.h"
#include "PerfCommon.h"
class RpsServer : public PerfBase {
public:
RpsServer(
_In_ PerfSelfSignedConfiguration* SelfSignedConfig
);
~RpsServer() override;
QUIC_STATUS
Init(
_In_ int argc,
_In_reads_(argc) _Null_terminated_ char* argv[]
) override;
QUIC_STATUS
Start(
_In_ QUIC_EVENT* StopEvent
) override;
QUIC_STATUS
Wait(
int Timeout
) override;
private:
QUIC_STATUS
ListenerCallback(
_In_ HQUIC ListenerHandle,
_Inout_ QUIC_LISTENER_EVENT* Event
);
QUIC_STATUS
ConnectionCallback(
_In_ HQUIC ConnectionHandle,
_Inout_ QUIC_CONNECTION_EVENT* Event
);
QUIC_STATUS
StreamCallback(
_In_ HQUIC StreamHandle,
_Inout_ QUIC_STREAM_EVENT* Event
);
MsQuicRegistration Registration;
MsQuicSession Session {Registration, RPS_ALPN};
MsQuicListener Listener {Session};
PerfSelfSignedConfiguration* SelfSignedConfig;
PerfSecurityConfig SecurityConfig;
uint32_t Iterations {RPS_DEFAULT_ITERATIONS};
uint16_t Port {RPS_DEFAULT_PORT};
uint32_t ResponseLength {RPS_DEFAULT_RESPONSE_LENGTH};
uint32_t ActiveConnectionCount {0};
QUIC_BUFFER* ResponseBuffer {nullptr};
QUIC_EVENT* CompletionEvent {nullptr};
};

Просмотреть файл

@ -23,6 +23,7 @@ PrintHelp(
"\n"
"Throughput Client options:\n"
"\n"
" -target:<####> The target server to connect to.\n"
#if _WIN32
" -comp:<####> The compartment ID to run in.\n"
" -core:<####> The CPU core to use for the main thread.\n"
@ -36,9 +37,9 @@ PrintHelp(
" -iosize:<####> The size of each send request queued. (buffered def:%u) (nonbuffered def:%u)\n"
" -iocount:<####> The number of outstanding send requests to queue per stream. (buffered def:%u) (nonbuffered def:%u)\n"
"\n",
THROUGHPUT_DEFAULT_PORT,
THROUGHPUT_DEFAULT_IO_SIZE_BUFFERED, THROUGHPUT_DEFAULT_IO_SIZE_NONBUFFERED,
THROGHTPUT_DEFAULT_SEND_COUNT_BUFFERED, THROUGHPUT_DEFAULT_SEND_COUNT_NONBUFFERED
PERF_DEFAULT_PORT,
PERF_DEFAULT_IO_SIZE_BUFFERED, PERF_DEFAULT_IO_SIZE_NONBUFFERED,
PERF_DEFAULT_SEND_COUNT_BUFFERED, PERF_DEFAULT_SEND_COUNT_NONBUFFERED
);
}
@ -46,6 +47,7 @@ ThroughputClient::ThroughputClient(
) {
QuicZeroMemory(&LocalIpAddr, sizeof(LocalIpAddr));
if (Session.IsValid()) {
Session.SetIdleTimeout(TPUT_DEFAULT_IDLE_TIMEOUT);
Session.SetAutoCleanup();
}
}
@ -115,10 +117,10 @@ ThroughputClient::Init(
TryGetValue(argc, argv, "sendbuf", &UseSendBuffer);
IoSize = UseSendBuffer ? THROUGHPUT_DEFAULT_IO_SIZE_BUFFERED : THROUGHPUT_DEFAULT_IO_SIZE_NONBUFFERED;
IoSize = UseSendBuffer ? PERF_DEFAULT_IO_SIZE_BUFFERED : PERF_DEFAULT_IO_SIZE_NONBUFFERED;
TryGetValue(argc, argv, "iosize", &IoSize);
IoCount = UseSendBuffer ? THROGHTPUT_DEFAULT_SEND_COUNT_BUFFERED : THROUGHPUT_DEFAULT_SEND_COUNT_NONBUFFERED;
IoCount = UseSendBuffer ? PERF_DEFAULT_SEND_COUNT_BUFFERED : PERF_DEFAULT_SEND_COUNT_NONBUFFERED;
TryGetValue(argc, argv, "iocount", &IoCount);
size_t Len = strlen(Target);
@ -274,6 +276,9 @@ ThroughputClient::Start(
while (StrmData->BytesSent < Length && SendRequestCount < IoCount) {
SendRequest* SendReq = SendRequestAllocator.Alloc(&BufferAllocator, IoSize, true);
SendReq->SetLength(Length - StrmData->BytesSent);
if (StrmData->BytesSent == 0) {
QuicZeroMemory(SendReq->QuicBuffer.Buffer, sizeof(uint64_t));
}
StrmData->BytesSent += SendReq->QuicBuffer.Length;
++SendRequestCount;
Status =
@ -300,7 +305,7 @@ ThroughputClient::Start(
WriteOutput("Failed ConnectionStart 0x%x\n", Status);
return Status;
}
WriteOutput("Started!\n");
Shutdown.ConnHandle = nullptr;
return Status;
}

Просмотреть файл

@ -82,13 +82,13 @@ private:
);
MsQuicRegistration Registration;
MsQuicSession Session{Registration, THROUGHPUT_ALPN};
MsQuicSession Session{Registration, PERF_ALPN};
QuicPoolAllocator<StreamData> StreamDataAllocator;
QuicPoolAllocator<ConnectionData> ConnectionDataAllocator;
QuicPoolAllocator<SendRequest> SendRequestAllocator;
QuicPoolBufferAllocator BufferAllocator;
UniquePtr<char[]> TargetData;
uint16_t Port {THROUGHPUT_DEFAULT_PORT};
uint16_t Port {PERF_DEFAULT_PORT};
QUIC_EVENT* StopEvent {nullptr};
uint64_t Length {0};
// FIXME: unused: bool ConstructionSuccess {false};

Просмотреть файл

@ -1,202 +0,0 @@
/*++
Copyright (c) Microsoft Corporation.
Licensed under the MIT License.
Abstract:
QUIC Perf Throughput Server Implementation.
--*/
#include "ThroughputServer.h"
#ifdef QUIC_CLOG
#include "ThroughputServer.cpp.clog.h"
#endif
static
void
PrintHelp(
) {
WriteOutput(
"\n"
"Throughput Server options:\n"
"\n"
" -thumbprint:<cert_hash> The hash or thumbprint of the certificate to use.\n"
" -cert_store:<store name> The certificate store to search for the thumbprint in.\n"
" -machine_cert:<0/1> Use the machine, or current user's, certificate store. (def:0)\n"
" -connections:<####> The number of connections to create. (def:0)\n"
" -port:<####> The UDP port of the server. (def:%u)\n"
"\n",
THROUGHPUT_DEFAULT_PORT
);
}
ThroughputServer::ThroughputServer(
_In_ PerfSelfSignedConfiguration* SelfSignedConfig
) : SelfSignedConfig{SelfSignedConfig} {
if (Session.IsValid()) {
Session.SetAutoCleanup();
Session.SetPeerUnidiStreamCount(THROUGHPUT_SERVER_PEER_UNI);
Session.SetDisconnectTimeout(THROUGHPUT_DEFAULT_DISCONNECT_TIMEOUT);
Session.SetIdleTimeout(THROUGHPUT_DEFAULT_IDLE_TIMEOUT);
}
}
QUIC_STATUS
ThroughputServer::Init(
_In_ int argc,
_In_reads_(argc) _Null_terminated_ char* argv[]
) {
if (argc > 0 && (IsArg(argv[0], "?") || IsArg(argv[0], "help"))) {
PrintHelp();
return QUIC_STATUS_INVALID_PARAMETER;
}
if (!Listener.IsValid()) {
return Listener.GetInitStatus();
}
TryGetValue(argc, argv, "port", &Port);
TryGetValue(argc, argv, "connections", &NumberOfConnections);
QUIC_STATUS Status = SecurityConfig.Initialize(argc, argv, Registration, SelfSignedConfig);
if (QUIC_FAILED(Status)) {
PrintHelp();
return Status;
}
return QUIC_STATUS_SUCCESS;
}
QUIC_STATUS
ThroughputServer::Start(
_In_ QUIC_EVENT* StopEvent
) {
QUIC_ADDR Address;
QuicAddrSetFamily(&Address, AF_UNSPEC);
QuicAddrSetPort(&Address, Port);
QUIC_STATUS Status =
Listener.Start(
&Address,
[](HQUIC Handle, void* Context, QUIC_LISTENER_EVENT* Event) -> QUIC_STATUS {
return ((ThroughputServer*)Context)->ListenerCallback(Handle, Event);
},
this);
if (QUIC_FAILED(Status)) {
return Status;
}
RefCount = CountHelper{StopEvent};
if (NumberOfConnections > 0) {
for (uint32_t i = 0; i < NumberOfConnections; i++) {
RefCount.AddItem();
}
} else {
//
// Add a single item so we can wait on the Count Helper
//
RefCount.AddItem();
}
return QUIC_STATUS_SUCCESS;
}
QUIC_STATUS
ThroughputServer::Wait(
_In_ int Timeout
) {
if (Timeout > 0) {
RefCount.Wait(Timeout);
} else {
RefCount.WaitForever();
}
return QUIC_STATUS_SUCCESS;
}
QUIC_STATUS
ThroughputServer::ListenerCallback(
_In_ HQUIC /*ListenerHandle*/,
_Inout_ QUIC_LISTENER_EVENT* Event
) {
switch (Event->Type) {
case QUIC_LISTENER_EVENT_NEW_CONNECTION: {
Event->NEW_CONNECTION.SecurityConfig = SecurityConfig;
QUIC_CONNECTION_CALLBACK_HANDLER Handler =
[](HQUIC Conn, void* Context, QUIC_CONNECTION_EVENT* Event) -> QUIC_STATUS {
return ((ThroughputServer*)Context)->
ConnectionCallback(
Conn,
Event);
};
MsQuic->SetCallbackHandler(
Event->NEW_CONNECTION.Connection,
(void*)Handler,
this);
BOOLEAN value = TRUE;
if (QUIC_FAILED(
MsQuic->SetParam(
Event->NEW_CONNECTION.Connection,
QUIC_PARAM_LEVEL_CONNECTION,
QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION,
sizeof(value),
&value))) {
WriteOutput("MsQuic->SetParam (CONN_DISABLE_1RTT_ENCRYPTION) failed!\n");
}
break;
}
}
return QUIC_STATUS_SUCCESS;
}
QUIC_STATUS
ThroughputServer::ConnectionCallback(
_In_ HQUIC ConnectionHandle,
_Inout_ QUIC_CONNECTION_EVENT* Event
) {
switch (Event->Type) {
case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE:
MsQuic->ConnectionClose(ConnectionHandle);
if (NumberOfConnections > 0) {
RefCount.CompleteItem();
}
break;
case QUIC_CONNECTION_EVENT_PEER_STREAM_STARTED: {
QUIC_STREAM_CALLBACK_HANDLER Handler =
[](HQUIC Stream, void* Context, QUIC_STREAM_EVENT* Event) -> QUIC_STATUS {
return ((ThroughputServer*)Context)->
StreamCallback(
Stream,
Event);
};
MsQuic->SetCallbackHandler(Event->PEER_STREAM_STARTED.Stream, (void*)Handler, this);
break;
}
default:
break;
}
return QUIC_STATUS_SUCCESS;
}
QUIC_STATUS
ThroughputServer::StreamCallback(
_In_ HQUIC StreamHandle,
_Inout_ QUIC_STREAM_EVENT* Event
) {
switch (Event->Type) {
case QUIC_STREAM_EVENT_PEER_SEND_ABORTED:
case QUIC_STREAM_EVENT_PEER_RECEIVE_ABORTED:
MsQuic->StreamShutdown(
StreamHandle,
QUIC_STREAM_SHUTDOWN_FLAG_ABORT_SEND | QUIC_STREAM_SHUTDOWN_FLAG_ABORT_RECEIVE,
0);
break;
case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE: {
MsQuic->StreamClose(StreamHandle);
break;
default:
break;
}
}
return QUIC_STATUS_SUCCESS;
}

Просмотреть файл

@ -1,69 +0,0 @@
/*++
Copyright (c) Microsoft Corporation.
Licensed under the MIT License.
Abstract:
QUIC Perf Throughput Server declaration. Defines the functions and
variables used in the ThroughputServer class.
--*/
#pragma once
#include "PerfHelpers.h"
#include "PerfBase.h"
#include "PerfCommon.h"
class ThroughputServer : public PerfBase {
public:
ThroughputServer(
_In_ PerfSelfSignedConfiguration* SelfSignedConfig
);
QUIC_STATUS
Init(
_In_ int argc,
_In_reads_(argc) _Null_terminated_ char* argv[]
) override;
QUIC_STATUS
Start(
_In_ QUIC_EVENT* StopEvent
) override;
QUIC_STATUS
Wait(
int Timeout
) override;
private:
QUIC_STATUS
ListenerCallback(
_In_ HQUIC ListenerHandle,
_Inout_ QUIC_LISTENER_EVENT* Event
);
QUIC_STATUS
ConnectionCallback(
_In_ HQUIC ConnectionHandle,
_Inout_ QUIC_CONNECTION_EVENT* Event
);
QUIC_STATUS
StreamCallback(
_In_ HQUIC StreamHandle,
_Inout_ QUIC_STREAM_EVENT* Event
);
MsQuicRegistration Registration;
MsQuicSession Session {Registration, THROUGHPUT_ALPN};
MsQuicListener Listener {Session};
PerfSelfSignedConfiguration* SelfSignedConfig;
PerfSecurityConfig SecurityConfig;
uint16_t Port {THROUGHPUT_DEFAULT_PORT};
uint32_t NumberOfConnections {0};
CountHelper RefCount;
};

Просмотреть файл

@ -35,11 +35,10 @@
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="PerfServer.cpp" />
<ClCompile Include="quicmain.cpp" />
<ClCompile Include="ThroughputClient.cpp" />
<ClCompile Include="ThroughputServer.cpp" />
<ClCompile Include="RpsClient.cpp" />
<ClCompile Include="RpsServer.cpp" />
<ClCompile Include="ThroughputClient.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="PerfHelpers.h" />

Просмотреть файл

@ -10,9 +10,8 @@ Abstract:
--*/
#include "PerfHelpers.h"
#include "ThroughputServer.h"
#include "PerfServer.h"
#include "ThroughputClient.h"
#include "RpsServer.h"
#include "RpsClient.h"
#ifdef QUIC_CLOG
@ -39,7 +38,7 @@ PrintHelp(
) {
WriteOutput(
"\n"
"Usage: quicperf -TestName:<Throughput|RPS> [-ServerMode:<1:0>] [options]\n"
"Usage: quicperf -ServerMode:<1:0> [-TestName:<Throughput|RPS>] [options]\n"
"\n"
);
}
@ -58,21 +57,16 @@ QuicMainStart(
return QUIC_STATUS_INVALID_PARAMETER;
}
if (!IsArg(argv[0], "TestName")) {
WriteOutput("Must specify -TestName argument\n");
ServerMode = 0;
if (!IsArg(argv[0], "ServerMode")) {
WriteOutput("Must specify -ServerMode argument\n");
PrintHelp();
return QUIC_STATUS_INVALID_PARAMETER;
}
const char* TestName = GetValue(argc, argv, "TestName");
TryGetValue(argc, argv, "ServerMode", &ServerMode);
argc--; argv++;
ServerMode = 0;
if (argc != 0 && IsArg(argv[0], "ServerMode")) {
TryGetValue(argc, argv, "ServerMode", &ServerMode);
argc--; argv++;
}
QUIC_STATUS Status;
if (ServerMode) {
@ -91,7 +85,6 @@ QuicMainStart(
}
}
MsQuic = new(std::nothrow) QuicApiTable;
if (MsQuic == nullptr) {
return QUIC_STATUS_OUT_OF_MEMORY;
@ -102,21 +95,29 @@ QuicMainStart(
return Status;
}
if (IsValue(TestName, "Throughput")) {
if (ServerMode) {
TestToRun = new(std::nothrow) ThroughputServer(SelfSignedConfig);
} else {
TestToRun = new(std::nothrow) ThroughputClient;
}
} else if (IsValue(TestName, "RPS")) {
if (ServerMode) {
TestToRun = new(std::nothrow) RpsServer(SelfSignedConfig);
} else {
TestToRun = new(std::nothrow) RpsClient;
}
if (ServerMode) {
TestToRun = new(std::nothrow) PerfServer(SelfSignedConfig);
} else {
delete MsQuic;
return QUIC_STATUS_INVALID_PARAMETER;
if (!IsArg(argv[0], "TestName")) {
WriteOutput("Must specify -TestName argument\n");
PrintHelp();
delete MsQuic;
MsQuic = nullptr;
return QUIC_STATUS_INVALID_PARAMETER;
}
const char* TestName = GetValue(argc, argv, "TestName");
argc--; argv++;
if (IsValue(TestName, "Throughput")) {
TestToRun = new(std::nothrow) ThroughputClient;
} else if (IsValue(TestName, "RPS")) {
TestToRun = new(std::nothrow) RpsClient;
} else {
delete MsQuic;
return QUIC_STATUS_INVALID_PARAMETER;
}
}
if (TestToRun != nullptr) {