blob: 6c9c123dfc60c606b10304bb754eeec1d7691c1a [file] [log] [blame] [edit]
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Netscape Portable Runtime (NSPR).
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998-2000
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/*
** File: thruput.c
** Description: Test server's throughput capability comparing various
** implmentation strategies.
**
** Note: Requires a server machine and an aribitrary number of
** clients to bang on it. Trust the numbers on the server
** more than those being displayed by the various clients.
*/
#include "prerror.h"
#include "prinrval.h"
#include "prinit.h"
#include "prio.h"
#include "prlock.h"
#include "prmem.h"
#include "prnetdb.h"
#include "prprf.h"
#include "prthread.h"
#include "pprio.h"
#include "plerror.h"
#include "plgetopt.h"
#define ADDR_BUFFER 100
#define PORT_NUMBER 51877
#define SAMPLING_INTERVAL 10
#define BUFFER_SIZE (32 * 1024)
static PRInt32 domain = PR_AF_INET;
static PRInt32 protocol = 6; /* TCP */
static PRFileDesc *err = NULL;
static PRIntn concurrency = 1;
static PRInt32 xport_buffer = -1;
static PRUint32 initial_streams = 1;
static PRInt32 buffer_size = BUFFER_SIZE;
static PRThreadScope thread_scope = PR_LOCAL_THREAD;
typedef struct Shared
{
PRLock *ml;
PRUint32 sampled;
PRUint32 threads;
PRIntervalTime timein;
PRNetAddr server_address;
} Shared;
static Shared *shared = NULL;
static PRStatus PrintAddress(const PRNetAddr* address)
{
char buffer[ADDR_BUFFER];
PRStatus rv = PR_NetAddrToString(address, buffer, sizeof(buffer));
if (PR_SUCCESS == rv)
PR_fprintf(err, "%s:%u\n", buffer, PR_ntohs(address->inet.port));
else PL_FPrintError(err, "PR_NetAddrToString");
return rv;
} /* PrintAddress */
static void PR_CALLBACK Clientel(void *arg)
{
PRStatus rv;
PRFileDesc *xport;
PRInt32 bytes, sampled;
PRIntervalTime now, interval;
PRBool do_display = PR_FALSE;
Shared *shared = (Shared*)arg;
char *buffer = (char*)PR_Malloc(buffer_size);
PRNetAddr *server_address = &shared->server_address;
PRIntervalTime connect_timeout = PR_SecondsToInterval(5);
PRIntervalTime sampling_interval = PR_SecondsToInterval(SAMPLING_INTERVAL);
PR_fprintf(err, "Client connecting to ");
(void)PrintAddress(server_address);
do
{
xport = PR_Socket(domain, PR_SOCK_STREAM, protocol);
if (NULL == xport)
{
PL_FPrintError(err, "PR_Socket");
return;
}
if (xport_buffer != -1)
{
PRSocketOptionData data;
data.option = PR_SockOpt_RecvBufferSize;
data.value.recv_buffer_size = (PRSize)xport_buffer;
rv = PR_SetSocketOption(xport, &data);
if (PR_FAILURE == rv)
PL_FPrintError(err, "PR_SetSocketOption - ignored");
data.option = PR_SockOpt_SendBufferSize;
data.value.send_buffer_size = (PRSize)xport_buffer;
rv = PR_SetSocketOption(xport, &data);
if (PR_FAILURE == rv)
PL_FPrintError(err, "PR_SetSocketOption - ignored");
}
rv = PR_Connect(xport, server_address, connect_timeout);
if (PR_FAILURE == rv)
{
PL_FPrintError(err, "PR_Connect");
if (PR_IO_TIMEOUT_ERROR != PR_GetError())
PR_Sleep(connect_timeout);
PR_Close(xport); /* delete it and start over */
}
} while (PR_FAILURE == rv);
do
{
bytes = PR_Recv(
xport, buffer, buffer_size, 0, PR_INTERVAL_NO_TIMEOUT);
PR_Lock(shared->ml);
now = PR_IntervalNow();
shared->sampled += bytes;
interval = now - shared->timein;
if (interval > sampling_interval)
{
sampled = shared->sampled;
shared->timein = now;
shared->sampled = 0;
do_display = PR_TRUE;
}
PR_Unlock(shared->ml);
if (do_display)
{
PRUint32 rate = sampled / PR_IntervalToMilliseconds(interval);
PR_fprintf(err, "%u streams @ %u Kbytes/sec\n", shared->threads, rate);
do_display = PR_FALSE;
}
} while (bytes > 0);
} /* Clientel */
static void Client(const char *server_name)
{
PRStatus rv;
PRHostEnt host;
char buffer[PR_NETDB_BUF_SIZE];
PRIntervalTime dally = PR_SecondsToInterval(60);
PR_fprintf(err, "Translating the name %s\n", server_name);
rv = PR_GetHostByName(server_name, buffer, sizeof(buffer), &host);
if (PR_FAILURE == rv)
PL_FPrintError(err, "PR_GetHostByName");
else
{
if (PR_EnumerateHostEnt(
0, &host, PORT_NUMBER, &shared->server_address) < 0)
PL_FPrintError(err, "PR_EnumerateHostEnt");
else
{
do
{
shared->threads += 1;
(void)PR_CreateThread(
PR_USER_THREAD, Clientel, shared,
PR_PRIORITY_NORMAL, thread_scope,
PR_UNJOINABLE_THREAD, 8 * 1024);
if (shared->threads == initial_streams)
{
PR_Sleep(dally);
initial_streams += 1;
}
} while (PR_TRUE);
}
}
}
static void PR_CALLBACK Servette(void *arg)
{
PRInt32 bytes, sampled;
PRIntervalTime now, interval;
PRBool do_display = PR_FALSE;
PRFileDesc *client = (PRFileDesc*)arg;
char *buffer = (char*)PR_Malloc(buffer_size);
PRIntervalTime sampling_interval = PR_SecondsToInterval(SAMPLING_INTERVAL);
if (xport_buffer != -1)
{
PRStatus rv;
PRSocketOptionData data;
data.option = PR_SockOpt_RecvBufferSize;
data.value.recv_buffer_size = (PRSize)xport_buffer;
rv = PR_SetSocketOption(client, &data);
if (PR_FAILURE == rv)
PL_FPrintError(err, "PR_SetSocketOption - ignored");
data.option = PR_SockOpt_SendBufferSize;
data.value.send_buffer_size = (PRSize)xport_buffer;
rv = PR_SetSocketOption(client, &data);
if (PR_FAILURE == rv)
PL_FPrintError(err, "PR_SetSocketOption - ignored");
}
do
{
bytes = PR_Send(
client, buffer, buffer_size, 0, PR_INTERVAL_NO_TIMEOUT);
PR_Lock(shared->ml);
now = PR_IntervalNow();
shared->sampled += bytes;
interval = now - shared->timein;
if (interval > sampling_interval)
{
sampled = shared->sampled;
shared->timein = now;
shared->sampled = 0;
do_display = PR_TRUE;
}
PR_Unlock(shared->ml);
if (do_display)
{
PRUint32 rate = sampled / PR_IntervalToMilliseconds(interval);
PR_fprintf(err, "%u streams @ %u Kbytes/sec\n", shared->threads, rate);
do_display = PR_FALSE;
}
} while (bytes > 0);
} /* Servette */
static void Server(void)
{
PRStatus rv;
PRNetAddr server_address, client_address;
PRFileDesc *xport = PR_Socket(domain, PR_SOCK_STREAM, protocol);
if (NULL == xport)
{
PL_FPrintError(err, "PR_Socket");
return;
}
rv = PR_InitializeNetAddr(PR_IpAddrAny, PORT_NUMBER, &server_address);
if (PR_FAILURE == rv) PL_FPrintError(err, "PR_InitializeNetAddr");
else
{
rv = PR_Bind(xport, &server_address);
if (PR_FAILURE == rv) PL_FPrintError(err, "PR_Bind");
else
{
PRFileDesc *client;
rv = PR_Listen(xport, 10);
PR_fprintf(err, "Server listening on ");
(void)PrintAddress(&server_address);
do
{
client = PR_Accept(
xport, &client_address, PR_INTERVAL_NO_TIMEOUT);
if (NULL == client) PL_FPrintError(err, "PR_Accept");
else
{
PR_fprintf(err, "Server accepting from ");
(void)PrintAddress(&client_address);
shared->threads += 1;
(void)PR_CreateThread(
PR_USER_THREAD, Servette, client,
PR_PRIORITY_NORMAL, thread_scope,
PR_UNJOINABLE_THREAD, 8 * 1024);
}
} while (PR_TRUE);
}
}
} /* Server */
static void Help(void)
{
PR_fprintf(err, "Usage: [-h] [<server>]\n");
PR_fprintf(err, "\t-s <n> Initial # of connections (default: 1)\n");
PR_fprintf(err, "\t-C <n> Set 'concurrency' (default: 1)\n");
PR_fprintf(err, "\t-b <nK> Client buffer size (default: 32k)\n");
PR_fprintf(err, "\t-B <nK> Transport recv/send buffer size (default: sys)\n");
PR_fprintf(err, "\t-G Use GLOBAL threads (default: LOCAL)\n");
PR_fprintf(err, "\t-X Use XTP transport (default: TCP)\n");
PR_fprintf(err, "\t-6 Use IPv6 (default: IPv4)\n");
PR_fprintf(err, "\t-h This message and nothing else\n");
PR_fprintf(err, "\t<server> DNS name of server\n");
PR_fprintf(err, "\t\tIf <server> is not specified, this host will be\n");
PR_fprintf(err, "\t\tthe server and not act as a client.\n");
} /* Help */
int main(int argc, char **argv)
{
PLOptStatus os;
const char *server_name = NULL;
PLOptState *opt = PL_CreateOptState(argc, argv, "hGX6C:b:s:B:");
err = PR_GetSpecialFD(PR_StandardError);
while (PL_OPT_EOL != (os = PL_GetNextOpt(opt)))
{
if (PL_OPT_BAD == os) continue;
switch (opt->option)
{
case 0: /* Name of server */
server_name = opt->value;
break;
case 'G': /* Globular threads */
thread_scope = PR_GLOBAL_THREAD;
break;
case 'X': /* Use XTP as the transport */
protocol = 36;
break;
case '6': /* Use IPv6 */
domain = PR_AF_INET6;
break;
case 's': /* initial_streams */
initial_streams = atoi(opt->value);
break;
case 'C': /* concurrency */
concurrency = atoi(opt->value);
break;
case 'b': /* buffer size */
buffer_size = 1024 * atoi(opt->value);
break;
case 'B': /* buffer size */
xport_buffer = 1024 * atoi(opt->value);
break;
case 'h': /* user wants some guidance */
default:
Help(); /* so give him an earful */
return 2; /* but not a lot else */
}
}
PL_DestroyOptState(opt);
shared = PR_NEWZAP(Shared);
shared->ml = PR_NewLock();
PR_fprintf(err,
"This machine is %s\n",
(NULL == server_name) ? "the SERVER" : "a CLIENT");
PR_fprintf(err,
"Transport being used is %s\n",
(6 == protocol) ? "TCP" : "XTP");
if (PR_GLOBAL_THREAD == thread_scope)
{
if (1 != concurrency)
{
PR_fprintf(err, " **Concurrency > 1 and GLOBAL threads!?!?\n");
PR_fprintf(err, " **Ignoring concurrency\n");
concurrency = 1;
}
}
if (1 != concurrency)
{
PR_SetConcurrency(concurrency);
PR_fprintf(err, "Concurrency set to %u\n", concurrency);
}
PR_fprintf(err,
"All threads will be %s\n",
(PR_GLOBAL_THREAD == thread_scope) ? "GLOBAL" : "LOCAL");
PR_fprintf(err, "Client buffer size will be %u\n", buffer_size);
if (-1 != xport_buffer)
PR_fprintf(
err, "Transport send & receive buffer size will be %u\n", xport_buffer);
if (NULL == server_name) Server();
else Client(server_name);
} /* main */
/* thruput.c */