2023-10-13 14:43:47 +00:00
|
|
|
#if 0
|
|
|
|
/*─────────────────────────────────────────────────────────────────╗
|
|
|
|
│ To the extent possible under law, Justine Tunney has waived │
|
|
|
|
│ all copyright and related or neighboring rights to this file, │
|
|
|
|
│ as it is written in the following disclaimers: │
|
|
|
|
│ • http://unlicense.org/ │
|
|
|
|
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
|
|
|
╚─────────────────────────────────────────────────────────────────*/
|
|
|
|
#endif
|
|
|
|
#include "libc/assert.h"
|
|
|
|
#include "libc/atomic.h"
|
|
|
|
#include "libc/calls/calls.h"
|
|
|
|
#include "libc/calls/struct/sigaction.h"
|
|
|
|
#include "libc/calls/struct/timespec.h"
|
2023-10-13 15:30:18 +00:00
|
|
|
#include "libc/dce.h"
|
2023-10-13 14:43:47 +00:00
|
|
|
#include "libc/errno.h"
|
|
|
|
#include "libc/fmt/conv.h"
|
|
|
|
#include "libc/intrin/kprintf.h"
|
|
|
|
#include "libc/macros.h"
|
|
|
|
#include "libc/mem/mem.h"
|
|
|
|
#include "libc/nt/accounting.h"
|
|
|
|
#include "libc/nt/enum/wsaid.h"
|
|
|
|
#include "libc/nt/errors.h"
|
|
|
|
#include "libc/nt/iocp.h"
|
|
|
|
#include "libc/nt/runtime.h"
|
|
|
|
#include "libc/nt/struct/overlapped.h"
|
|
|
|
#include "libc/nt/thunk/msabi.h"
|
|
|
|
#include "libc/nt/winsock.h"
|
|
|
|
#include "libc/runtime/runtime.h"
|
|
|
|
#include "libc/sock/sock.h"
|
|
|
|
#include "libc/sock/struct/sockaddr.h"
|
|
|
|
#include "libc/sock/wsaid.internal.h"
|
|
|
|
#include "libc/stdio/stdio.h"
|
|
|
|
#include "libc/str/str.h"
|
|
|
|
#include "libc/sysv/consts/af.h"
|
|
|
|
#include "libc/sysv/consts/auxv.h"
|
|
|
|
#include "libc/sysv/consts/ipproto.h"
|
|
|
|
#include "libc/sysv/consts/sig.h"
|
|
|
|
#include "libc/sysv/consts/sock.h"
|
|
|
|
#include "libc/thread/thread.h"
|
|
|
|
#include "libc/thread/thread2.h"
|
|
|
|
#include "third_party/getopt/getopt.internal.h"
|
|
|
|
|
|
|
|
__msabi extern typeof(__sys_bind_nt) *const __imp_bind;
|
|
|
|
__msabi extern typeof(__sys_closesocket_nt) *const __imp_closesocket;
|
|
|
|
|
|
|
|
enum State { SENDING, RECEIVING };
|
|
|
|
|
|
|
|
struct Client {
|
|
|
|
struct NtOverlapped overlap;
|
|
|
|
int64_t handle;
|
|
|
|
enum State state;
|
|
|
|
char buf[1500];
|
|
|
|
struct NtIovec iov;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int64_t iocp;
|
|
|
|
static char msg[128];
|
|
|
|
static uint32_t msglen;
|
|
|
|
static const char *prog;
|
|
|
|
static atomic_int a_termsig;
|
|
|
|
static atomic_long a_requests;
|
|
|
|
static atomic_bool a_finished;
|
|
|
|
|
|
|
|
static wontreturn void PrintUsage(int fd, int rc) {
|
|
|
|
tinyprint(fd, "Usage: ", prog, " -H HOST -P PORT\n", NULL);
|
|
|
|
exit(rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool32 (*__msabi ConnectEx)(int64_t hSocket, const void *name,
|
|
|
|
int namelen, const void *opt_lpSendBuffer,
|
|
|
|
uint32_t dwSendDataLength,
|
|
|
|
uint32_t *opt_out_lpdwBytesSent,
|
|
|
|
struct NtOverlapped *lpOverlapped);
|
|
|
|
|
|
|
|
static int64_t CreateNewCompletionPort(uint32_t dwNumberOfConcurrentThreads) {
|
|
|
|
return CreateIoCompletionPort(-1, 0, 0, dwNumberOfConcurrentThreads);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool AssociateDeviceWithCompletionPort(int64_t hCompletionPort,
|
|
|
|
int64_t hDevice,
|
|
|
|
uint64_t dwCompletionKey) {
|
|
|
|
int64_t h =
|
|
|
|
CreateIoCompletionPort(hDevice, hCompletionPort, dwCompletionKey, 0);
|
|
|
|
return h == hCompletionPort;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void NewClient(struct Client *client, const struct sockaddr_in *addr) {
|
|
|
|
|
|
|
|
client->handle = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0,
|
|
|
|
kNtWsaFlagOverlapped);
|
|
|
|
if (client->handle == -1) {
|
|
|
|
fprintf(stderr, "WSASocket() failed w/ %d\n", WSAGetLastError());
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
struct sockaddr_in bindaddr = {AF_INET};
|
|
|
|
if (__imp_bind(client->handle, &bindaddr, sizeof(bindaddr))) {
|
|
|
|
fprintf(stderr, "bind() failed w/ %d\n", WSAGetLastError());
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!AssociateDeviceWithCompletionPort(iocp, client->handle,
|
|
|
|
(intptr_t)client)) {
|
|
|
|
fprintf(stderr, "AssociateDeviceWithCompletionPort() failed w/ %d\n",
|
|
|
|
GetLastError());
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
if (!SetFileCompletionNotificationModes(client->handle,
|
|
|
|
kNtFileSkipSetEventOnHandle)) {
|
|
|
|
fprintf(stderr, "SetFileCompletionNotificationModes() failed w/ %d\n",
|
|
|
|
GetLastError());
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct NtOverlapped overlap = {0};
|
|
|
|
if (!ConnectEx(client->handle, addr, sizeof(*addr), msg, msglen, 0,
|
|
|
|
&overlap) &&
|
|
|
|
WSAGetLastError() != kNtErrorIoPending) {
|
|
|
|
fprintf(stderr, "ConnectEx() failed w/ %d\n", WSAGetLastError());
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-13 15:30:18 +00:00
|
|
|
static void *Worker(void *arg) {
|
2023-10-13 14:43:47 +00:00
|
|
|
while (!a_finished) {
|
2023-10-14 08:06:00 +00:00
|
|
|
uint32_t i;
|
2023-10-13 14:43:47 +00:00
|
|
|
uint32_t dwFlags;
|
2023-10-14 08:06:00 +00:00
|
|
|
uint32_t dwRecordCount;
|
|
|
|
struct NtOverlappedEntry records[8];
|
|
|
|
if (!GetQueuedCompletionStatusEx(iocp, records, ARRAYLEN(records),
|
|
|
|
&dwRecordCount, -1u, false)) {
|
2023-10-13 14:43:47 +00:00
|
|
|
fprintf(stderr, "GetQueuedCompletionStatus() failed w/ %d\n",
|
|
|
|
GetLastError());
|
|
|
|
exit(1);
|
|
|
|
}
|
2023-10-14 08:06:00 +00:00
|
|
|
for (i = 0; i < dwRecordCount; ++i) {
|
|
|
|
uint32_t dwBytes = records[i].dwNumberOfBytesTransferred;
|
|
|
|
struct Client *client = (struct Client *)records[i].lpCompletionKey;
|
|
|
|
switch (client->state) {
|
|
|
|
case SENDING:
|
2023-10-13 14:43:47 +00:00
|
|
|
dwFlags = 0;
|
|
|
|
client->state = RECEIVING;
|
|
|
|
client->iov.buf = client->buf;
|
|
|
|
client->iov.len = sizeof(client->buf) - 1;
|
|
|
|
bzero(&client->overlap, sizeof(client->overlap));
|
|
|
|
if (WSARecv(client->handle, &client->iov, 1, 0, &dwFlags,
|
|
|
|
&client->overlap, 0) &&
|
|
|
|
WSAGetLastError() != kNtErrorIoPending) {
|
|
|
|
fprintf(stderr, "WSARecv() failed w/ %d\n", WSAGetLastError());
|
|
|
|
exit(1);
|
|
|
|
}
|
2023-10-14 08:06:00 +00:00
|
|
|
break;
|
|
|
|
case RECEIVING:
|
2023-10-13 14:43:47 +00:00
|
|
|
if (!dwBytes) {
|
|
|
|
fprintf(stderr, "got disconnect\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
client->buf[dwBytes] = 0;
|
|
|
|
if (!startswith(client->buf, "HTTP/1.1 200")) {
|
|
|
|
fprintf(stderr, "request failed: %d bytes %`'s\n", dwBytes,
|
|
|
|
client->buf);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
++a_requests;
|
|
|
|
client->state = SENDING;
|
|
|
|
client->iov.buf = msg;
|
|
|
|
client->iov.len = msglen;
|
|
|
|
bzero(&client->overlap, sizeof(client->overlap));
|
|
|
|
if (WSASend(client->handle, &client->iov, 1, 0, 0, &client->overlap,
|
|
|
|
0) &&
|
|
|
|
WSAGetLastError() != kNtErrorIoPending) {
|
|
|
|
fprintf(stderr, "WSASend() failed w/ %d\n", WSAGetLastError());
|
|
|
|
exit(1);
|
|
|
|
}
|
2023-10-14 08:06:00 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
__builtin_unreachable();
|
|
|
|
}
|
2023-10-13 14:43:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-10-13 15:30:18 +00:00
|
|
|
static void OnTerm(int sig) {
|
2023-10-13 14:43:47 +00:00
|
|
|
a_termsig = sig;
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char *argv[]) {
|
|
|
|
|
2023-10-13 15:30:18 +00:00
|
|
|
if (!IsWindows()) {
|
|
|
|
tinyprint(2, "error: this program is intended for windows\n", NULL);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2023-10-13 14:43:47 +00:00
|
|
|
prog = argv[0];
|
|
|
|
if (!prog) {
|
2023-10-14 08:06:00 +00:00
|
|
|
prog = "winbench";
|
2023-10-13 14:43:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int opt;
|
2023-10-14 08:06:00 +00:00
|
|
|
int nclients = 1000;
|
|
|
|
int nthreads = GetMaximumProcessorCount(0xffff);
|
2023-10-13 14:43:47 +00:00
|
|
|
struct sockaddr_in destaddr = {AF_INET, htons(8080), {htonl(0x7f000001)}};
|
|
|
|
while ((opt = getopt(argc, argv, "hH:P:")) != -1) {
|
|
|
|
switch (opt) {
|
|
|
|
case 'H':
|
|
|
|
destaddr.sin_addr.s_addr = inet_addr(optarg);
|
|
|
|
break;
|
|
|
|
case 'P':
|
|
|
|
destaddr.sin_port = htons(atoi(optarg));
|
|
|
|
break;
|
|
|
|
case 'h':
|
|
|
|
PrintUsage(1, 0);
|
|
|
|
default:
|
|
|
|
PrintUsage(2, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t ip = ntohl(destaddr.sin_addr.s_addr);
|
|
|
|
uint16_t port = ntohs(destaddr.sin_port);
|
|
|
|
msglen = snprintf(msg, sizeof(msg),
|
|
|
|
"GET / HTTP/1.1\r\n"
|
|
|
|
"Host: %hhu.%hhu.%hhu.%hhu:%hu\r\n"
|
|
|
|
"\r\n",
|
|
|
|
ip >> 24, ip >> 16, ip >> 8, ip, port);
|
|
|
|
|
|
|
|
struct NtWsaData kNtWsaData = {0};
|
|
|
|
WSAStartup(0x0202, &kNtWsaData);
|
|
|
|
|
|
|
|
static struct NtGuid ConnectExGuid = WSAID_CONNECTEX;
|
|
|
|
ConnectEx = __get_wsaid(&ConnectExGuid);
|
|
|
|
|
|
|
|
iocp = CreateNewCompletionPort(0);
|
|
|
|
if (!iocp) {
|
|
|
|
fprintf(stderr, "CreateNewCompletionPort() failed w/ %d\n", GetLastError());
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct sigaction sa = {.sa_handler = OnTerm};
|
|
|
|
unassert(!sigaction(SIGINT, &sa, 0));
|
|
|
|
unassert(!sigaction(SIGHUP, &sa, 0));
|
|
|
|
unassert(!sigaction(SIGTERM, &sa, 0));
|
|
|
|
|
|
|
|
sigset_t block;
|
|
|
|
sigfillset(&block);
|
|
|
|
pthread_attr_t attr;
|
2024-07-04 17:52:16 +00:00
|
|
|
int pagesz = getpagesize();
|
2023-10-13 14:43:47 +00:00
|
|
|
pthread_t *threads = calloc(nthreads, sizeof(pthread_t));
|
|
|
|
unassert(!pthread_attr_init(&attr));
|
|
|
|
unassert(!pthread_attr_setstacksize(&attr, 65536));
|
|
|
|
unassert(!pthread_attr_setguardsize(&attr, pagesz));
|
|
|
|
unassert(!pthread_attr_setsigmask_np(&attr, &block));
|
|
|
|
for (int i = 0; i < nthreads; ++i) {
|
|
|
|
if ((errno = pthread_create(threads + i, &attr, Worker, 0))) {
|
|
|
|
perror("pthread_create");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
unassert(!pthread_attr_destroy(&attr));
|
|
|
|
|
|
|
|
struct timespec start_time = timespec_real();
|
|
|
|
|
|
|
|
struct Client *clients = calloc(nclients, sizeof(struct Client));
|
|
|
|
for (int i = 0; i < nclients; ++i) {
|
|
|
|
NewClient(clients + i, &destaddr);
|
|
|
|
}
|
|
|
|
|
2023-10-14 08:06:00 +00:00
|
|
|
sleep(10);
|
2023-10-13 14:43:47 +00:00
|
|
|
|
|
|
|
a_finished = true;
|
|
|
|
long request_count = a_requests;
|
|
|
|
struct timespec end_time = timespec_real();
|
|
|
|
|
|
|
|
for (int i = 0; i < nthreads; ++i) {
|
|
|
|
if ((errno = pthread_join(threads[i], 0))) {
|
|
|
|
perror("pthread_join");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
double elapsed_seconds =
|
|
|
|
timespec_tonanos(timespec_sub(end_time, start_time)) * 1e-9;
|
|
|
|
double requests_per_second = request_count / elapsed_seconds;
|
|
|
|
fprintf(stderr, "qps = %g\n", requests_per_second);
|
|
|
|
}
|