|
|
/*++
Copyright (C) Microsoft Corporation, 1996 - 1999
Module Name:
iosvr.c
Abstract:
I/O completion port perf test.
Author:
Mario Goertzel [MarioGo]
Revision History:
MarioGo 3/3/1996 Based on win32 sdk winnt\sockets sample.
--*/
#include "ioperf.h"
// PERF CHECK: Must be less for 4K for optimum perf on small tests ??
enum PROTOCOL { TCP = 0, SPX, NMPIPE, UDP } Protocol = TCP;
CHAR *ProtocolNames[] = { "TCP/IP", "SPX", "Named Pipes", "UDP/IP" };
DWORD ProtocolFrameSize[] = { 1460, 1400 /* ? */, ((64 * 1024) - 1)/4, 1472 };
DWORD MaxWriteSize;
BOOL fUseSend = FALSE;
const char *USAGE = "-n <# clients> -n <request size> -n <reply size> -n <test case> -n <# threads> -n <concurrency factor> -t <protocol>\n" "\t-n <# clients> - for connection protocols. On datagram this\n" "\t controls the number of outstanding recv's\n" "\t-n <request size> - bytes, default 24\n" "\t-n <reply size> - bytes, default 24\n" "\t-n <test case>\n" "\t 1 - Uses async writes (64*frame size)\n" "\t 2 - Uses async writes (4096) - default \n" "\t 3 - (winsock only) Uses send() for reply\n" "\t-n <# threads> - worker threads, default # of processors * 2\n" "\t-n <concurrency factor> - default # of processors\n" "\t-t - tcp, spx, nmpipe (protseqs ok)\n" ;
typedef long STATUS;
typedef struct _PER_CLIENT_DATA { HANDLE hClient; struct _PER_CLIENT_DATA *pMe; OVERLAPPED OverlappedRead; struct _PER_CLIENT_DATA *pMe2; OVERLAPPED OverlappedWrite; PMESSAGE pRequest; PMESSAGE pReply; DWORD dwPreviousRead; DWORD dwPreviousWrite; DWORD dwTotalToWrite; DWORD dwRequestsProcessed; SOCKADDR_IN DgSendAddr; SOCKADDR_IN DgRecvAddr; DWORD dwRecvAddrSize; } PER_CLIENT_DATA, *PPER_CLIENT_DATA;
PPER_CLIENT_DATA *ClientData;
typedef struct _PER_THREAD_DATA { DWORD TotalTransactions; DWORD TotalRequestBytes; DWORD TotalReplyBytes; } PER_THREAD_DATA, *PPER_THREAD_DATA;
PPER_THREAD_DATA *ThreadData;
DWORD dwNumberOfClients; DWORD dwNumberOfWorkers; DWORD dwConcurrency; DWORD dwWorkIndex; DWORD dwRequestSize; DWORD dwReplySize; SYSTEM_INFO SystemInfo; HANDLE CompletionPort; DWORD dwActiveClientCount; HANDLE hBenchmarkStart; BOOL fClientsGoHome = FALSE;
BOOL WINAPI CreateNetConnections( VOID );
BOOL WINAPI CreateWorkers( VOID );
DWORD WINAPI WorkerThread( LPVOID WorkContext );
VOID WINAPI CompleteBenchmark( VOID );
int __cdecl main ( int argc, char *argv[], char *envp[] ) { ParseArgv(argc, argv);
//
// try to get timing more accurate... Avoid context
// switch that could occur when threads are released
//
SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_TIME_CRITICAL); //
// Figure out how many processors we have to size the minimum
// number of worker threads and concurrency
//
GetSystemInfo (&SystemInfo);
dwNumberOfClients = 1; dwNumberOfWorkers = 2 * SystemInfo.dwNumberOfProcessors; dwConcurrency = SystemInfo.dwNumberOfProcessors; dwRequestSize = 24; dwReplySize = 24;
if (Iterations == 1000) { Dump("Assuming 4 iterations for scalability test\n"); Iterations = 4; }
if (sizeof(MESSAGE) > 24) { ApiError("Configuration problem, message size > 24", 0); }
if (_stricmp(Protseq, "tcp") == 0 || _stricmp(Protseq, "ncacn_ip_tcp") == 0 ) { Protocol = TCP; } else if ( _stricmp(Protseq, "spx") == 0 || _stricmp(Protseq, "ncacn_spx") == 0 ) { Protocol = SPX; } else if ( _stricmp(Protseq, "nmpipe") == 0 || _stricmp(Protseq, "ncacn_np") == 0 ) { Protocol = NMPIPE; } else if ( _stricmp(Protseq, "udp") == 0 || _stricmp(Protseq, "ncadg_ip_udp") == 0 ) { Protocol = UDP; } if (Options[0] > 0) dwNumberOfClients = Options[0];
if (Options[1] > 0) { dwRequestSize = Options[1]; }
if (Options[2] > 0) { dwReplySize = Options[2]; }
if (Options[3] > 0) { switch(Options[3]) { case 1: MaxWriteSize = 4 * ProtocolFrameSize[Protocol]; break; case 2: MaxWriteSize = 4096; break; case 3: fUseSend = TRUE; break; default: printf("Invalid test case: %d\n", Options[3]); return(0); break; } } else { MaxWriteSize = 4096; }
if (Options[4] > 0) { dwNumberOfWorkers = Options[4]; }
if (Options[5] > 0) { dwConcurrency = Options[5]; }
printf("%2d Clients %2d Workers Concurrency %d, listening on %s\n", dwNumberOfClients, dwNumberOfWorkers, dwConcurrency, ProtocolNames[Protocol] );
ClientData = (PPER_CLIENT_DATA *)Allocate(sizeof(PPER_CLIENT_DATA) * dwNumberOfClients); ThreadData = (PPER_THREAD_DATA *)Allocate(sizeof(PPER_THREAD_DATA) * dwNumberOfWorkers);
if (!ThreadData || !ClientData) { ApiError("malloc", GetLastError()); }
if (!CreateNetConnections()) { return 1; }
if (!CreateWorkers()) { return 1; }
CompleteBenchmark();
return 0; }
VOID SubmitWrite(PER_CLIENT_DATA *pClient, DWORD size) { DWORD t1, t2; INT write_type = Protocol; BOOL b; INT err; DWORD status; WSABUF buf;
pClient->dwTotalToWrite = size; pClient->dwPreviousWrite = size;
if ( ( write_type == TCP || write_type == SPX ) && !fUseSend ) { // We want to use WriteFile for TCP & SPX if not using send.
write_type = NMPIPE; }
switch(write_type) { case NMPIPE:
if (size > MaxWriteSize) { size = MaxWriteSize; }
pClient->dwPreviousWrite = size;
b = WriteFile(pClient->hClient, pClient->pReply, size, &t1, &pClient->OverlappedWrite);
if (!b && GetLastError() != ERROR_IO_PENDING) { ApiError("WriteFile", GetLastError()); } break;
case UDP:
memcpy(&pClient->DgSendAddr, &pClient->DgRecvAddr, sizeof(SOCKADDR_IN)); buf.buf = (PCHAR) pClient->pReply; buf.len = size; t1 = 0; err = WSASendTo((SOCKET)pClient->hClient, &buf, 1, &t1, 0, (PSOCKADDR)&pClient->DgSendAddr, sizeof(SOCKADDR_IN), &pClient->OverlappedWrite, 0);
if (err != 0 && GetLastError() != ERROR_IO_PENDING) { ApiError("WSASendTo", GetLastError()); } break;
case TCP: case SPX: err = send((SOCKET)pClient->hClient, (PCHAR)pClient->pReply, sizeof(MESSAGE), 0);
if (err == SOCKET_ERROR) { ApiError("send", GetLastError()); }
break; default: ApiError("Bad protocol", 0); }
return; }
VOID SubmitRead(PER_CLIENT_DATA *pClient) { DWORD t1, t2, t3; BOOL b; DWORD status; INT err;
if (Protocol == UDP) { WSABUF buf; t1 = t2 = 0; pClient->dwRecvAddrSize = sizeof(pClient->DgRecvAddr); buf.buf = (PCHAR)pClient->pRequest; buf.len = dwRequestSize; status = WSARecvFrom((SOCKET)pClient->hClient, &buf, 1, &t1, &t2, (PSOCKADDR)&pClient->DgRecvAddr, &pClient->dwRecvAddrSize, &pClient->OverlappedRead, 0); if (status != NO_ERROR && GetLastError() != ERROR_IO_PENDING) { ApiError("WSARecvFrom", GetLastError()); } } else { b = ReadFile(pClient->hClient, pClient->pRequest, dwRequestSize, &t1, &pClient->OverlappedRead ); if (!b && GetLastError () != ERROR_IO_PENDING) { ApiError("ReadFile", GetLastError()); } } return; }
VOID WINAPI CompleteBenchmark ( VOID ) { DWORD StartCalls; DWORD TotalTicks, FinalCalls, MaxCalls, MinCalls, TotalCalls; DWORD i, j;
PPER_CLIENT_DATA pClient;
SetEvent(hBenchmarkStart); Sleep(1000);
for (i = 0; i < Iterations; i++) {
StartTime();
StartCalls = 0;
for (j = 0; j < dwNumberOfClients; j++ ) { pClient = ClientData[j]; StartCalls += pClient->dwRequestsProcessed; }
Sleep(Interval * 1000);
FinalCalls = MaxCalls = 0; MinCalls = ~0; for (j = 0; j < dwNumberOfClients; j++) { pClient = ClientData[j]; FinalCalls += pClient->dwRequestsProcessed; if (pClient->dwRequestsProcessed < MinCalls ) { MinCalls = pClient->dwRequestsProcessed; } if (pClient->dwRequestsProcessed > MaxCalls) { MaxCalls = pClient->dwRequestsProcessed; } }
TotalCalls = FinalCalls - StartCalls;
TotalTicks = FinishTiming();
Dump("Ticks: %4d, Total: %4d, Average %4d, TPS %3d\n", TotalTicks, TotalCalls, TotalCalls / dwNumberOfClients, TotalCalls * 1000 / TotalTicks );
Verbose("Max: %d, Min: %d\n", MaxCalls, MinCalls); }
// Clients will be shutdown on next call...
fClientsGoHome = TRUE;
Sleep(5000); printf("Test Complete\n");
for (i = 0; i < dwNumberOfWorkers; i++) { printf("\tThread[%2d] %d request and %d reply bytes in %d IOs\n", i, ThreadData[i]->TotalRequestBytes, ThreadData[i]->TotalReplyBytes, ThreadData[i]->TotalTransactions ); } }
BOOL WINAPI CreateNetConnections( void ) { STATUS status; DWORD i; SOCKET listener; INT err; WSADATA WsaData; DWORD nbytes; BOOL b; PPER_CLIENT_DATA pClient;
if (Protocol == TCP || Protocol == SPX) { status = WSAStartup (0x2, &WsaData); CHECK_STATUS(status, "WSAStartup");
//
// Open a socket to listen for incoming connections.
//
if (Protocol == TCP) { SOCKADDR_IN localAddr;
listener = WSASocketW(AF_INET, SOCK_STREAM, 0, 0, 0, WSA_FLAG_OVERLAPPED); if (listener == INVALID_SOCKET) { ApiError("socket", GetLastError()); } //
// Bind our server to the agreed upon port number.
//
ZeroMemory (&localAddr, sizeof (localAddr)); localAddr.sin_port = htons (TCP_PORT); localAddr.sin_family = AF_INET; err = bind (listener, (PSOCKADDR) & localAddr, sizeof (localAddr)); if (err == SOCKET_ERROR) { ApiError("bind", GetLastError()); } } else if (Protocol == SPX) { SOCKADDR_IPX localAddr; listener = socket (AF_IPX, SOCK_STREAM, NSPROTO_SPX); if (listener == INVALID_SOCKET) { ApiError("socket", GetLastError()); } ZeroMemory (&localAddr, sizeof (localAddr)); localAddr.sa_socket = htons (SPX_PORT); localAddr.sa_family = AF_IPX; err = bind (listener, (PSOCKADDR) & localAddr, sizeof (localAddr)); if (err == SOCKET_ERROR) { ApiError("bind", GetLastError()); } } else if (Protocol == SPX) { ApiError("Case not implemented", 0); }
// Prepare to accept client connections. Allow up to 5 pending
// connections.
err = listen (listener, 5); if (err == SOCKET_ERROR) { ApiError("listen", GetLastError()); } //
// Only Handle a single Queue
//
for (i = 0; i < dwNumberOfClients; i++) { SOCKET s;
pClient = Allocate(sizeof(PER_CLIENT_DATA)); if (!pClient) { ApiError("Allocate", GetLastError()); } pClient->pRequest = Allocate(dwRequestSize); pClient->pReply = Allocate(dwReplySize); if ( !pClient->pRequest || !pClient->pReply) { ApiError("Allocate", GetLastError()); } // Accept incoming connect requests
s = accept (listener, NULL, NULL); if (s == INVALID_SOCKET) { // exiting anyway, no need to cleanup.
ApiError("accept", GetLastError()); } dbgprintf("Accepted client %d\n", i); // Note that dwConcurrency says how many concurrent cpu bound threads to
// allow thru this should be tunable based on the requests. CPU bound requests
// will really really honor this.
pClient->hClient = (HANDLE)s;
CompletionPort = CreateIoCompletionPort(pClient->hClient, CompletionPort, (ULONG_PTR)pClient, dwConcurrency); if (!CompletionPort) { ApiError("CreateIoCompletionPort", GetLastError()); } //
// Start off an asynchronous read on the socket.
//
pClient->dwPreviousRead = 0; pClient->dwRequestsProcessed = 0; ZeroMemory(&pClient->OverlappedRead, sizeof(OVERLAPPED)); ZeroMemory(&pClient->OverlappedWrite, sizeof(OVERLAPPED)); b = ReadFile(pClient->hClient, pClient->pRequest, dwRequestSize, &nbytes, &pClient->OverlappedRead ); if (!b && GetLastError() != ERROR_IO_PENDING) { ApiError("ReadFile", GetLastError()); } ClientData[i] = pClient; } dwActiveClientCount = dwNumberOfClients;
} else if (Protocol == NMPIPE) { HANDLE h; OVERLAPPED *lpo; DWORD nbytes, index; BOOL b;
for (i = 0; i < dwNumberOfClients; i++) { h = CreateNamedPipe(NM_PORT, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_MESSAGE | (PIPE_READMODE_MESSAGE, 0) // ************
| PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 4096, // ***************
4096, // ***************
INFINITE, 0); if (!h) { ApiError("CreateNamedPipe", GetLastError()); }
//
// Wait for clients to connect
//
pClient = Allocate(sizeof(PER_CLIENT_DATA)); if (!pClient) { ApiError("Allocate", GetLastError()); }
ZeroMemory(pClient, sizeof(PER_CLIENT_DATA)); pClient->pRequest = Allocate(dwRequestSize); pClient->pReply = Allocate(dwReplySize); if ( !pClient->pRequest || !pClient->pReply) { ApiError("Allocate", GetLastError()); } // Accept incoming connect requests
pClient->hClient = h;
b = ConnectNamedPipe(pClient->hClient, &pClient->OverlappedRead);
dbgprintf("ConnectNamedPipe: %d %d\n", b, GetLastError());
if (b == 0) { if (GetLastError() == ERROR_IO_PENDING) { b = GetOverlappedResult(pClient->hClient, &pClient->OverlappedRead, &nbytes, TRUE);
if (b == 0) { ApiError("GetOverlappedResult", GetLastError()); } dbgprintf("Client connected\n"); } else { ApiError("ConnectNamedPipe", GetLastError()); } }
// Add the clients pipe instance to the completion port.
CompletionPort = CreateIoCompletionPort(h, CompletionPort, (ULONG_PTR)pClient, dwConcurrency);
if (!CompletionPort) { ApiError("CreteIoCompletionPort", GetLastError()); } //
// Start off an asynchronous read on the socket.
//
pClient->dwPreviousRead = 0; pClient->dwRequestsProcessed = 0; ZeroMemory(&pClient->OverlappedRead, sizeof(OVERLAPPED)); ZeroMemory(&pClient->OverlappedWrite, sizeof(OVERLAPPED)); b = ReadFile(pClient->hClient, pClient->pRequest, dwRequestSize, &nbytes, &pClient->OverlappedRead ); if (!b && GetLastError() != ERROR_IO_PENDING) { ApiError("ReadFile", GetLastError()); } ClientData[i] = pClient; } dwActiveClientCount = dwNumberOfClients; } else if (Protocol == UDP) { SOCKADDR_IN localAddr;
status = WSAStartup (0x2, &WsaData); CHECK_STATUS(status, "WSAStartup");
listener = WSASocketW(AF_INET, SOCK_DGRAM, IPPROTO_UDP, 0, 0, WSA_FLAG_OVERLAPPED); if (listener == INVALID_SOCKET) { ApiError("socket", GetLastError()); } //
// Bind our server to the agreed upon port number.
//
ZeroMemory (&localAddr, sizeof (localAddr)); localAddr.sin_port = htons (UDP_PORT); localAddr.sin_family = AF_INET; err = bind (listener, (PSOCKADDR) & localAddr, sizeof (localAddr)); if (err == SOCKET_ERROR) { ApiError("bind", GetLastError()); }
CompletionPort = CreateIoCompletionPort((HANDLE) listener, CompletionPort, 0, dwConcurrency); if (!CompletionPort) { ApiError("CreateIoCompletionPort", GetLastError()); }
//
// Start off asynchronous reads on the socket.
//
for(i = 0; i < dwNumberOfClients; i++) { pClient = Allocate(sizeof(PER_CLIENT_DATA)); if (!pClient) { ApiError("Allocate", GetLastError()); } pClient->pRequest = Allocate(dwRequestSize); pClient->pReply = Allocate(dwReplySize); if ( !pClient->pRequest || !pClient->pReply) { ApiError("Allocate", GetLastError()); } ZeroMemory(&pClient->OverlappedRead, sizeof(OVERLAPPED)); ZeroMemory(&pClient->OverlappedWrite, sizeof(OVERLAPPED));
pClient->hClient = (HANDLE)listener; pClient->dwPreviousRead = 0; pClient->dwRequestsProcessed = 0; pClient->pMe = pClient; pClient->pMe2 = pClient;
Trace("Created: %p %p %p\n", pClient, &pClient->OverlappedRead, &pClient->OverlappedWrite);
ClientData[i] = pClient; SubmitRead(pClient); } dwActiveClientCount = dwNumberOfClients; } else { ApiError("Invalid protocol", 0); }
// Protocol independent part
hBenchmarkStart = CreateEvent (NULL, TRUE, FALSE, NULL);
if (!hBenchmarkStart) { ApiError("CreateEvent", GetLastError()); }
return TRUE; }
BOOL WINAPI CreateWorkers( void ) { DWORD ThreadId; HANDLE ThreadHandle; DWORD i; PPER_THREAD_DATA pThreadData;
for (i = 0; i < dwNumberOfWorkers; i++) { pThreadData = Allocate(sizeof(PER_THREAD_DATA));
if (!pThreadData) { ApiError("malloc", GetLastError()); }
ZeroMemory(pThreadData, sizeof(PER_THREAD_DATA));
ThreadHandle = CreateThread(NULL, 0, WorkerThread, (LPVOID)pThreadData, 0, &ThreadId ); if (!ThreadHandle) { ApiError("CreateThread", GetLastError()); }
CloseHandle(ThreadHandle);
ThreadData[i] = pThreadData; }
return TRUE; }
DWORD WINAPI WorkerThread ( LPVOID WorkContext ) { PPER_THREAD_DATA Me; INT err; DWORD ResponseLength; BOOL b; LPOVERLAPPED lpo; DWORD nbytes; ULONG_PTR WorkIndex; PPER_CLIENT_DATA pClient; LONG count;
WaitForSingleObject (hBenchmarkStart, INFINITE);
Me = (PPER_THREAD_DATA) WorkContext;
for (;;) { lpo = 0;
b = GetQueuedCompletionStatus(CompletionPort, &nbytes, &WorkIndex, &lpo, INFINITE );
// dbgprintf("GetQueuedCompletionStatus: %d %d %p\n", b, nbytes, lpo);
if (WorkIndex == 0) { // Must be a datagram read or write
WorkIndex = (ULONG_PTR)((unsigned char *)lpo - 4); WorkIndex = *(PDWORD)WorkIndex; }
Me->TotalTransactions++;
if (b || lpo) { if (b) { DWORD nbytes2;
pClient = (PPER_CLIENT_DATA)WorkIndex;
if (lpo == &pClient->OverlappedWrite) { dbgprintf("Write completed %d (%p)\n", nbytes, pClient); Me->TotalReplyBytes += nbytes;
nbytes = pClient->dwTotalToWrite - pClient->dwPreviousWrite;
if (nbytes) { if (nbytes > MaxWriteSize) { nbytes = MaxWriteSize; }
pClient->dwPreviousWrite += nbytes;
b = WriteFile(pClient->hClient, (PBYTE)pClient->pReply + pClient->dwPreviousWrite - nbytes, nbytes, &nbytes2, &pClient->OverlappedWrite);
dbgprintf("Write completed: %d %d (of %d) %d (of %d)\n", b, nbytes2, nbytes, pClient->dwPreviousWrite, dwReplySize); if (!b && GetLastError() != ERROR_IO_PENDING) { ApiError("WriteFile", GetLastError()); } } } else { Me->TotalRequestBytes += nbytes;
dbgprintf(" Read completed %d (%p)\n", nbytes, pClient);
if (nbytes == 0) { Trace("Connection closed (zero byte read)\n"); CloseHandle(pClient->hClient); continue; }
switch (pClient->pRequest->MessageType) { case CONNECT: // Send test parameters back to the client
pClient->pReply->MessageType = SETUP; pClient->pReply->u.Setup.RequestSize = dwRequestSize; pClient->pReply->u.Setup.ReplySize = dwReplySize;
SubmitWrite(pClient, sizeof(MESSAGE));
pClient->dwPreviousRead = 0;
SubmitRead(pClient); break;
case DATA_RQ: // Make sure we got all the data and no more.
pClient->dwRequestsProcessed++;
nbytes += pClient->dwPreviousRead;
if (nbytes > dwRequestSize) { ApiError("Too much data returned\n", 0); }
if (nbytes < dwRequestSize) { dbgprintf("Partial receive of %d (of %d)\n", nbytes, dwRequestSize); // Resubmit the IO for the rest of the request.
pClient->dwPreviousRead = nbytes;
b = ReadFile(pClient->hClient, ((PBYTE)pClient->pRequest) + nbytes, dwRequestSize - nbytes, &nbytes, &pClient->OverlappedRead);
if (!b && GetLastError () != ERROR_IO_PENDING) { ApiError("ReadFile for remainder", GetLastError()); } // Pickup this or another IO
break; }
if (nbytes != pClient->pRequest->u.Data.TotalSize) { printf("Invalid request size, got %d, expected %d\n", nbytes, pClient->pRequest->u.Data.TotalSize); ApiError("test sync", 0); }
pClient->dwPreviousRead = 0;
// Could sleep/do work here.
// Send a response and post another asynchronous read on the
// socket.
if (fClientsGoHome == FALSE) { pClient->pReply->MessageType = DATA_RP; } else { pClient->pReply->MessageType = FINISH; }
pClient->pReply->u.Data.TotalSize = dwReplySize; SubmitWrite(pClient, dwReplySize);
SubmitRead(pClient);
break;
default: ApiError("Invalid message type", pClient->pRequest->MessageType); } } // read or write
} else { Trace("Client closed connection\n", GetLastError()); } } else { ApiError("Wait failed", GetLastError()); } } // loop
// not reached
return 0; }
|