|
|
//
// QTCP.C version 1.0.3
//
// This program tests the quality of a network connection in terms of
// variation in latency (jitter). It is based on TTCP, a public domain
// program, written for BSD. The version of TTCP upon which this was
// based has been contributed to by:
//
// T.C. Slattery, USNA (18 Dec 84)
// Mike Muuss and T. Slattery (16 Oct 85)
// Silicon Graphics, Inc. (1989)
//
// QTCP is written by Yoram Bernet ([email protected])
// further development work by John Holmes ([email protected])
//
// QTCP user level code may be used to provide rough jitter measurements,
// which indicate both operating system and network jitter. However, QTCP
// is intended to be used in conjunction with kernel timestamping for precise
// jitter measurements. The kernel component timestmp.sys should be installed
// when running on Win2000 (beta-3 or later).
//
// timestmp.sys is written by Shreedhar Madhavapeddi ([email protected])
//
//
// Distribution Status -
// Public Domain. Distribution Unlimited.
//
// Version History -
// 0.8:
// - adaptation of TTCP by Yoram Bernet -- core functionality
// 0.9: (6/15/99)
// - first version by John Holmes -- bug fixes and new features
// - fixed all compile warnings
// - added -v option to set up RSVP connection without sending data
// - added -y option to skip confirmation of continues
// - fixed line length error in log files
// - fixed service type string to display properly
// - added best effort and no service service types (BE & NS)
// - added version string print upon execution
// 0.9.1: (6/17/99)
// - check for hardware clock reset using correlation coefficient
// - fixed incorrect clock skew in .sta file
// - fixed -v option to keep socket open until user carriage returns
// - added local statistics to clock skew computation for better estimate
// - added -k option to prevent using local statistics for clock skew
// - fixed maximum latency computation
// 0.9.2: (6/23/99)
// - fixed peak rate in flowspec so no shaping happens in CL servicetype
// - added -e option to force shaping
// - fixed error in allocating size of log array with bufsize <= 1500 bytes
// - fixed not exiting on receiver
// - fixed access violation if no filename specified on receiver
// - changed dummy log entries to be off by default
// - added -F option to convert raw file to log file
// 0.9.3: (6/29/99)
// - improved low transmission speed at high packet/second rates by changing
// default # async buffers from 3 to 32
// - fixed user mode timestamps to use NtQueryPerformanceCounter()
// - added -u option to use usermode timestamps in log generation
// 0.9.4: (7/8/99)
// - cleaned up source (chopped up main into a bunch of functions to improve readability)
// - fixed default buffer size to be 1472 bytes, so whole packet is 1500 bytes.
// - rewrote i/o code to use callbacks for asynch i/o in order to improve throughput
// - doing the right thing if not getting kernel-mode timestamps
// - added ability to run for a specified amount of time with -n##s paramater
// - added dynamic resizing of log array on receiver to prevent access violations
// with mismatched parameters
// - fixed devious bug in the GrowLogArray routine
// - fixed total time reported for long runs (use UINT64 instead of DWORD)
// - fixed problem with -F option specified on empty but extant file
// - added RSVPMonitor Thread to watch for RSVP-err messages on receiver and
// early abort by sender
// - removed -a option as it is now obsolete
// - revised usage screen to make more clear what pertains to sender and what
// pertains to receiver
// - fixed crash if receiver terminates before transmitter finishes
// 0.9.5: (7/15/99)
// - re-added error checking on WriteFileEx and ReadFileEx routines
// 0.9.6: (7/20/99)
// - changed default filler data in buffer so that it is less compressible to
// better account for links that compress data before sending
// - added -i option to use more compressible data
// 0.9.7: (7/24/99)
// - put back a thread to watch for 'q' on receiver to quit properly before sender's done
// - added control channel to better handle RSVP timeouts, early aborts, etc.
// - if no calibrations are specified, we calibrate based on all buffers
// - gracefully exit if LogRecord runs out of memory, saving all logs we've got so far
// - changed default behavior so raw file is dumped with no normalization whatsoever.
// - improved the way anomalous points are caught in clock-skew calc
// 0.9.8: (7/28/99)
// - fixed field assignments & file opening problem on converting raw file to log.
// - changed latency to be written to file to signed and fixed normalization routine for
// cases when clocks are orders of magnitude different (underflow error)
// - added absolute deviation as goodness of fit measure
// - added routine to look for clock jumps and fix for them (with -k3 option)
// 0.9.9: (8/4/99)
// - changed format of .sta file to include more useful information and test params
// - changed Logging scheme so that we are limited by disk space instead of memory
// (now using a memory mapped file for the log, so the theoretical limit has gone from
// less than 2GB to 18EB, but we won't ever get that in practice on normal disks)
// - added -ni option to run indefinitely
// - added -R##B option to specify tokenrate in bytes
// - made default not to show dropped packets at console (it only causes more drops)
// - added -q## option to log only every nth packet
// 1.0.0: (8/6/99)
// - fixed bug where if a new qtcp receiver is started immediately after a previous
// instance, it will think "theend" packets are normal packets and AV
// - added check for the piix4 timer chip and an appropriate warning
// - using precise incorrect value in FixWackyTimestamps function
// - added -A option (aggregate data processing of all .sta files in a directory)
// 1.0.1: (8/6/99)
// - fixed incorrect calculation of send rate with dropped packets
// 1.0.2: (8/23/99)
// - improved clock skip detection algorithm
// - fixed a bug in control channel communication of TokenRate
// - fixed problem with forceshape option when rate is specified in bytes
// 1.0.3: (8/26/99)
// - fixed SENDERS NO_SENDERS bug
// - added summary over time to aggregate stats option
// - changed .sta file format to include number of drops
// - fixed shaping in best effort servicetype
// ToDo:
// - add histogram to output in .sta file and on console
// - add ability to run w/o control channel connection
// - mark control channel traffic as higher priority
// - add more aggregate stats (time varying statistics) -- maybe fourier xform
#ifndef lint
static char RCSid[] = "qtcp.c $Revision: 1.0.3 $"; #endif
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <io.h>
#include <signal.h>
#include <ctype.h>
#include <sys/types.h>
#include <winsock2.h>
#include <qossp.h>
#include <winbase.h>
#include <time.h>
#include <shlwapi.h>
#if defined(_AMD64_)
#include <math.h>
#endif
#include "ioctl.h"
#define CONTROL_PORT 7239
CHAR *VERSION_STRING = "1.0.3"; #define MAX_STRING 255
INT64 MAX_INT64=9223372036854775807;
HANDLE hRSVPMonitor; DWORD idRSVPMonitor;
INT64 g_BadHalAdjustment = 46869688; // this is the value on a piix4 chip
SYSTEM_INFO g_si; char g_szErr[255]; CRITICAL_SECTION g_csLogRecord; EXCEPTION_RECORD g_erec;
BOOL g_fOtherSideFinished = FALSE; BOOL g_fReadyForXmit = FALSE; SOCKET fd; SOCKET g_sockControl = INVALID_SOCKET; struct sockaddr_in sinhim; struct sockaddr_in sinme; short port = 5003; // UDP port number
char *host; // ptr to name of host
char szHisAddr[MAX_STRING]; char szMyAddr[MAX_STRING];
int trans; // 0=receive, !0=transmit mode
int normalize = 0; // dump raw file after normalizing
char *Name = NULL; // Name of file for logs
HANDLE hRawFile = NULL; HANDLE hLogFile = NULL; HANDLE hStatFile = NULL; HANDLE hDriver = NULL; // handle to the timestmp.sys driver
WSADATA WsaData; WSAEVENT QoSEvents;
SYSTEMTIME systimeStart; INT64 timeStart; INT64 time0; INT64 time1; INT64 timeElapsed;
CHAR* TheEnd = "TheEnd"; CHAR* ControlledLoad = "CL"; CHAR* Guaranteed = "GR"; CHAR* BestEffort = "BE"; CHAR* NoTraffic = "NT";
LPCTSTR DriverName = TEXT("\\\\.\\Timestmp");
BOOL fWackySender = FALSE; BOOL fWackyReceiver = FALSE;
typedef struct { HANDLE hSocket; INT TokenRate; INT MaxSDUSize; INT BucketSize; INT MinPolicedSize; SERVICETYPE dwServiceType; CHAR *szServiceType; INT buflen; // length of buffer
INT nbuf; // number of buffers to send
INT64 calibration; BOOLEAN UserStamps; // By default, we use kernel mode timestamping, if available
BOOLEAN SkipConfirm; // By default, we wait for user confirmation at certain times
BOOLEAN RESVonly; // By default, we send data after getting the resv
int SkewFitMode; // by default, 0 = no fit, 1 = chisq, 2 = chisq w/outlier removal
// 3 = abs dev
BOOLEAN Wait; // By default, we wait for a QoS reservation
BOOLEAN Dummy; // insert dummy rows in log by default
BOOLEAN PrintDrops; // report dropped packets on console
BOOLEAN ForceShape; // by default, we do not force shaping on CL flows
BOOLEAN RateInBytes; // KB by default
BOOLEAN AggregateStats; // by default, we do not do this
BOOLEAN ConvertOnly; // by default, we act normally and do not go around converting files
BOOLEAN NoSenderTimestamps; BOOLEAN NoReceiverTimestamps; BOOLEAN TimedRun; // true if we're running for a specified amount of time
BOOLEAN RunForever; // run until the person pushes 'q'
BOOLEAN nBufUnspecified; // true if the user has not specified the -n parameter
BOOLEAN RandomFiller;// by default, we use random, incompressible filler data
int LoggingPeriod; // by default, 1 (log every packet)
} QTCPPARAMS, *PQTCPPARAMS;
QTCPPARAMS g_params;
typedef struct { BOOL Done; // done if true
int nWritesInProgress; // number of writes outstanding
int nReadsInProgress; // number of reads outstanding
int nBuffersSent; // number of buffers sent to the device
int nBuffersReceived; // number of buffers received from network
int nBytesTransferred; // number of bytes written to device
} QTCPSTATE, *PQTCPSTATE;
QTCPSTATE g_state;
typedef struct { OVERLAPPED Overlapped; PVOID pBuffer; DWORD BytesWritten; } IOREQ, *PIOREQ;
#define MAX_PENDING_IO_REQS 64 // number of simultaneous async calls.
// This format is used for the buffer
// transmitted on the wire.
typedef struct _BUFFER_FORMAT{ INT64 TimeSentUser; INT64 TimeReceivedUser; INT64 TimeSent; INT64 TimeReceived; INT64 Latency; INT BufferSize; INT SequenceNumber; } BUFFER_FORMAT, *PBUFFER_FORMAT;
// This format is used for the scheduling record
// written based on the received buffers.
typedef struct _LOG_RECORD{ INT64 TimeSentUser; INT64 TimeReceivedUser; INT64 TimeSent; INT64 TimeReceived; INT64 Latency; INT BufferSize; INT SequenceNumber; } LOG_RECORD, *PLOG_RECORD;
// The LOG structure is a data abstraction for a log of LOG_RECORDS that uses memory
// mapped files to have a theoretical storage limit of 18EB. It uses two buffers in memory
// along with a watcher thread so that there is no delay when switching from one bit to
// the next.
typedef struct { INT64 nBuffersLogged; PBYTE pbMapView; // view of file in Get/SetLogElement functions
INT64 qwMapViewOffset; // offset of Get/Set view in file (rounded down to allocation)
char *szStorageFile; // the name of the mapped file on disk (so we can delete it)
HANDLE hStorageFile; // the memory mapped file on disk
HANDLE hFileMapping; // the file mapping object for our storage file
INT64 qwFileSize; // the size of the storage file in bytes
} LOG, *PLOG; LOG g_log;
// The STATS structure keeps a record of overall statistics for the qtcp run
typedef struct { char szStaFile[MAX_PATH]; char szSender[MAX_STRING]; char szReceiver[MAX_STRING]; int nBuffers; int nTokenRate; int nBytesPerBuffer; double sendrate; double recvrate; double median; double mean; double var; double kurt; double skew; double abdev; FILETIME time; int nDrops; } STATS, *PSTATS;
INT64 totalBuffers; INT anomalies = 0; INT SequenceNumber = 0; INT LastSequenceNumber = -1;
#define bcopy(s, d, c) memcpy((u_char *)(d), (u_char *)(s), (c))
#define bzero(d, l) memset((d), '\0', (l))
#define SENDER 1
#define RECEIVER 0
#define SECONDS_BETWEEN_HELLOS 120
// control messages
#define MSGCH_DELIMITER '!'
#define MSGST_RSVPERR "RSVPERR"
#define MSGST_ABORT "ABORT"
#define MSGST_ERROR "ERROR"
#define MSGST_DONE "DONE"
#define MSGST_HELLO "HELLO"
#define MSGST_RATE "RATE"
#define MSGST_SIZE "SIZE"
#define MSGST_NUM "NUM"
#define MSGST_READY "READY"
#define MSGST_VER "VER"
// -------------------
// Function prototypes
// -------------------
VOID SetDefaults();
VOID Usage();
BOOLEAN GoodParams();
VOID SetupLogs();
VOID SetupSockets(); SOCKET OpenQoSSocket();
INT SetQoSSocket( SOCKET fd, BOOL Sending);
VOID WaitForQoS( BOOL Sender, SOCKET fd);
ULONG GetRsvpStatus( DWORD dwTimeout, SOCKET fd);
VOID PrintRSVPStatus( ULONG code);
VOID DoTransmit();
VOID WINAPI TransmitCompletionRoutine( DWORD dwErrorCode, DWORD dwNumberOfBytesTransferred, LPOVERLAPPED pOverlapped);
VOID WINAPI DelimiterSendCompletion( DWORD dwErrorCode, DWORD dwNumberOfBytesTransferred, LPOVERLAPPED pOverlapped);
VOID FillBuffer( CHAR *Cp, INT Cnt);
INT TimeStamp( CHAR *Cp, INT Cnt);
VOID DoReceive();
VOID WINAPI RecvCompletionRoutine( DWORD dwErrorCode, DWORD dwNumberOfBytesTransferred, LPOVERLAPPED pOverlapped);
VOID LogRecord(CHAR * Buffer);
BOOL CreateLog(PLOG plog, INT64 c); BOOL GetLogEntry(PLOG plog, PLOG_RECORD prec, INT64 i); BOOL DestroyLog(PLOG plog); BOOL SetLogEntry(PLOG plog, PLOG_RECORD prec, INT64 i); BOOL AddLogEntry(PLOG plog, PLOG_RECORD prec);
UINT64 GetUserTime();
DWORD MyCreateFile( IN PCHAR Name, IN PCHAR Extension, OUT HANDLE *File); void AggregateStats();
int IndexOfStatRecWith(int rate, int size, INT64 time, PSTATS pStats, int cStats);
BOOL GetStatsFromFile(PSTATS pstats);
VOID DoStatsFromFile(); DWORD OpenRawFile( IN PCHAR Name, OUT HANDLE *File);
INT64 ReadSchedulingRecords(HANDLE file); VOID DoStats(); VOID WriteSchedulingRecords( HANDLE File, BOOLEAN InsertDummyRows);
void AdvancedStats();
VOID GenericStats();
VOID CheckForLostPackets();
VOID WriteStats( UCHAR * HoldingBuffer, INT Count);
VOID NormalizeTimeStamps();
VOID ClockSkew( DOUBLE * Slope, DOUBLE * Offset);
BOOLEAN AnomalousPoint( DOUBLE x, DOUBLE y);
VOID AdjustForClockSkew( DOUBLE Slope, DOUBLE Offset); BOOL FixWackyTimestamps();
// monitor threads
DWORD WINAPI RSVPMonitor (LPVOID lpvThreadParm); DWORD WINAPI KeyboardMonitor (LPVOID lpvThreadParm); DWORD WINAPI ControlSocketMonitor(LPVOID lpvThreadParm); DWORD WINAPI LogWatcher(LPVOID lpvThreadParm);
// utilities
int SendControlMessage(SOCKET sock, char * szMsg); void ErrorExit(char *msg, DWORD dwErrorNumber); UINT64 GetBadHalAdjustment(); //int compare( const void *arg1, const void *arg2 );
int __cdecl compare(const void *elem1, const void *elem2 ) ; void medfit(double x[], double y[], int N, double *a, double *b, double *abdev); double mode(const double data[], const int N); void RemoveDuplicates(int rg[], int * pN); void RemoveDuplicatesI64(INT64 rg[], int * pN); #define RoundUp(val, unit) (val + (val % unit))
#define InRange(val, low, high) ((val >= low) && (val < high)) ? TRUE:FALSE
void PrintFlowspec(LPFLOWSPEC lpfs);
VOID __cdecl main(INT argc,CHAR **argv) { int error; char *stopstring; char szBuf[MAX_STRING]; BOOL b; ULONG bytesreturned; printf("qtcp version %s\n\n",VERSION_STRING);
if (GetBadHalAdjustment() == (UINT64)g_BadHalAdjustment) { printf("WARNING: This machine has a timer whose frequency matches that of the piix4\n"); printf(" chip. There is a known bug in the HAL for this timer that causes the\n"); printf(" timer to jump forward about 4.7 seconds every once in a while.\n"); printf(" If you notice large jumps in the timestamps from this machine, try\n"); printf(" running with the -k3 option to attempt to correct for the timer bug.\n\n"); } srand( (unsigned)time( NULL ) ); // seed the random number generator
timeStart = GetUserTime(); GetSystemInfo(&g_si); error = WSAStartup( 0x0101, &WsaData ); if (error == SOCKET_ERROR) { printf("qtcp: WSAStartup failed %ld:", WSAGetLastError()); }
if (argc < 2) Usage();
Name = malloc(MAX_STRING); bzero(Name,MAX_STRING);
SetDefaults();
argv++; argc--; while( argc>0 && argv[0][0] == '-' ) { switch (argv[0][1]) { case 'B': g_params.BucketSize = atoi(&argv[0][2]); break; case 'm': g_params.MinPolicedSize = atoi(&argv[0][2]); break; case 'M': g_params.MaxSDUSize = atoi(&argv[0][2]); break; case 'R': g_params.TokenRate = (int)strtod(&argv[0][2],&stopstring); if (*stopstring == 0) { // normal run
g_params.RateInBytes = FALSE; break; } if (*stopstring == 'B') { // rate is in bytes / sec, not kbytes/sec
g_params.RateInBytes = TRUE; break; } else { Usage(); break; } case 'S': g_params.szServiceType = &argv[0][2]; if(!strncmp(g_params.szServiceType, ControlledLoad, 2)){ g_params.dwServiceType = SERVICETYPE_CONTROLLEDLOAD; break; } if(!strncmp(g_params.szServiceType, Guaranteed, 2)){ g_params.dwServiceType = SERVICETYPE_GUARANTEED; break; } if(!strncmp(g_params.szServiceType, BestEffort, 2)){ g_params.dwServiceType = SERVICETYPE_BESTEFFORT; g_params.Wait = FALSE; break; } if(!strncmp(g_params.szServiceType, NoTraffic, 2)){ g_params.dwServiceType = SERVICETYPE_NOTRAFFIC; g_params.Wait = FALSE; break; } fprintf(stderr, "Invalid service type (not CL or GR).\n"); fprintf(stderr, "Using GUARANTEED service.\n"); break; case 'e': g_params.ForceShape = TRUE; break; case 'W': g_params.Wait = FALSE; break; case 't': trans = 1; break; case 'f': strcpy(Name,&argv[0][2]); break; case 'F': strcpy(Name,&argv[0][2]); g_params.ConvertOnly = TRUE; break; case 'A': strcpy(Name,&argv[0][2]); g_params.AggregateStats = TRUE; break; case 'r': trans = 0; break; case 'n': g_params.nbuf = (INT)strtod(&argv[0][2],&stopstring); if (*stopstring == 0) { // normal run
g_params.nBufUnspecified = FALSE; break; } if (*stopstring == 'i') { // run for an infinite time
g_params.RunForever = TRUE; break; } if (*stopstring == 's') { // run for a specified time
g_params.TimedRun = TRUE; printf("Running for %d seconds\n",g_params.nbuf); break; } else { Usage(); break; } case 'c': g_params.calibration = atoi(&argv[0][2]); break; case 'k': g_params.SkewFitMode = atoi(&argv[0][2]); if (g_params.SkewFitMode < 0 || g_params.SkewFitMode > 3) ErrorExit("Invalid Skew Fit Mode",g_params.SkewFitMode); break; case 'l': g_params.buflen = atoi(&argv[0][2]); break; case 'p':
port = (short)atoi(&argv[0][2]);
break; case 'd': g_params.Dummy = TRUE; break; case 'N': normalize = 1; break; case 'P': g_params.PrintDrops = TRUE; break; case 'v': g_params.RESVonly = TRUE; break; case 'y': g_params.SkipConfirm = TRUE; break; case 'u': g_params.UserStamps = TRUE; break; case 'i': g_params.RandomFiller = FALSE; break; case 'q': g_params.LoggingPeriod = atoi(&argv[0][2]); break; default: Usage(); } argv++; argc--; }
//
// Make an ioctl to Timestmp driver, if its exists about the
// port to timestamp on.
//
printf("Trying to open %s\n", DriverName); hDriver = CreateFile(DriverName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, // Default security
OPEN_EXISTING, 0, 0); // No template
if(hDriver == INVALID_HANDLE_VALUE) { printf("Timestmp.sys CreateFile- Error %ld - Maybe its not INSTALLED\n", GetLastError()); // Otherwise, print success and close the driver
} else {
printf("Timestmp.sys - CreateFile Success.\n");
b = DeviceIoControl( hDriver, // handle to a device, file, or directory
IOCTL_TIMESTMP_REGISTER_PORT, // control code of operation to perform
&port, // pointer to buffer to supply input data
2, //nInBufferSize, // size, in bytes, of input buffer
NULL, //lpOutBuffer, // pointer to buffer to receive output data
0, //nOutBufferSize, // size, in bytes, of output buffer
&bytesreturned, // pointer to variable to receive byte count
NULL // pointer to overlapped structure
);
printf("IOCTL performed\n"); if (!b) {
printf("IOCTL FAILED!\n", GetLastError()); // Close the driver
CloseHandle(hDriver); } else { printf("IOCTL succeeded!\n"); } } // get the host address if we're the sender
if(trans) { if(argc != 1) Usage(); host = malloc(strlen(argv[0]) + 1); strcpy(host,argv[0]); } // first, we see if this is a conversion -- if it is, just jump right in, else go on
if (g_params.ConvertOnly) { DoStatsFromFile(); exit(0); }
// see if we're supposed to do stat aggregation
if (g_params.AggregateStats) { AggregateStats(); exit(0); } // Do argument sanity tests & set default values if not already set
if (!GoodParams()) exit(1);
// Spawn off the control socket monitor thread
CreateThread(NULL, 0, ControlSocketMonitor, (LPVOID)host, 0, NULL);
// Get the sockets ready, set for QoS, and wait for a connection
SetupSockets();
// Check for a RESV only session
if (g_params.RESVonly) { // keep socket open and hang out
fprintf(stdout, "RSVP connection established. Press return to quit.\n"); while(TRUE){ if(getchar()) break; } exit(0); }
// start up the RSVPMonitor and keyboard monitor threads to watch for wackiness
hRSVPMonitor = CreateThread(NULL,0,RSVPMonitor,NULL,0,&idRSVPMonitor); CreateThread(NULL,0,KeyboardMonitor,NULL,0,NULL);
// wait for the control channel to be set up, if it's not already
while (g_sockControl == INVALID_SOCKET) Sleep(50);
if (!trans) { // we want to make sure these are not initialized, so we don't put wrong values in .sta
g_params.buflen = 0; g_params.nbuf = 2048; // it's ok to init this because it's not saved in .sta
g_params.TokenRate = 0; } totalBuffers = g_params.nbuf + g_params.calibration; // Tell the receiver the important parameters
if (trans) { if (g_params.RunForever) totalBuffers = 2048; sprintf(szBuf, "%s %d", MSGST_NUM, totalBuffers); SendControlMessage(g_sockControl, szBuf); sprintf(szBuf, "%s %d", MSGST_SIZE, g_params.buflen); SendControlMessage(g_sockControl, szBuf); if (g_params.RateInBytes) sprintf(szBuf, "%s %d", MSGST_RATE, g_params.TokenRate); else sprintf(szBuf, "%s %d", MSGST_RATE, 1000 * g_params.TokenRate); SendControlMessage(g_sockControl, szBuf); }
while (!g_fReadyForXmit) Sleep(50); // If we're the receiver, set up the log buffer and files
if((Name != NULL) && !trans){ SetupLogs(); }
// Let the user know what's up
if(trans){ fprintf(stdout, "qtcp TRANSMITTER\n"); if (g_params.calibration) fprintf(stdout, "\tSending %d calibration buffers.\n", g_params.calibration); fprintf(stdout, "\tSending %d buffers of length %d.\n", g_params.nbuf, g_params.buflen); fprintf(stdout, "\tDestination address (port) is %s (%d).\n", argv[0], port); if (g_params.RateInBytes) fprintf(stdout, "\tToken rate is %d bytes/sec.\n", g_params.TokenRate); else fprintf(stdout, "\tToken rate is %d Kbytes/sec.\n", g_params.TokenRate); fprintf(stdout, "\tBucket size is %d bytes.\n", g_params.BucketSize); } else{ fprintf(stdout, "qtcp RECEIVER\n"); if (g_params.calibration) fprintf(stdout, "\tPrepared to receive %d calibration buffers.\n", g_params.calibration); if (!g_params.nBufUnspecified) { fprintf(stdout, "\tPrepared to receive %d buffers.\n", g_params.nbuf); } } // Do the actual communication
time0 = GetUserTime(); if (trans) DoTransmit(); else DoReceive(); time1 = GetUserTime(); timeElapsed = (time1 - time0)/10000;
// tell the other guy we're done
SendControlMessage(g_sockControl, MSGST_DONE); // get to a new line
printf("\n");
// put some stats on the transmitter console
if (trans) { printf("Sent %ld bytes in %I64d milliseconds = %I64d KBps\n", g_state.nBytesTransferred, timeElapsed, g_state.nBytesTransferred/timeElapsed); }
// wait for the other side to tell us it's done.
while (!g_fOtherSideFinished) Sleep(50); // let the user know if timestmp.sys was installed
if (g_params.NoSenderTimestamps && g_params.NoReceiverTimestamps) printf("WARNING: No kernel-level timestamps detected on sender or receiver\n\tUsing user-mode timestamps.\n"); else if (g_params.NoSenderTimestamps) printf("WARNING: No kernel-level timestamps detected on sender\n\tUsing user-mode timestamps.\n"); else if (g_params.NoReceiverTimestamps) printf("WARNING: No kernel-level timestamps detected on receiver\n Using user-mode timestamps.\n");
// Close down the sockets
if (closesocket((SOCKET)g_params.hSocket) != 0) fprintf(stderr,"closesocket failed: %d\n",WSAGetLastError());
if(timeElapsed <= 100){ fprintf(stdout, "qtcp %s:Time interval too short for valid measurement!\n", trans?"-t":"-r"); }
// Close files & exit
if(!trans && Name != NULL){ if (g_log.nBuffersLogged) { DoStats(); } else { printf("ERROR: no buffers logged due to errors.\n"); } CloseHandle(hRawFile); CloseHandle(hLogFile); CloseHandle(hStatFile); DestroyLog(&g_log); } if (WSACleanup() != 0) fprintf(stderr,"WSACleanup failed: %d\n",WSAGetLastError()); printf("\n"); _exit(0); } // main()
VOID SetDefaults() { g_params.hSocket = NULL; g_params.TokenRate = 100; g_params.MaxSDUSize = QOS_NOT_SPECIFIED; g_params.BucketSize = QOS_NOT_SPECIFIED; g_params.MinPolicedSize = QOS_NOT_SPECIFIED; g_params.dwServiceType = SERVICETYPE_GUARANTEED; g_params.szServiceType = "GR"; g_params.buflen = 1472; /* length of buffer */ g_params.nbuf = 2 * 1024; /* number of buffers to send */ g_params.calibration = 0; g_params.UserStamps = FALSE; // By default, we use kernel mode timestamping, if available
g_params.SkipConfirm = FALSE; // By default, we wait for user confirmation at certain times
g_params.SkewFitMode = 2; // by default, we use absolute deviation
g_params.Wait = TRUE; // By default, we wait for a QoS reservation
g_params.Dummy = FALSE; // insert dummy rows in log by default
g_params.PrintDrops = FALSE; // report dropped packets on console
g_params.ForceShape = FALSE; // by default, we do not force shaping on CL flows
g_params.RateInBytes = FALSE; // KB by default
g_params.ConvertOnly = FALSE; // by default, we act normally and do not go around converting files
g_params.AggregateStats = FALSE; g_params.NoSenderTimestamps = FALSE; g_params.NoReceiverTimestamps = FALSE; g_params.TimedRun = FALSE; // by default, we run for a number of packets
g_params.RunForever = FALSE; // by default, we run fora number of packets
g_params.nBufUnspecified = TRUE; g_params.RandomFiller = TRUE; // by default, we use random filler to prevent compression
g_params.LoggingPeriod = 1; } // SetDefaults()
VOID Usage() { fprintf(stderr,"Usage: qtcp [-options] -t host\n"); fprintf(stderr," qtcp [-options] -r\n"); fprintf(stderr," -t options:\n"); fprintf(stderr," -B## TokenBucket size signaled to network and to traffic control\n"); fprintf(stderr," (default is equal to buffer size)\n"); fprintf(stderr," -m## MinPolicedSize signaled to network and to traffic control\n"); fprintf(stderr," (default is equal to buffer size)\n"); fprintf(stderr," -R## TokenRate in kilobytes per second (default is 100 KBPS)\n"); fprintf(stderr," -R##B TokenRate in bytes per second\n"); fprintf(stderr," -e Force shaping to TokenRate.\n"); fprintf(stderr," -M MaxSDUSize to be used in signaling messages (default is buffer\n"); fprintf(stderr," size\n"); fprintf(stderr," -l## length of buffers to transmit (default is 1472 bytes)\n"); fprintf(stderr," -n## number of source bufs written to network (default is 2048 bytes)\n"); fprintf(stderr," -n##s numbef of seconds to send buffers for (numbef of buffers will\n"); fprintf(stderr," be calculated based on other parameters\n"); fprintf(stderr," -ni run indefinitely (will stop when 'q' is pressed on either)\n"); fprintf(stderr," -c## Specifies number of calibration packets to be sent\n"); fprintf(stderr," Calibration packets will be sent immediately\n"); fprintf(stderr," After calibration packets are sent, n additional\n"); fprintf(stderr," packets will be sent. This option is useful if you want to\n"); fprintf(stderr," change network conditions after a set calibration phase\n"); fprintf(stderr," -y skip confirmation message after calibration phase\n"); fprintf(stderr," -p## port number to send to or listen at (default 5003)\n"); fprintf(stderr," -i use more compressible buffer data\n"); fprintf(stderr," -r options:\n"); fprintf(stderr," -f\"filename\" Name prefix to be used in generating log file and\n"); fprintf(stderr," statistics summary. (no file generated by default)\n"); fprintf(stderr," -c## Specifies number of buffers to use in clock-skew calibration\n"); fprintf(stderr," -k0 do not calculate clock skew\n"); fprintf(stderr," -k1 use chi squared as goodness of fit\n"); fprintf(stderr," -k2 use absolute deviation as goodness of fit (default)\n"); fprintf(stderr," -k3 use abs dev and check for clock jumps\n"); fprintf(stderr," -d suppress insertion of dummy log records for lost packets.\n"); fprintf(stderr," -N Normalize before dumping raw file (default is after)\n"); fprintf(stderr," -P enables console reporting of dropped packets\n"); fprintf(stderr," -u use user mode timestamps instead of kernel timestamps in logs\n"); fprintf(stderr," -q## log only every ##th packet\n"); fprintf(stderr," common options:\n"); fprintf(stderr," -S\"service type\" (CL or GR -- GR is default)\n"); fprintf(stderr," -W Suppress waiting for QoS reservation\n"); fprintf(stderr," -v Set up QoS reservation only, send no data\n"); fprintf(stderr," -F\"filename\" Name prefix of raw file to be converted to log file\n"); fprintf(stderr," -A\"path\" Path to directory for aggregate statistics computation\n");
WSACleanup(); exit(1); } // Usage()
BOOLEAN GoodParams() { BOOLEAN ok = TRUE; if(g_params.buflen < sizeof(BUFFER_FORMAT)){ printf("Buffer size too small for record!\n"); ok = FALSE; }
// Unless otherwise specified, min policed size will be equal to
// buflen.
if(g_params.MinPolicedSize == QOS_NOT_SPECIFIED){ g_params.MinPolicedSize = g_params.buflen; } // Same goes for bucket size
if(g_params.BucketSize == QOS_NOT_SPECIFIED){ g_params.BucketSize = g_params.buflen; }
// And for MaxSDU
if(g_params.MaxSDUSize == QOS_NOT_SPECIFIED){ g_params.MaxSDUSize = g_params.buflen; }
// If the bucket size is smaller than the buffer size,
// and this is a sender, then warn the user because
// data will be discarded
if((g_params.BucketSize < g_params.buflen) && trans){ printf("Token bucket size is smaller than buffer size!\n"); ok = FALSE; }
if(g_params.MaxSDUSize < g_params.buflen){ printf("MaxSDU cannot be less than buffer size!\n"); ok = FALSE; }
if(g_params.buflen < 5){ g_params.buflen = 5; // send more than the sentinel size
}
if(g_params.TimedRun) { if (g_params.RateInBytes) g_params.nbuf = g_params.nbuf * g_params.TokenRate / g_params.buflen; else g_params.nbuf = g_params.nbuf * g_params.TokenRate * 1000 / g_params.buflen; printf("Using %d buffers\n",g_params.nbuf); }
return ok; } // GoodParams()
VOID SetupLogs() { CreateLog(&g_log, totalBuffers);
// set up logging files
if(ERROR_SUCCESS != MyCreateFile(Name,".raw",&hRawFile)){ fprintf(stderr, "WARNING: Could not create raw file.\n"); } if(ERROR_SUCCESS == MyCreateFile(Name,".log", &hLogFile)){ fprintf(stdout,"Logging per-packet data to %s.log.\n",Name); } else{ fprintf(stderr, "WARNING: Could not create log file.\n"); }
if(ERROR_SUCCESS == MyCreateFile(Name, ".sta", &hStatFile)){ fprintf(stdout,"Writing statistics sumary to %s.sta\n",Name); } else{ fprintf(stderr,"Could not create statistics file.\n"); } } // SetupLogs()
VOID SetupSockets() { struct hostent *addr; ULONG addr_tmp; char szAddr[MAX_STRING]; int dwAddrSize, dwError;
// Set address and port parameters
if(trans) { bzero((char *)&sinhim, sizeof(sinhim)); if (atoi(host) > 0 ) { sinhim.sin_family = AF_INET; sinhim.sin_addr.s_addr = inet_addr(host); } else{ if ((addr=gethostbyname(host)) == NULL){ printf("ERROR: bad hostname\n"); WSACleanup(); exit(1); } sinhim.sin_family = addr->h_addrtype; bcopy(addr->h_addr,(char*)&addr_tmp, addr->h_length); sinhim.sin_addr.s_addr = addr_tmp; }
sinhim.sin_port = htons(port); sinme.sin_port = 0; /* free choice */ } else{ sinme.sin_port = htons(port); }
sinme.sin_family = AF_INET;
// Open socket for QoS traffic
fd = OpenQoSSocket();
if((fd == (UINT_PTR)NULL) || (fd == INVALID_SOCKET)){ fprintf(stderr,"Failed to open QoS socket!\n"); exit(1); }
// Prepare to get QoS notifications
if((QoSEvents = WSACreateEvent()) == WSA_INVALID_EVENT){ fprintf(stderr, "Failed to create an event for QoS notifications %ld\n", WSAGetLastError()); exit(1); }
if(WSAEventSelect(fd, QoSEvents, FD_QOS) == SOCKET_ERROR){ fprintf(stderr, "Unable to get notifications for QoS events. %ld\n", WSAGetLastError()); }
if(trans){ // Set QoS on sending traffic
if(SetQoSSocket(fd, TRUE)){ exit(1); }
fprintf(stdout, "Initiated QoS connection. Waiting for receiver.\n");
WaitForQoS(SENDER, fd); } else{ // we're the receiver, so bind and wait
if(bind(fd, (PSOCKADDR)&sinme, sizeof(sinme)) < 0){ printf("bind() failed: %ld\n", GetLastError( )); }
if(SetQoSSocket(fd, FALSE)){ exit(1); }
fprintf(stdout, "Waiting for QoS sender to initiate QoS connection.\n");
WaitForQoS(RECEIVER, fd); }
// set some options
// none to set!
g_params.hSocket = (HANDLE)fd; } // SetupSockets()
SOCKET OpenQoSSocket( ) { INT bufferSize = 0; INT numProtocols; LPWSAPROTOCOL_INFO installedProtocols, qosProtocol; INT i; SOCKET fd; BOOLEAN QoSInstalled = FALSE;
// Call WSAEnumProtocols to determine buffer size required
numProtocols = WSAEnumProtocols(NULL, NULL, &bufferSize);
if((numProtocols != SOCKET_ERROR) && (WSAGetLastError() != WSAENOBUFS)){ printf("Failed to enumerate protocols!\n"); return((UINT_PTR)NULL); } else{ // Enumerate the protocols, find the QoS enabled one
installedProtocols = (LPWSAPROTOCOL_INFO)malloc(bufferSize);
numProtocols = WSAEnumProtocols(NULL, (LPVOID)installedProtocols, &bufferSize);
if(numProtocols == SOCKET_ERROR){ printf("Failed to enumerate protocols!\n"); return((UINT_PTR)NULL); } else{ qosProtocol = installedProtocols;
for(i=0; i<numProtocols; i++){ if((qosProtocol->dwServiceFlags1 & XP1_QOS_SUPPORTED)&& (qosProtocol->dwServiceFlags1 & XP1_CONNECTIONLESS) && (qosProtocol->iAddressFamily == AF_INET)){ QoSInstalled = TRUE; break; } qosProtocol++; } }
// Now open the socket.
if (!QoSInstalled) { fprintf(stderr,"ERROR: No QoS protocols installed on this machine\n"); exit(1); }
fd = WSASocket(0, SOCK_DGRAM, 0, qosProtocol, 0, WSA_FLAG_OVERLAPPED);
free(installedProtocols);
return(fd); } } // OpenQoSSocket()
INT SetQoSSocket( SOCKET fd, BOOL Sending) { QOS qos; INT status; LPFLOWSPEC flowSpec; INT dummy;
INT receiverServiceType = Sending? SERVICETYPE_NOTRAFFIC: g_params.dwServiceType;
qos.ProviderSpecific.len = 0; qos.ProviderSpecific.buf = 0;
// receiving flowspec is either NO_TRAFFIC (on a sender) or all
// defaults except for the service type (on a receiver)
flowSpec = &qos.ReceivingFlowspec;
flowSpec->TokenRate = QOS_NOT_SPECIFIED; flowSpec->TokenBucketSize = QOS_NOT_SPECIFIED; flowSpec->PeakBandwidth = QOS_NOT_SPECIFIED; flowSpec->Latency = QOS_NOT_SPECIFIED; flowSpec->DelayVariation = QOS_NOT_SPECIFIED; flowSpec->ServiceType = receiverServiceType; flowSpec->MaxSduSize = QOS_NOT_SPECIFIED; flowSpec->MinimumPolicedSize = QOS_NOT_SPECIFIED;
// now do the sending flowspec
flowSpec = &qos.SendingFlowspec;
if(Sending){ if (g_params.RateInBytes) flowSpec->TokenRate = g_params.TokenRate; else flowSpec->TokenRate = g_params.TokenRate * 1000; flowSpec->TokenBucketSize = g_params.BucketSize;
if (g_params.ForceShape) { if (g_params.RateInBytes) flowSpec->PeakBandwidth = g_params.TokenRate; else flowSpec->PeakBandwidth = g_params.TokenRate * 1000; } else flowSpec->PeakBandwidth = QOS_NOT_SPECIFIED; flowSpec->Latency = QOS_NOT_SPECIFIED; flowSpec->DelayVariation = QOS_NOT_SPECIFIED; flowSpec->ServiceType = g_params.dwServiceType; if (g_params.ForceShape && flowSpec->ServiceType == SERVICETYPE_BESTEFFORT ) flowSpec->ServiceType = SERVICETYPE_GUARANTEED | SERVICE_NO_QOS_SIGNALING;
flowSpec->MaxSduSize = g_params.MaxSDUSize; flowSpec->MinimumPolicedSize = g_params.MinPolicedSize;
printf("Sending Flowspec\n"); PrintFlowspec(&qos.SendingFlowspec); status = WSAConnect(fd, (PSOCKADDR)&sinhim, sizeof(sinhim), NULL, NULL, &qos, NULL); if(status){ printf("SetQoS failed on socket: %ld\n", WSAGetLastError()); } } else{ flowSpec->TokenRate = QOS_NOT_SPECIFIED; flowSpec->TokenBucketSize = QOS_NOT_SPECIFIED; flowSpec->PeakBandwidth = QOS_NOT_SPECIFIED; flowSpec->Latency = QOS_NOT_SPECIFIED; flowSpec->DelayVariation = QOS_NOT_SPECIFIED; flowSpec->ServiceType = SERVICETYPE_NOTRAFFIC; flowSpec->MaxSduSize = QOS_NOT_SPECIFIED; flowSpec->MinimumPolicedSize = QOS_NOT_SPECIFIED;
status = WSAIoctl(fd, SIO_SET_QOS, &qos, sizeof(QOS), NULL, 0, &dummy, NULL, NULL); if(status){ printf("SetQoS failed on socket: %ld\n", WSAGetLastError()); } } return(status); } // SetQoSSocket()
VOID WaitForQoS( BOOL Sender, SOCKET fd) { ULONG status;
if(!g_params.Wait){ // For best effort, we don't do anything QoS... Return
// right away. In this case, the sender should be started
// after the reciever, since there is no synchronization
// via rsvp and data could be missed.
fprintf(stdout, "WARNING: Not waiting for QoS reservation.\n"); return; } while(TRUE){ // get the statuscode, waiting for as long as it takes
status = GetRsvpStatus(WSA_INFINITE,fd);
switch (status) { case WSA_QOS_RECEIVERS: // at least one RESV has arrived
if (Sender) fprintf(stdout, "QoS reservation installed for %s service.\n", g_params.szServiceType); break; case WSA_QOS_SENDERS: // at least one PATH has arrived
if (!Sender) fprintf(stdout, "QoS sender detected using %s service.\n", g_params.szServiceType); break; default: PrintRSVPStatus(status); break; }
// if we received one of the coveted status codes, break out
// altogether. otherwise wait and see if we get another batch
// of indications.
if( ((status == WSA_QOS_RECEIVERS) && Sender) || ((status == WSA_QOS_SENDERS) && !Sender) ) { break; } } } // WaitForQoS()
ULONG GetRsvpStatus( DWORD dwTimeout, SOCKET fd) { LPQOS qos; UCHAR qosBuffer[500]; LPRSVP_STATUS_INFO rsvpStatus; INT bytesReturned; qos = (LPQOS)qosBuffer; qos->ProviderSpecific.len = sizeof(RSVP_STATUS_INFO); qos->ProviderSpecific.buf = (PUCHAR)(qos+1); // wait for notification that a QoS event has occured
WSAWaitForMultipleEvents(1, &QoSEvents, FALSE, dwTimeout, TRUE);
// loop through all qos events
WSAIoctl(fd, SIO_GET_QOS, NULL, 0, qosBuffer, sizeof(qosBuffer), &bytesReturned, NULL, NULL);
rsvpStatus = (LPRSVP_STATUS_INFO)qos->ProviderSpecific.buf; return rsvpStatus->StatusCode; } // GetRsvpStatus
VOID PrintRSVPStatus(ULONG code) { switch (code) { case WSA_QOS_RECEIVERS: // at least one RESV has arrived
printf("WSA_QOS_RECEIVERS\n"); break; case WSA_QOS_SENDERS: // at least one PATH has arrived
printf("WSA_QOS_SENDERS\n"); break; case WSA_QOS_REQUEST_CONFIRMED: // Reserve has been confirmed
printf("WSA_QOS_REQUEST_CONFIRMED\n"); break; case WSA_QOS_ADMISSION_FAILURE: // error due to lack of resources
printf("WSA_QOS_ADMISSION_FAILURE\n"); break; case WSA_QOS_POLICY_FAILURE: // rejected for admin reasons
printf("WSA_QOS_POLICY_FAILURE\n"); break; case WSA_QOS_BAD_STYLE: // unknown or conflicting style
printf("WSA_QOS_BAD_STYLE\n"); break; case WSA_QOS_BAD_OBJECT: // problem with some part of the
// filterspec/providerspecific
// buffer in general
printf("WSA_QOS_BAD_OBJECT\n"); break; case WSA_QOS_TRAFFIC_CTRL_ERROR: // problem with some part of the
// flowspec
printf("WSA_QOS_TRAFFIC_CTRL_ERROR\n"); break; case WSA_QOS_GENERIC_ERROR: // general error
printf("WSA_QOS_GENERIC_ERROR\n"); break; default: printf("Unknown RSVP StatusCode %lu\n", code); break; } } // PrintRSVPStatus
VOID DoTransmit() { IOREQ IOReq[MAX_PENDING_IO_REQS] = { 0 }; INT i; BOOL ret; BOOL fOk;
g_state.nBytesTransferred = 0; g_state.nBuffersSent = 0; g_state.nWritesInProgress = 0;
// fill up the initial buffers and send them on their way
for (i=0; i<MAX_PENDING_IO_REQS; i++) { IOReq[i].pBuffer = malloc(g_params.buflen); FillBuffer(IOReq[i].pBuffer,g_params.buflen); TimeStamp(IOReq[i].pBuffer,g_params.buflen); IOReq[i].Overlapped.Internal = 0; IOReq[i].Overlapped.InternalHigh = 0; IOReq[i].Overlapped.Offset = 0; IOReq[i].Overlapped.OffsetHigh = 0; IOReq[i].Overlapped.hEvent = NULL;
if (g_state.nBuffersSent < totalBuffers) { WriteFileEx(g_params.hSocket, IOReq[i].pBuffer, g_params.buflen, &IOReq[i].Overlapped, TransmitCompletionRoutine);
g_state.nWritesInProgress++; g_state.nBuffersSent++; } }
// now loop until an error happens or we're done writing to the socket
while (g_state.nWritesInProgress > 0) { SleepEx(INFINITE, TRUE); }
// send the end of transmission delimiters
for (i=0; i<MAX_PENDING_IO_REQS; i++) { strncpy(IOReq[i].pBuffer,TheEnd,strlen(TheEnd)); fOk = WriteFileEx(g_params.hSocket, IOReq[i].pBuffer, strlen(TheEnd), &IOReq[i].Overlapped, DelimiterSendCompletion); g_state.nWritesInProgress++;
if (!fOk) { printf("WriteFileEx() failed: %lu\n",GetLastError()); }
}
// wait for all the delimiters to be sent
while (g_state.nWritesInProgress > 0) { SleepEx(INFINITE, TRUE); }
// free up the used memory
for (i=0; i<MAX_PENDING_IO_REQS; i++) { free(IOReq[i].pBuffer); } } // DoTransmit()
VOID WINAPI TransmitCompletionRoutine( DWORD dwErrorCode, DWORD dwNumberOfBytesTransferred, LPOVERLAPPED pOverlapped) { PIOREQ pIOReq = (PIOREQ) pOverlapped; BOOL fOk; if (dwErrorCode == ERROR_REQUEST_ABORTED) { g_state.Done = TRUE; } else if (dwErrorCode != NO_ERROR) { printf("ERROR: Write completed abnormally: %u\n",dwErrorCode); }
g_state.nWritesInProgress--; g_state.nBytesTransferred += dwNumberOfBytesTransferred;
// check to make sure we're not done
if (g_state.Done) return;
// give some indication of life
if(!(g_state.nBuffersSent % 100)){ fprintf(stdout, "."); }
// if there are more buffers to go, send one
if (g_state.nBuffersSent < totalBuffers || g_params.RunForever) { // see if this was the last of the calibration buffers (if we want confirmation)
if (g_params.SkipConfirm == FALSE) { if (g_params.calibration && (g_state.nBuffersSent == g_params.calibration)) { printf("\nCalibration complete. Type 'c' to continue.\n"); while(TRUE){ if(getchar() == 'c'){ break; } } } } // fill in the buffer with new values
FillBuffer(pIOReq->pBuffer,g_params.buflen); TimeStamp(pIOReq->pBuffer,g_params.buflen);
// send a request to write the new buffer
fOk = WriteFileEx(g_params.hSocket, pIOReq->pBuffer, g_params.buflen, pOverlapped, TransmitCompletionRoutine);
if (!fOk) { printf("WriteFileEx() failed: %lu\n",GetLastError()); }
g_state.nWritesInProgress++; g_state.nBuffersSent++; } } // TransmitCompletionRoutine()
VOID WINAPI DelimiterSendCompletion( DWORD dwErrorCode, DWORD dwNumberOfBytesTransferred, LPOVERLAPPED pOverlapped) { g_state.nWritesInProgress--; } // DelimiterSendCompletion()
VOID FillBuffer( CHAR *Cp, INT Cnt) { PBUFFER_FORMAT buf = (PBUFFER_FORMAT) Cp; CHAR c = 0; // Fill with a background pattern
if (g_params.RandomFiller) { // incompressible
while(Cnt-- > 0) { c = rand() % 0x5F; c += 0x20; *Cp++ = c; } } else { // compressible
while(Cnt-- > 0){ while(!isprint((c&0x7F))) c++; *Cp++ = (c++&0x7F); } }
buf->TimeSent = -1; buf->TimeReceived = -1; } // FillBuffer()
INT TimeStamp( CHAR *Cp, INT Cnt) { PBUFFER_FORMAT record; LARGE_INTEGER timeSent; INT64 time;
record = (BUFFER_FORMAT *)Cp; // Stamp with length and sequence number
if(Cnt < sizeof(BUFFER_FORMAT)){ printf("ERROR: Buffer length smaller than record size!\n"); return(0); } else{ time = GetUserTime(); record->TimeSentUser = time; record->BufferSize = Cnt; record->SequenceNumber = SequenceNumber++; } return 1; } // TimeStamp()
VOID DoReceive() { IOREQ IOReq[MAX_PENDING_IO_REQS] = { 0 }; INT i; BOOL ret; // set the start state
g_state.Done = FALSE; g_state.nBytesTransferred = 0; g_state.nBuffersReceived = 0; g_state.nReadsInProgress = 0;
// fill up the initial buffers and send them on their way
for (i=0; i<MAX_PENDING_IO_REQS; i++) { IOReq[i].pBuffer = malloc(g_params.buflen); IOReq[i].Overlapped.Internal = 0; IOReq[i].Overlapped.InternalHigh = 0; IOReq[i].Overlapped.Offset = 0; IOReq[i].Overlapped.OffsetHigh = 0; IOReq[i].Overlapped.hEvent = NULL;
if (g_state.nBuffersReceived < totalBuffers) { ReadFileEx(g_params.hSocket, IOReq[i].pBuffer, g_params.buflen, &IOReq[i].Overlapped, RecvCompletionRoutine);
g_state.nReadsInProgress++; } }
InitializeCriticalSection(&g_csLogRecord);
// now loop until an error happens or we're done writing to the socket
while ((g_state.nReadsInProgress > 0) && !g_state.Done) { SleepEx(5000, TRUE); if (g_state.Done) break; } DeleteCriticalSection(&g_csLogRecord);
// cancel the other pending reads
CancelIo(g_params.hSocket);
// free up the used memory
for (i=0; i<MAX_PENDING_IO_REQS; i++) { free(IOReq[i].pBuffer); } } // DoReceive()
VOID WINAPI RecvCompletionRoutine( DWORD dwErrorCode, DWORD dwNumberOfBytesTransferred, LPOVERLAPPED pOverlapped) { PIOREQ pIOReq = (PIOREQ) pOverlapped; BOOL fOk; static BOOL fLastWasError = FALSE;
g_state.nReadsInProgress--; g_state.nBytesTransferred += dwNumberOfBytesTransferred;
if (dwNumberOfBytesTransferred == 0) { // an error occurred
if (!fLastWasError) { printf("ERROR in RecvCompletionRoutine: code=%d, lasterr=%d\n", dwErrorCode, GetLastError()); printf("\tReceived no data. Telling sender to abort...\n"); SendControlMessage(g_sockControl, MSGST_ERROR); } fLastWasError = TRUE; } else fLastWasError = FALSE;
// if this is the first packet we've received, save the system time
if (g_state.nBuffersReceived == 0) { GetSystemTime(&systimeStart); }
// give some indication of life
if(!(g_state.nBuffersReceived % 100)){ fprintf(stdout, "."); }
// end of transmission delimiter? if so, set the total buffers to the number got
if(!(strncmp(pIOReq->pBuffer, TheEnd, 6))) { totalBuffers = g_state.nBuffersReceived; g_state.Done = TRUE; }
// check to see if someone's set our done flag (if they have, leave)
if (g_state.Done) return;
// if not, then the buffer should hold a scheduling record.
if(dwNumberOfBytesTransferred>0 && dwNumberOfBytesTransferred <= sizeof(BUFFER_FORMAT)) { printf("Buffer too small for scheduling record\n"); printf("\tOnly %d bytes read.\n", dwNumberOfBytesTransferred); }
// Log the record, but don't log more than one at a time (lock on this call)
if (dwNumberOfBytesTransferred >= sizeof(BUFFER_FORMAT) && g_state.nBuffersReceived % g_params.LoggingPeriod == 0) { EnterCriticalSection(&g_csLogRecord); LogRecord(pIOReq->pBuffer); LeaveCriticalSection(&g_csLogRecord); }
// if there are more buffers (or if we don't know how many are coming), ask for one
if ((g_state.nBuffersReceived < totalBuffers) || g_params.nBufUnspecified) { // send a request to read the next buffer
fOk = ReadFileEx(g_params.hSocket, pIOReq->pBuffer, g_params.buflen, pOverlapped, RecvCompletionRoutine);
if (!fOk) { printf("ReadFileEx() failed: %lu\n",GetLastError()); } g_state.nReadsInProgress++; g_state.nBuffersReceived++; } } // RecvCompletionRoutine()
void LogRecord(char * Buffer) { // This function copies the recieved record to the scheduling array.
// The contents of the array are processed and written to file once
// reception is complete.
PBUFFER_FORMAT inRecord = (PBUFFER_FORMAT)Buffer; LOG_RECORD outRecord; INT64 time; SYSTEMTIME CurrentTime;
time = GetUserTime();
outRecord.TimeSentUser = inRecord->TimeSentUser; outRecord.TimeReceivedUser = time; outRecord.TimeSent = inRecord->TimeSent; outRecord.TimeReceived = inRecord->TimeReceived; outRecord.BufferSize = inRecord->BufferSize; outRecord.SequenceNumber = inRecord->SequenceNumber;
if (inRecord->TimeSent == -1) { outRecord.TimeSent = outRecord.TimeSentUser; g_params.NoSenderTimestamps = TRUE; }
if (inRecord->TimeReceived == -1) { outRecord.TimeReceived = outRecord.TimeReceivedUser; g_params.NoReceiverTimestamps = TRUE; }
if(g_params.UserStamps){ outRecord.TimeSent = outRecord.TimeSentUser; outRecord.TimeReceived = outRecord.TimeReceivedUser; } outRecord.Latency = outRecord.TimeReceived - outRecord.TimeSent;
AddLogEntry(&g_log, &outRecord);
if(g_params.PrintDrops){ if(inRecord->SequenceNumber != LastSequenceNumber + g_params.LoggingPeriod){ GetLocalTime(&CurrentTime);
printf("\n%4d/%02d/%02d %02d:%02d:%02d:%04d: ", CurrentTime.wYear, CurrentTime.wMonth, CurrentTime.wDay, CurrentTime.wHour, CurrentTime.wMinute, CurrentTime.wSecond, CurrentTime.wMilliseconds);
printf("Dropped %d packets after packet %d.\n", inRecord->SequenceNumber - LastSequenceNumber, LastSequenceNumber); }
LastSequenceNumber = inRecord->SequenceNumber; } return; } // LogRecord()
BOOL CreateLog(PLOG plog, INT64 c) { // sets up a log structure that can hold c entries
char szTempFile[MAX_PATH]; char szTempPath[MAX_PATH]; SYSTEM_INFO si; DWORD dwFileSizeHigh; DWORD dwFileSizeLow; INT64 qwFileSize;
// get some system info
GetSystemInfo(&si); // allocate logging array
plog->nBuffersLogged = 0; plog->pbMapView = NULL; plog->qwMapViewOffset = -1;
// set up the temporary storage file for logging
GetTempPath(MAX_PATH, szTempPath); GetTempFileName(szTempPath, "qtc", 0, szTempFile); plog->szStorageFile = malloc(strlen(szTempFile) + 1); strcpy(plog->szStorageFile, szTempFile); plog->hStorageFile = CreateFile(szTempFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL); if (plog->hStorageFile == INVALID_HANDLE_VALUE) ErrorExit("Could not create temp storage file",GetLastError());
// create the memory mapping kernel object
qwFileSize = c * sizeof(LOG_RECORD); dwFileSizeHigh = (DWORD) (qwFileSize >> 32); dwFileSizeLow = (DWORD) (qwFileSize & 0xFFFFFFFF); plog->qwFileSize = qwFileSize; plog->hFileMapping = CreateFileMapping(plog->hStorageFile, NULL, PAGE_READWRITE, dwFileSizeHigh,dwFileSizeLow,NULL); if (plog->hFileMapping == NULL) ErrorExit("Could not create mapping for temp storage file",GetLastError()); return TRUE; }
BOOL DestroyLog(PLOG plog) { DWORD dwError; // destroys the log and all associated data
dwError = CloseHandle(plog->hFileMapping); if (!dwError) printf("Error in DestroyLog:CloseHandle(FileMapping) %d\n",GetLastError()); dwError = CloseHandle(plog->hStorageFile); if (!dwError) printf("Error in DestroyLog:CloseHandle(StorageFile) %d\n",GetLastError()); dwError = UnmapViewOfFile(plog->pbMapView); if (!dwError) printf("Error in DestroyLog:UnmapViewOfFile(plog->pbMapView) %d\n",GetLastError()); dwError = DeleteFile(plog->szStorageFile); if (!dwError) printf("Error in DestroyLog:DeleteFile(StroageFile) %d\n",GetLastError()); free(plog->szStorageFile); return FALSE; }
void PrintLogRecord(PLOG_RECORD prec) { char szBuf[MAX_STRING];
sprintf(szBuf,"%d: %I64u - %I64u (%I64d)", prec->SequenceNumber,prec->TimeSent,prec->TimeReceived,prec->Latency); puts(szBuf); }
BOOL ExtendLog(PLOG plog) { // makes the log bigger by some fixed constant
HANDLE hNewFileMapping; INT64 qwNewFileSize; UnmapViewOfFile(plog->pbMapView);
qwNewFileSize = plog->qwFileSize + g_si.dwAllocationGranularity * sizeof(LOG_RECORD); hNewFileMapping = CreateFileMapping(plog->hStorageFile, NULL, PAGE_READWRITE, (DWORD)(qwNewFileSize >> 32), (DWORD)(qwNewFileSize & 0xFFFFFFFF), NULL); if (hNewFileMapping == NULL) { ErrorExit("Could not create mapping for temp storage file",GetLastError()); return FALSE; } plog->qwFileSize = qwNewFileSize; CloseHandle(plog->hFileMapping); plog->hFileMapping = hNewFileMapping; plog->qwMapViewOffset = -1; return TRUE; }
BOOL GetLogEntry(PLOG plog, PLOG_RECORD prec, INT64 i) { // fills prec with the (0 indexed) i'th log in plog
// returns TRUE if it was successful, FALSE otherwise
INT64 qwT; PLOG_RECORD entry;
// first, check to see if this is within the range of our file
if ((INT64)((i+1)*sizeof(LOG_RECORD)) > plog->qwFileSize) { // too high, so we return false
return FALSE; } // we have to round down to the nearest allocation boundary
qwT = sizeof(LOG_RECORD) * i; // offset within file
qwT /= g_si.dwAllocationGranularity; // in allocation granularity units
// check to see if we do not already have this mapped in memory
if (plog->qwMapViewOffset != qwT * g_si.dwAllocationGranularity) { if (plog->pbMapView != NULL) UnmapViewOfFile(plog->pbMapView); plog->qwMapViewOffset = qwT * g_si.dwAllocationGranularity; // offset of lower allocation bound
if (plog->qwFileSize < (INT64)g_si.dwAllocationGranularity) { // file is smaller than allocation granularity
plog->qwMapViewOffset = 0; plog->pbMapView = MapViewOfFile(plog->hFileMapping, FILE_MAP_WRITE, 0, 0, 0); } else if (plog->qwFileSize - plog->qwMapViewOffset < g_si.dwAllocationGranularity) { // we're within an allocation granularity of the end of the file
plog->pbMapView = MapViewOfFile(plog->hFileMapping, FILE_MAP_WRITE, (DWORD)(plog->qwMapViewOffset >> 32), (DWORD)(plog->qwMapViewOffset & 0xFFFFFFFF), (DWORD)(plog->qwFileSize - plog->qwMapViewOffset)); } else { // we're just somewhere in the file with space around us
plog->pbMapView = MapViewOfFile(plog->hFileMapping, FILE_MAP_WRITE, (DWORD)(plog->qwMapViewOffset >> 32), (DWORD)(plog->qwMapViewOffset & 0xFFFFFFFF), RoundUp(g_si.dwAllocationGranularity,sizeof(LOG_RECORD))); } if (plog->pbMapView == NULL) ErrorExit("GetLogEntry could not MapViewOfFile",GetLastError()); } qwT = sizeof(LOG_RECORD) * i; entry = (PLOG_RECORD)(plog->pbMapView + (qwT - plog->qwMapViewOffset)); CopyMemory(prec, entry, sizeof(LOG_RECORD)); return TRUE; }
BOOL SetLogEntry(PLOG plog, PLOG_RECORD prec, INT64 i) { // fills log entry i with the data pointed to by prec
// returns TRUE if it was successful, FALSE otherwise
INT64 qwT; PLOG_RECORD entry;
// first, check to see if this is within the range of our file
if ((INT64)((i+1)*sizeof(LOG_RECORD)) > plog->qwFileSize) { // we need to make our mapping bigger
ExtendLog(plog); } // we have to round down to the nearest allocation boundary
qwT = sizeof(LOG_RECORD) * i; // offset within file
qwT /= g_si.dwAllocationGranularity; // in allocation granularity units
// check to see if we do not already have this mapped in memory
if (plog->qwMapViewOffset != qwT * g_si.dwAllocationGranularity) { if (plog->pbMapView != NULL) UnmapViewOfFile(plog->pbMapView); plog->qwMapViewOffset = qwT * g_si.dwAllocationGranularity; // offset of lower allocation bound
if (plog->qwFileSize < (INT64)g_si.dwAllocationGranularity) { // file is smaller than allocation granularity
plog->qwMapViewOffset = 0; plog->pbMapView = MapViewOfFile(plog->hFileMapping, FILE_MAP_WRITE, 0, 0, 0); } else if (plog->qwFileSize - plog->qwMapViewOffset < g_si.dwAllocationGranularity) { // we're within an allocation granularity of the end of the file
plog->pbMapView = MapViewOfFile(plog->hFileMapping, FILE_MAP_WRITE, (DWORD)(plog->qwMapViewOffset >> 32), (DWORD)(plog->qwMapViewOffset & 0xFFFFFFFF), (DWORD)(plog->qwFileSize - plog->qwMapViewOffset)); } else { // we're just somewhere in the file with space around us
plog->pbMapView = MapViewOfFile(plog->hFileMapping, FILE_MAP_WRITE, (DWORD)(plog->qwMapViewOffset >> 32), (DWORD)(plog->qwMapViewOffset & 0xFFFFFFFF), RoundUp(g_si.dwAllocationGranularity,sizeof(LOG_RECORD))); } if (plog->pbMapView == NULL) ErrorExit("SetLogEntry could not MapViewOfFile",GetLastError()); } qwT = sizeof(LOG_RECORD) * i; entry = (PLOG_RECORD)(plog->pbMapView + (qwT - plog->qwMapViewOffset));
CopyMemory(entry, prec, sizeof(LOG_RECORD)); return TRUE; }
BOOL AddLogEntry(PLOG plog, PLOG_RECORD prec) { PLOG_RECORD entry; // adds the data pointed to by prec to the end of the log
// returns TRUE if it was successful, FALSE otherwise
SetLogEntry(plog, prec, plog->nBuffersLogged);
plog->nBuffersLogged++;
return TRUE; }
UINT64 GetUserTime() { // This function returns the performance counter time in units of 100ns
LARGE_INTEGER count, freq;
NtQueryPerformanceCounter(&count,&freq); // make sure we have hardware performance counting
if(freq.QuadPart == 0) { NtQuerySystemTime(&count); return (UINT64)count.QuadPart; } return (UINT64)((10000000 * count.QuadPart) / freq.QuadPart); } // GetUserTime()
UINT64 GetBadHalAdjustment() { // this function returns the amount the hal timer in a machine with
// an intel chipset with the piix4 timer chip will jump forward in the case of
// repeated garbage returned fom the piix4 (bug #347410) so we can correct it out
// in the FixWackyTimestamps routine
LARGE_INTEGER freq; UINT64 diff;
QueryPerformanceFrequency(&freq); // so we want to find how much it is increased in 100ns intervals if we increase
// byte 3 by 1.
diff = 0x01000000; diff *= 10000000; diff /= (UINT64)freq.QuadPart; return diff; }
DWORD MyCreateFile( IN PCHAR Name, IN PCHAR Extension, OUT HANDLE *File) { HANDLE hFile; UCHAR * fileName;
fileName = malloc(strlen(Name) + 5); bzero(fileName,strlen(Name) + 5); strncpy(fileName, Name, strlen(Name)); if (strlen(Extension)==4) { strcat(fileName,Extension); } else return !ERROR_SUCCESS;
hFile = CreateFile(fileName, GENERIC_WRITE | GENERIC_READ, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
*File = hFile;
return(INVALID_HANDLE_VALUE == hFile ? (!(ERROR_SUCCESS)) : ERROR_SUCCESS); } // MyCreateFile()
void AggregateStats() { // this will go through the directory specified in Name and aggregate stats from
// all the .sta files therein. it will then output the results of the aggregation
// in a file within that directory called stats.qtc
char szDirPath[3 * MAX_PATH]; char szSearchString[3 * MAX_PATH]; WIN32_FIND_DATA FileData; // Data structure describes the file found
HANDLE hSearch; // Search handle returned by FindFirstFile
PCHAR rgszStaFiles[1000]; // an array of the names of the .sta files
int cStaFiles = 0, i,j,k,l; // keeps track of how many of the .sta files there are
STATS * pStats; int rgSizes[1000], cSizes = 0; int rgRates[1000], cRates = 0; char szAggFile[3 * MAX_PATH]; char szLineBuf[1000]; STATS statsT; FILE *pfile; FILETIME rgtime[1000]; SYSTEMTIME st; ULARGE_INTEGER uliT; int ctime = 0; int cSpecs = 0;
PathCanonicalize(szDirPath,Name); if (szDirPath[strlen(szDirPath) - 1] == '"') szDirPath[strlen(szDirPath) - 1] = 0; if (!PathIsDirectory(szDirPath)) { printf("Path (%s) is not a directory\n",szDirPath); ErrorExit("Invalid Path for aggregate stats", -1); }
// so now szDirPath is the path to the directory we want to go through
// and we begin our search for .sta files
sprintf(szSearchString,"%s\\*.sta",szDirPath); hSearch = FindFirstFile (szSearchString, &FileData); if (hSearch == INVALID_HANDLE_VALUE) { ErrorExit("No .sta files found.",GetLastError()); } do { rgszStaFiles[cStaFiles] = malloc(sizeof(char) * 3 * MAX_PATH); // check to see if it's a good .sta file
sprintf(statsT.szStaFile,"%s\\%s", szDirPath, FileData.cFileName); if (GetStatsFromFile(&statsT)) { // if it's good, include it
strcpy(rgszStaFiles[cStaFiles], FileData.cFileName); cStaFiles++; } } while (FindNextFile(hSearch, &FileData)); if (GetLastError() != ERROR_NO_MORE_FILES) { ErrorExit("Problem in FindNextFile()",GetLastError()); }
// open the stats file
sprintf(szAggFile,"%s\\stats.qtc",szDirPath); pfile = fopen(szAggFile,"w+"); if (pfile == NULL) printf("Could not open file for aggregate stats: %s\n",szAggFile); pStats = malloc(cStaFiles * sizeof(STATS)); ZeroMemory(pStats, cStaFiles * sizeof(STATS)); for (i=0; i<cStaFiles; i++) { sprintf(pStats[i].szStaFile, "%s\\%s", szDirPath, rgszStaFiles[i]); GetStatsFromFile(&(pStats[i])); }
// at this point our pStats array is loaded up, so we can go to work
for (i=0; i<cStaFiles; i++) { rgSizes[i] = pStats[i].nBytesPerBuffer; rgRates[i] = pStats[i].nTokenRate; rgtime[i] = pStats[i].time; }
// now sort them and get out the dupliates
cSizes = cRates = ctime = cStaFiles; RemoveDuplicates(rgSizes, &cSizes); RemoveDuplicates(rgRates, &cRates); RemoveDuplicatesI64((INT64 *)rgtime, &ctime); // --- do the stats by by time ---
fprintf(pfile, "Latency Characteristics at varying times\n"); fprintf(pfile, " Latency Characteristics (microseconds) Rates (Bps) Buffers\n"); fprintf(pfile, " Time (UTC) Median StDev Mean Skew Kurt Send Receive Received Dropped\n"); for (i=0; i<cRates; i++) { for (j=0; j<cSizes; j++) { // print the flowspec
if (IndexOfStatRecWith(rgRates[i],rgSizes[j],-1,pStats,cStaFiles) != -1) { fprintf(pfile, "FLOWSPEC %d: %dB buffers at %d Bps\n", cSpecs++, rgSizes[j], rgRates[i]); for (k=0; k<ctime; k++) { // check to see if there is something with these params and print it
ZeroMemory(&uliT, sizeof(ULARGE_INTEGER)); CopyMemory(&uliT, &rgtime[k], sizeof(ULARGE_INTEGER)); l = IndexOfStatRecWith(rgRates[i],rgSizes[j],uliT.QuadPart,pStats,cStaFiles); if (l > 0) { FileTimeToSystemTime(&pStats[l].time, &st); fprintf(pfile,"%02hu/%02hu/%04hu %2hu:%02hu.%02hu.%03hu: %10.1lf %10.1lf %10.1lf %8.2lf %8.2lf %10.1lf %10.1lf %10d %10d\n", st.wMonth, st.wDay, st.wYear, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, pStats[l].median, sqrt((double)pStats[l].var), pStats[l].mean, pStats[l].skew, pStats[l].kurt, pStats[l].sendrate, pStats[l].recvrate, pStats[l].nBuffers, pStats[l].nDrops); } } fprintf(pfile,"\n"); } } }
fprintf(pfile, "Latency Characteristics by flowspec\n"); // --- do the stats by flowspec ---
// now write the file, line by line, to szLineBuf, then to the file
// median
fprintf(pfile,"Median Latency (microseconds)\n"); fprintf(pfile," "); for (i=0; i<cSizes; i++) fprintf(pfile,"%9dB ",rgSizes[i]); fprintf(pfile,"\n"); for (i=0; i<cRates; i++) { fprintf(pfile,"%7dBps ",rgRates[i]); for (j=0; j<cSizes; j++) { k = IndexOfStatRecWith(rgRates[i],rgSizes[j],-1,pStats,cStaFiles); if (k != -1) { fprintf(pfile,"%10.1lf ",pStats[k].median); } else { fprintf(pfile," "); } } fprintf(pfile,"\n"); } fprintf(pfile,"\n"); // mean
fprintf(pfile,"Mean Latency (microseconds)\n"); fprintf(pfile," "); for (i=0; i<cSizes; i++) fprintf(pfile,"%9dB ",rgSizes[i]); fprintf(pfile,"\n"); for (i=0; i<cRates; i++) { fprintf(pfile,"%7dBps ",rgRates[i]); for (j=0; j<cSizes; j++) { k = IndexOfStatRecWith(rgRates[i],rgSizes[j],-1,pStats,cStaFiles); if (k != -1) { fprintf(pfile,"%10.2lf ",pStats[k].mean); } else { fprintf(pfile," "); } } fprintf(pfile,"\n"); } fprintf(pfile,"\n");
// variance
fprintf(pfile,"Latency Standard Deviation\n"); fprintf(pfile," "); for (i=0; i<cSizes; i++) fprintf(pfile,"%9dB ",rgSizes[i]); fprintf(pfile,"\n"); for (i=0; i<cRates; i++) { fprintf(pfile,"%7dBps ",rgRates[i]); for (j=0; j<cSizes; j++) { k = IndexOfStatRecWith(rgRates[i],rgSizes[j],-1,pStats,cStaFiles); if (k != -1) { fprintf(pfile,"%10.2lf ",sqrt((double)pStats[k].var)); } else { fprintf(pfile," "); } } fprintf(pfile,"\n"); } fprintf(pfile,"\n");
// skew
fprintf(pfile,"Latency Skew\n"); fprintf(pfile," "); for (i=0; i<cSizes; i++) fprintf(pfile,"%9dB ",rgSizes[i]); fprintf(pfile,"\n"); for (i=0; i<cRates; i++) { fprintf(pfile,"%7dBps ",rgRates[i]); for (j=0; j<cSizes; j++) { k = IndexOfStatRecWith(rgRates[i],rgSizes[j],-1,pStats,cStaFiles); if (k != -1) { fprintf(pfile,"%10.2lf ",pStats[k].skew); } else { fprintf(pfile," "); } } fprintf(pfile,"\n"); } fprintf(pfile,"\n");
// kurtosis
fprintf(pfile,"Latency Kurtosis\n"); fprintf(pfile," "); for (i=0; i<cSizes; i++) fprintf(pfile,"%9dB ",rgSizes[i]); fprintf(pfile,"\n"); for (i=0; i<cRates; i++) { fprintf(pfile,"%7dBps ",rgRates[i]); for (j=0; j<cSizes; j++) { k = IndexOfStatRecWith(rgRates[i],rgSizes[j],-1,pStats,cStaFiles); if (k != -1) { fprintf(pfile,"%10.2lf ",pStats[k].kurt); } else { fprintf(pfile," "); } } fprintf(pfile,"\n"); } fprintf(pfile,"\n");
// send rate
fprintf(pfile,"Send Rate (Bps)\n"); fprintf(pfile," "); for (i=0; i<cSizes; i++) fprintf(pfile,"%9dB ",rgSizes[i]); fprintf(pfile,"\n"); for (i=0; i<cRates; i++) { fprintf(pfile,"%7dBps ",rgRates[i]); for (j=0; j<cSizes; j++) { k = IndexOfStatRecWith(rgRates[i],rgSizes[j],-1,pStats,cStaFiles); if (k != -1) { fprintf(pfile,"%10.1lf ",pStats[k].sendrate); } else { fprintf(pfile," "); } } fprintf(pfile,"\n"); } fprintf(pfile,"\n");
// recv rate
fprintf(pfile,"Receive Rate (Bps)\n"); fprintf(pfile," "); for (i=0; i<cSizes; i++) fprintf(pfile,"%9dB ",rgSizes[i]); fprintf(pfile,"\n"); for (i=0; i<cRates; i++) { fprintf(pfile,"%7dBps ",rgRates[i]); for (j=0; j<cSizes; j++) { k = IndexOfStatRecWith(rgRates[i],rgSizes[j],-1,pStats,cStaFiles); if (k != -1) { fprintf(pfile,"%10.1lf ",pStats[k].recvrate); } else { fprintf(pfile," "); } } fprintf(pfile,"\n"); } fprintf(pfile,"\n");
// show the file to the screen, just for kicks
rewind(pfile); while (fgets(szLineBuf, 1000, pfile) != NULL) printf("%s", szLineBuf); // we're done, so we free up the memory we used
printf("Saved aggregate stats to %s\n",szAggFile); fclose(pfile); for (i=0; i<cStaFiles; i++) { free(rgszStaFiles[i]); } free(pStats); }
int IndexOfStatRecWith(int rate, int size, INT64 time, PSTATS pStats, int cStats) { // returns an index into pStats that has the requested values for rate and size
// if there are more than one, returns arbitrary match
// returns -1 if no suitable entry is found.
int i; ULARGE_INTEGER uliT;
for (i=0; i<cStats; i++) { if (rate == -1 || pStats[i].nTokenRate == rate) { if (size == -1 || pStats[i].nBytesPerBuffer == size) { CopyMemory(&uliT, &(pStats[i].time), sizeof(ULARGE_INTEGER)); if (time == -1 || uliT.QuadPart == (UINT64)time) { return i; } } } }
return -1; }
BOOL GetStatsFromFile(PSTATS pstats) { // this function gets the overall statistics from the .sta file it's pointed to
// it returns true if successful, false otherwise
PCHAR szBuf = NULL; double T1,T2,T3; int nT1,nT2,nT3,nT4,nT5,nT6; HANDLE hFile; DWORD dwFileSize; DWORD dwRead; int nFields; SYSTEMTIME st;
szBuf = malloc(sizeof(CHAR) * 1000); if (!szBuf) return FALSE; ZeroMemory(szBuf,1000); // open the file
hFile = CreateFile(pstats->szStaFile,GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); dwFileSize = GetFileSize(hFile, NULL); if (dwFileSize == 0) return FALSE; // read the whole file into szBuf
ReadFile(hFile, szBuf, dwFileSize, &dwRead, NULL);
// close the file
CloseHandle(hFile);
// parse the buffer
nFields = sscanf(szBuf, "Sender: %s Receiver: %s\n" \ "First packet received: %hu:%hu.%hu.%hu %hu/%hu/%hu (UTC)\n" \ "Buffer size: %d\tTokenrate: %d\n" \ "Received %d packets.\n" \ "Logged %d records.\n" \ "Received %d bytes in %d milliseconds = %d KBps\n" \ "Clock skew is %lf microseconds per second.\n " \ "\tbased on %d calibration points\n" \ "Overall send rate: %lf Bytes/s\n" \ "Overall recv rate: %lf Bytes/s\n" \ "Latency Statistics (microsecond units): median: %lf\n" \ "\tMean: %lf\tStdev: %lf\tAbDev: %lf\n" \ "\tVariance: %lf\tSkew: %lf\t Kurtosis: %lf \n" \ "Dropped %d packets\n", pstats->szSender, pstats->szReceiver, &(st.wHour), &(st.wMinute), &(st.wSecond), &(st.wMilliseconds), &(st.wDay), &(st.wMonth), &(st.wYear), &(pstats->nBytesPerBuffer), &(pstats->nTokenRate), &(pstats->nBuffers), &nT2, &nT3, &nT4, &nT5, &T1, &nT6, &(pstats->sendrate), &(pstats->recvrate), &(pstats->median), &(pstats->mean),&T2,&(pstats->abdev), &(pstats->var),&(pstats->skew),&(pstats->kurt), &(pstats->nDrops));
if (nFields != 28 && nFields != 27) { // see if they ran without clock skew calc
nFields = sscanf(szBuf, "Sender: %s Receiver: %s\n" \ "First packet received: %hu:%hu.%hu.%hu %hu/%hu/%hu (UTC)\n" \ "Buffer size: %d\tTokenrate: %d\n" \ "Received %d packets.\n" \ "Logged %d records.\n" \ "Received %d bytes in %d milliseconds = %d KBps\n" \ "Overall send rate: %lf Bytes/s\n" \ "Overall recv rate: %lf Bytes/s\n" \ "Latency Statistics (microsecond units): median: %lf\n" \ "\tMean: %lf\tStdev: %lf\tAbDev: %lf\n" \ "\tVariance: %lf\tSkew: %lf\t Kurtosis: %lf \n" \ "Dropped %d packets\n", pstats->szSender, pstats->szReceiver, &(st.wHour), &(st.wMinute), &(st.wSecond), &(st.wMilliseconds), &(st.wDay), &(st.wMonth), &(st.wYear), &(pstats->nBytesPerBuffer), &(pstats->nTokenRate), &nT1, &nT2, &nT3, &nT4, &nT5, &(pstats->sendrate), &(pstats->recvrate), &(pstats->median), &(pstats->mean),&T2,&(pstats->abdev), &(pstats->var),&(pstats->skew),&(pstats->kurt), &(pstats->nDrops)); if (nFields != 26 && nFields != 25) return FALSE; }
// assemble a FILETIME structure from the date & time
if (!SystemTimeToFileTime(&st,&pstats->time)) { return FALSE; }
free(szBuf);
return TRUE; }
VOID DoStatsFromFile() { DOUBLE slope = 0; DOUBLE offset = 0; printf("Logging stats from file.\n"); if (Name == NULL) { fprintf(stderr, "ERROR: you must specify a file to convert\n"); } if(MyCreateFile(Name, ".log", &hLogFile) != ERROR_SUCCESS) { fprintf(stderr, "ERROR: could not create log file\n"); exit(1); } if(OpenRawFile(Name, &hRawFile) != ERROR_SUCCESS) { fprintf(stderr, "ERROR: could not open raw file\n"); exit(1); }
ReadSchedulingRecords(hRawFile); if (g_params.calibration == 0) g_params.calibration = g_log.nBuffersLogged; NormalizeTimeStamps();
// here we check for wacky timestamps on the sender and receiver
if (g_params.SkewFitMode == 3) FixWackyTimestamps(); if (g_params.SkewFitMode) { ClockSkew(&slope, &offset); AdjustForClockSkew(slope,offset); NormalizeTimeStamps(); }
if(hLogFile != INVALID_HANDLE_VALUE) { WriteSchedulingRecords(hLogFile, g_params.Dummy); } printf("Done stats from file.\n"); } // DoStatsFromFile()
DWORD OpenRawFile( IN PCHAR Name, OUT HANDLE *File ) { HANDLE hFile; UCHAR * logName;
logName = malloc(strlen(Name) + 4); strncpy(logName, Name, strlen(Name)); logName[strlen(Name)+0] = '.'; logName[strlen(Name)+1] = 'r'; logName[strlen(Name)+2] = 'a'; logName[strlen(Name)+3] = 'w'; logName[strlen(Name)+4] = (UCHAR)NULL;
hFile = CreateFile(logName, GENERIC_READ, 0, NULL, OPEN_EXISTING , FILE_ATTRIBUTE_NORMAL, NULL); *File = hFile;
return(INVALID_HANDLE_VALUE == hFile ? (!(ERROR_SUCCESS)) : ERROR_SUCCESS); } // OpenRawFile()
INT64 ReadSchedulingRecords(HANDLE File) { char szTempFile[MAX_PATH]; char szTempPath[MAX_PATH]; LOG_RECORD currentRecord; CHAR lineBuf[MAX_STRING]; CHAR nextChar[2] = {0,0}; DWORD readBytes = 0; INT assignedFields;
if (!File || (File == INVALID_HANDLE_VALUE)) { fprintf(stderr,"ERROR: Invalid File\n"); return 0; }
CreateLog(&g_log, 2048); // loop through the file, reading in line after line
do { // get the next line of characters
bzero(lineBuf, MAX_STRING); ZeroMemory(lineBuf, MAX_STRING); do { ReadFile(File,nextChar,1,&readBytes,NULL); if (readBytes == 0) { if (g_log.nBuffersLogged == 0) { fprintf(stderr,"ERROR: no logs read\n"); exit(1); } break; } strcat(lineBuf,nextChar); } while (*nextChar != '\n'); // parse line and add it to the log
assignedFields = sscanf(lineBuf, "%I64u:%I64u:%I64u:%d:%d\n", &(currentRecord.TimeSent), &(currentRecord.TimeReceived), &(currentRecord.Latency), &(currentRecord.BufferSize), &(currentRecord.SequenceNumber)); if ((assignedFields != 5) && (assignedFields != EOF)) printf("ERROR: parsing the log gave bad field assignments on record %d\n", g_log.nBuffersLogged);
if (assignedFields == EOF) break; AddLogEntry(&g_log, ¤tRecord); } while (readBytes != 0);
printf("read %d records\n",g_log.nBuffersLogged); return g_log.nBuffersLogged; // return the number of records read
} // ReadSchedulingRecords()
VOID DoStats() { DOUBLE slope = 0; DOUBLE offset = 0;
GenericStats(); if(!normalize){ if(hRawFile != INVALID_HANDLE_VALUE){ WriteSchedulingRecords(hRawFile, FALSE); } } NormalizeTimeStamps(); if(normalize){ if(hRawFile != INVALID_HANDLE_VALUE){ WriteSchedulingRecords(hRawFile, FALSE); } }
if(!g_params.calibration) { // if we have nothing specified, calibrate on all buffers
g_params.calibration = g_state.nBuffersReceived; } // here we check for wacky timestamps on the sender and receiver
if (g_params.SkewFitMode == 3) FixWackyTimestamps(); if(g_params.SkewFitMode) { ClockSkew(&slope, &offset); AdjustForClockSkew(slope, offset); NormalizeTimeStamps(); }
// we calculate these stats on the normalized / skew adjusted data
AdvancedStats(); CheckForLostPackets(); if(hLogFile != INVALID_HANDLE_VALUE){ WriteSchedulingRecords(hLogFile, g_params.Dummy); } printf("\n"); }
VOID WriteSchedulingRecords( HANDLE File, BOOLEAN InsertDummyRows) { LOG_RECORD scheduleRecord; CHAR formattingBuffer[MAX_STRING]; INT dwWritten; INT64 records = g_log.nBuffersLogged; INT wrote; INT i; INT64 maxLatency = (INT64)0;
if(!File || (File == INVALID_HANDLE_VALUE)){ return; }
while(records){ GetLogEntry(&g_log, &scheduleRecord, g_log.nBuffersLogged - records); ZeroMemory(formattingBuffer,MAX_STRING); wrote = sprintf(formattingBuffer, "%020I64u:%020I64u:%010I64d:%10d:%10d\n", scheduleRecord.TimeSent, scheduleRecord.TimeReceived, scheduleRecord.Latency, scheduleRecord.BufferSize, scheduleRecord.SequenceNumber);
WriteFile(File, formattingBuffer, wrote, &dwWritten, NULL);
records--; } } // WriteSchedulingRecords()
VOID GenericStats() { INT bytesWritten; UCHAR holdingBuffer[MAX_STRING]; INT count;
// say who the sender and receiver are
count = sprintf(holdingBuffer, "Sender: %s Receiver: %s\n",szHisAddr, szMyAddr); WriteStats(holdingBuffer, count); printf("%s",holdingBuffer);
// say when we received the first packet
count = sprintf(holdingBuffer, "First packet received: %02u:%02u.%02u.%03u %02u/%02u/%04u (UTC)\n", systimeStart.wHour, systimeStart.wMinute, systimeStart.wSecond, systimeStart.wMilliseconds, systimeStart.wDay, systimeStart.wMonth, systimeStart.wYear); WriteStats(holdingBuffer, count); printf("%s",holdingBuffer); // write the test params to the .sta file
bzero(holdingBuffer, MAX_STRING); count = _snprintf(holdingBuffer, MAX_STRING -1, "Buffer size: %d\tTokenrate: %d\n", g_params.buflen, g_params.TokenRate); WriteStats(holdingBuffer, count); printf("%s",holdingBuffer); // write some generic results
bzero(holdingBuffer, MAX_STRING); count = _snprintf(holdingBuffer, MAX_STRING-1, // leave room for NULL
"Received %u packets.\n", g_state.nBuffersReceived); WriteStats(holdingBuffer, count); printf("%s",holdingBuffer);
bzero(holdingBuffer, MAX_STRING); count = _snprintf(holdingBuffer, MAX_STRING-1, // leave room for NULL
"Logged %I64u records.\n", g_log.nBuffersLogged); WriteStats(holdingBuffer, count); printf("%s",holdingBuffer);
bzero(holdingBuffer, MAX_STRING); count = _snprintf(holdingBuffer, MAX_STRING-1, // room for NULL
"Received %ld bytes in %I64d milliseconds = %I64d KBps\n", g_state.nBytesTransferred, timeElapsed, g_state.nBytesTransferred/timeElapsed); WriteStats(holdingBuffer, count); printf("%s",holdingBuffer); } // GenericStats()
void AdvancedStats() { // write some more interesting stats to the .sta file
char szBuf[MAX_STRING]; INT64 i,n; int count; INT64 FirstTime,LastTime; double rate, median, mean, var, abdev, skew, kurt, sdev, ep = 0.0, s, p; LOG_RECORD rec; double * sortedLatencies;
// overall send rate
GetLogEntry(&g_log, &rec, 0); FirstTime = rec.TimeSent; GetLogEntry(&g_log, &rec, g_log.nBuffersLogged - 1); LastTime = rec.TimeSent; rate = (rec.SequenceNumber * g_params.buflen)/((double)(LastTime - FirstTime)/10000000.0); count = sprintf(szBuf, "Overall send rate: %.3f Bytes/s\n",rate); WriteStats(szBuf, count); printf("%s",szBuf); GetLogEntry(&g_log, &rec, 0); FirstTime = rec.TimeReceived; GetLogEntry(&g_log, &rec, g_log.nBuffersLogged - 1); LastTime = rec.TimeReceived; rate = (g_state.nBytesTransferred)/((double)(LastTime - FirstTime)/10000000.0); count = sprintf(szBuf, "Overall recv rate: %.3f Bytes/s\n",rate); WriteStats(szBuf, count); printf("%s",szBuf);
// now show mean, variance, avdev, etc of latency.
s = 0.0; n = g_log.nBuffersLogged; sortedLatencies = malloc(sizeof(double) * (UINT)n); for (i=0; i < n; i++) { // first pass, we get mean
GetLogEntry(&g_log, &rec, i); s += (double)rec.Latency/10.0; sortedLatencies[i] = (double)rec.Latency/10.0; } qsort(sortedLatencies,(UINT)n,sizeof(double),compare); median = (n & 1) ? sortedLatencies[(n-1)/2] : 0.5*(sortedLatencies[n/2] + sortedLatencies[n/2 - 1]); free(sortedLatencies); mean = s / n; abdev = var = skew = kurt = 0.0; for (i=0; i<n; i++) { // second pass, we get 1st,2nd,3rd,4th moments of deviation from mean
GetLogEntry(&g_log, &rec, i); abdev += fabs(s=(double)rec.Latency/10.0 - mean); ep += s; var += (p = s*s); skew += (p *= s); kurt += (p *= s); } abdev /= n; var = (var - ep*ep/n) / (n-1); sdev = sqrt(var); if (var) { // if var=0, no skew/kurtosis defined
skew /= (n*var*sdev); kurt = kurt / (n*var*var) - 3.0; }
count = sprintf(szBuf, "Latency Statistics (microsecond units): median: %.1lf\n",median); WriteStats(szBuf, count); printf("%s",szBuf); count = sprintf(szBuf, "\tMean: %6.2lf\tStdev: %6.2lf\tAbDev: %6.2lf\n",mean,sdev,abdev); WriteStats(szBuf, count); printf("%s",szBuf); count = sprintf(szBuf, "\tVariance: %6.2lf\tSkew: %6.2lf\tKurtosis: %6.2lf\n",var,skew,kurt); WriteStats(szBuf, count); printf("%s",szBuf); }
VOID CheckForLostPackets() { LOG_RECORD currentRecord; INT currentSequenceNumber = 0; INT bytesWritten; UCHAR holdingBuffer[MAX_STRING]; INT count; INT64 nLost = 0; INT i;
for(i=0; i<g_log.nBuffersLogged; i++){ GetLogEntry(&g_log, ¤tRecord, i); if(currentRecord.SequenceNumber != currentSequenceNumber){ nLost += currentRecord.SequenceNumber - currentSequenceNumber; currentSequenceNumber = currentRecord.SequenceNumber; }
currentSequenceNumber += g_params.LoggingPeriod; } count = sprintf(holdingBuffer, "Dropped %I64u packets\n", nLost); WriteStats(holdingBuffer, count); } // CheckForLostPackets()
VOID WriteStats( UCHAR * HoldingBuffer, INT Count) { INT bytesWritten;
if(Count < 0){ Count = MAX_STRING; }
WriteFile(hStatFile, HoldingBuffer, Count, &bytesWritten, NULL); } // WriteStats()
VOID NormalizeTimeStamps() { LOG_RECORD currentRecord; INT bytesWritten; UCHAR holdingBuffer[MAX_STRING]; INT count; INT i;
UINT64 timeSent; UINT64 timeReceived; UINT64 smaller; INT64 constantDelay = MAX_INT64; INT64 currentDelay; UINT64 base = 0xFFFFFFFFFFFFFFFF;
for(i=0; i<g_log.nBuffersLogged; i++){ GetLogEntry(&g_log, ¤tRecord, i); currentDelay = currentRecord.TimeReceived - currentRecord.TimeSent; constantDelay = (currentDelay < constantDelay) ? currentDelay : constantDelay; }
// now subtract off the constant delay off
for(i=0; i<g_log.nBuffersLogged; i++){ GetLogEntry(&g_log, ¤tRecord, i); currentRecord.TimeReceived -= constantDelay; currentRecord.Latency = currentRecord.TimeReceived - currentRecord.TimeSent; SetLogEntry(&g_log, ¤tRecord, i); }
for (i=0; i<g_log.nBuffersLogged; i++) { GetLogEntry(&g_log, ¤tRecord, i); smaller = (currentRecord.TimeReceived < currentRecord.TimeSent) ? currentRecord.TimeReceived : currentRecord.TimeSent; base = (base < smaller)?base:smaller; // find the smallest timestamp
} // now we can subtract the base off of the send & receive times
for (i=0; i<g_log.nBuffersLogged; i++) { GetLogEntry(&g_log, ¤tRecord, i); currentRecord.TimeSent -= base; currentRecord.TimeReceived -= base; SetLogEntry(&g_log, ¤tRecord, i); } } // NormalizeTimeStamps()
VOID ClockSkew( DOUBLE * Slope, DOUBLE * Offset) { // If there is a calibration period, we can estimate clock skew between
// sender and receiver. See comments under AdjustForClockSkew. We use
// calculus to determine the best-fit slope.
INT i; LOG_RECORD currentRecord; DOUBLE N; DOUBLE slope; DOUBLE offset; UCHAR holdingBuffer[MAX_STRING]; INT count; double *x, *y, abdev; double devpercent; // We find the clock skew using medfit, a function which fits to minimum absolute deviation
N = (double) g_params.calibration; x = malloc(sizeof(double) * (UINT)N); y = malloc(sizeof(double) * (UINT)N); for (i = 0; i<N; i++) { GetLogEntry(&g_log,¤tRecord,i); x[i] = (DOUBLE)currentRecord.TimeSent; y[i] = (DOUBLE)currentRecord.Latency; } medfit(x, y, (INT)N, &offset, &slope, &abdev);
// Now write out our findings.
bzero(holdingBuffer, MAX_STRING);
count = _snprintf(holdingBuffer, MAX_STRING-1, // leave room for NULL
"Clock skew is %f microseconds per second.\n " \ "\tbased on %d calibration points\n", 100000*slope, g_params.calibration);
WriteStats(holdingBuffer, count); printf("%s",holdingBuffer);
for (i = 0,devpercent = 0.0; i<N; i++) { devpercent += y[i]; } devpercent /= N; devpercent = 100 * abdev / devpercent;
printf("\tfit resulted in avg. absolute deviation of %f percent from mean\n",devpercent);
free(x); free(y); *Slope = slope; *Offset = offset; } // ClockSkew()
BOOLEAN AnomalousPoint( DOUBLE x, DOUBLE y) { // here we simply keep a buffer of the past 10 calls and if this one
// falls out of a few standard deviations of the 8 inner points, we deem it anomalous
static DOUBLE buf[10]; DOUBLE sortedbuf[10]; DOUBLE mean = 0; DOUBLE sum = 0; DOUBLE sumsqdev = 0; DOUBLE median = 0; DOUBLE sdev = 0; DOUBLE N; static int curIndex = 0; int i; static INT64 submittedPoints;
buf[curIndex % 10] = y; curIndex++; submittedPoints++; if (g_params.SkewFitMode != 4) return FALSE;
if (submittedPoints >= 10) { sum = 0; sumsqdev = 0;
// sort them into sortedbuf
for (i=0; i<10; i++) sortedbuf[i] = buf[i]; qsort(sortedbuf, 10, sizeof(DOUBLE), compare);
// use only the inner 8 points in the calculation of mean & var
for (i = 1; i < 9; i++) { sum += sortedbuf[i]; }
N = 8.0; // using only 8 points
mean = sum / N;
for (i = 1; i < 9; i++) { sumsqdev += ((sortedbuf[i] - mean) * (sortedbuf[i] - mean)); } sdev = sqrt(sumsqdev / N); if (fabs(y - mean) < 2.5 * sdev) { return FALSE; } else { anomalies++; return TRUE; } }
return TRUE; } // AnomalousPoint()
VOID AdjustForClockSkew( DOUBLE Slope, DOUBLE Offset) { //
// When measuring very low jitter, clock drift between machines
// introduces noise in the form of a monotonically increasing
// skew between sending and receiving clock. This effect can be
// filtered out by finding the best-fit slope for all samples
// taken during the calibration period, then using this slope to
// normalize the entire run. This routine normalizes using the
// slope determined in the routine ClockSkew.
//
INT i; LOG_RECORD currentRecord; INT64 minLatency = MAX_INT64; INT64 x; DOUBLE mXPlusB;
for(i=0; i < g_log.nBuffersLogged; i++){ GetLogEntry(&g_log, ¤tRecord, i); mXPlusB = (currentRecord.TimeSent*Slope) + Offset; // offset is not necessary
currentRecord.TimeReceived -= (INT64)mXPlusB; currentRecord.Latency -= (INT64)mXPlusB;
SetLogEntry(&g_log, ¤tRecord, i);
//
// find the minimum latency value
//
minLatency = (currentRecord.Latency < minLatency)? currentRecord.Latency: minLatency; }
for(i=0; i < g_log.nBuffersLogged; i++){ GetLogEntry(&g_log, ¤tRecord, i); currentRecord.Latency -= minLatency; currentRecord.TimeReceived -= minLatency; SetLogEntry(&g_log, ¤tRecord, i); } } // AdjustForClockSkew()
#define WACKY 2.5
BOOL FixWackyTimestamps() { // This routine will look over the sender & receiver timestamps and try to see if there
// are any non-clock skew related irregularities (such as one of them bumping it's clock
// a fixed amount every once-in-a-while) and try to remove them.
INT64 *sendstamps, *recvstamps; double *sendgaps, *recvgaps; double *sortedsendgaps, *sortedrecvgaps; double sendmean, sendsdev, sendsum, sendsumsqdev; double recvmean, recvsdev, recvsum, recvsumsqdev; double mediansendgap, medianrecvgap; double modesendgap, moderecvgap; double meansendwackiness, sdevsendwackiness, sumsendwackiness, sumsqdevsendwackiness; double meanrecvwackiness, sdevrecvwackiness, sumrecvwackiness, sumsqdevrecvwackiness; double fractionaldevofsendwackiness, fractionaldevofrecvwackiness; double normalsendgapmean, normalrecvgapmean; double trimmeansendgap, trimmeanrecvgap; BOOL *fWackoSend, *fWackoRecv; int cWackoSend, cWackoRecv; BOOL *fMaybeWackoSend, *fMaybeWackoRecv; int i,N; LOG_RECORD currentRecord; const double FixThreshold = 0.1; double CumulativeFixMagnitude = 0.0;
N = (int)g_log.nBuffersLogged; cWackoSend = cWackoRecv = 0; // fill our arrays.
sendstamps = malloc(sizeof(INT64) * N); recvstamps = malloc(sizeof(INT64) * N); sendgaps = malloc(sizeof(double) * N); recvgaps = malloc(sizeof(double) * N); sortedsendgaps = malloc(sizeof(double) *N); sortedrecvgaps = malloc(sizeof(double) *N); fWackoRecv = malloc(sizeof(BOOL) * N); fWackoSend = malloc(sizeof(BOOL) * N); fMaybeWackoSend = malloc(sizeof(BOOL) * N); fMaybeWackoRecv = malloc(sizeof(BOOL) * N);
for (i=0; i<N; i++) { GetLogEntry(&g_log, ¤tRecord, i); sendstamps[i] = currentRecord.TimeSent; recvstamps[i] = currentRecord.TimeReceived; fWackoSend[i] = FALSE; fMaybeWackoSend[i] = FALSE; fWackoRecv[i] = FALSE; fMaybeWackoRecv[i] = FALSE; } // First, check for wacky timestamps. This is a multistep process:
// 1. Calculate the interpacket gaps on both sender & receiver.
for (i=1; i<N; i++) { sendgaps[i] = (double) (sendstamps[i] - sendstamps[i-1]); recvgaps[i] = (double) (recvstamps[i] - recvstamps[i-1]); } // 2. We will define wacky as being at least WACKY standard deviations away from the
// mean.
sendsum = recvsum = 0.0; for (i=1; i<N; i++) { sendsum += sendgaps[i]; recvsum += recvgaps[i]; } sendmean = sendsum / N; recvmean = recvsum / N; sendsumsqdev = recvsumsqdev = 0.0; for (i=1; i<N; i++) { sendsumsqdev += ((sendgaps[i] - sendmean) * (sendgaps[i] - sendmean)); recvsumsqdev += ((recvgaps[i] - recvmean) * (recvgaps[i] - recvmean)); } sendsdev = sqrt(sendsumsqdev / N); recvsdev = sqrt(recvsumsqdev / N);
for (i=1; i<N; i++) { if ((sendgaps[i] < sendmean - WACKY*sendsdev) || (sendgaps[i] > sendmean + WACKY*sendsdev)) { fMaybeWackoSend[i] = fWackoSend[i] = TRUE; } if ((recvgaps[i] < recvmean - WACKY*recvsdev) || (recvgaps[i] > recvmean + WACKY*recvsdev)) { fMaybeWackoRecv[i] = fWackoRecv[i] = TRUE; } } // 3. Check to see if any wacky points are unpaired (that is, a wacky point in the
// sending timestamps is not matched with an equally wacky point in the receiving
// timestamps).
for (i=1; i<N; i++) { if (fMaybeWackoSend[i] && fMaybeWackoRecv[i]) { // I should check to make sure they're equally wacky, but i'm not currently
fMaybeWackoSend[i] = fWackoSend[i] = FALSE; fMaybeWackoRecv[i] = fWackoRecv[i] = FALSE; } } // 4. Check to see if any wacky unpaired points are solitary (that is, they are not
// surrounded by other wacky points).
for (i=1; i<N-1; i++) { if (fMaybeWackoSend[i]) { if (fMaybeWackoSend[i-1] || fMaybeWackoSend[i+1]) { fWackoSend[i] = FALSE; } } if (fMaybeWackoRecv[i]) { if (fMaybeWackoRecv[i-1] || fMaybeWackoRecv[i+1]) { fWackoRecv[i] = FALSE; } } } if (fMaybeWackoSend[N-1] && fMaybeWackoSend[N-2]) fWackoSend[N-1] = FALSE; if (fMaybeWackoRecv[N-1] && fMaybeWackoRecv[N-2]) fWackoRecv[N-1] = FALSE; // 5. If we find a point that meets all these criteria, label it wacky and add it to
// our list of wacky points.
for (i=1; i<N; i++) { fMaybeWackoSend[i] = fWackoSend[i]; fMaybeWackoRecv[i] = fWackoRecv[i]; }
// Now we find out the stats for the sends & receivees to use as the baseline
sendsum = recvsum = 0.0; cWackoSend = cWackoRecv = 0; for (i=1; i<N; i++) { sortedsendgaps[i] = sendgaps[i]; sortedrecvgaps[i] = recvgaps[i]; if (!fWackoSend[i]) { sendsum += sendgaps[i]; cWackoSend++; } if (!fWackoRecv[i]) { recvsum += recvgaps[i]; cWackoRecv++; } } normalsendgapmean = sendsum / cWackoSend; normalrecvgapmean = recvsum / cWackoRecv; qsort(sortedsendgaps, N, sizeof(double), compare); qsort(sortedrecvgaps, N, sizeof(double), compare); if (N & 1) { // odd N
mediansendgap = sortedsendgaps[(N+1) / 2]; medianrecvgap = sortedrecvgaps[(N+1) / 2]; } else { // even N
i = N/2; mediansendgap = 0.5 * (sortedsendgaps[i] + sortedsendgaps[i+1]); medianrecvgap = 0.5 * (sortedrecvgaps[i] + sortedrecvgaps[i+1]); } sendsum = recvsum = 0.0; for (i=(int)(0.05*N); i<(int)(0.85*N); i++) { // find the 80% trimmean (bottom heavy)
sendsum += sortedsendgaps[i]; recvsum += sortedrecvgaps[i]; } trimmeansendgap = sendsum / (0.80 * N); trimmeanrecvgap = recvsum / (0.80 * N); modesendgap = mode(sendgaps, N); moderecvgap = mode(recvgaps, N);
// 6. we have to check to see if the wackiness at each wacky point is about equal to what
// we think it ought to be, based on the timer clock
for (i=1; i<N; i++) { if (fWackoSend[i]) { if (!InRange(sendgaps[i] - g_BadHalAdjustment, mediansendgap - sendsdev, mediansendgap + sendsdev)) { fWackoSend[i] = FALSE; cWackoSend--; } } if (fWackoRecv[i]) { if (!InRange(recvgaps[i] - g_BadHalAdjustment, medianrecvgap - recvsdev, medianrecvgap + recvsdev)) { fWackoRecv[i] = FALSE; cWackoRecv--; } } }
// Now we want to correct for the wacky timestamps, so we see if the wacky points are all
// equally wacky. If they are, we're psyched and we simply subtract off the wackiness
// from the wacky points and all points after them. (Wackiness is cumulative!)
cWackoSend = cWackoRecv = 0; sumsendwackiness = sumrecvwackiness = sumsqdevsendwackiness = sumsqdevrecvwackiness = 0.0; for (i=1; i<N; i++) { if (fWackoSend[i]) { sumsendwackiness += (sendgaps[i] - trimmeansendgap); cWackoSend++; } if (fWackoRecv[i]) { sumrecvwackiness += (recvgaps[i] - trimmeanrecvgap); cWackoRecv++; } } meansendwackiness = sumsendwackiness / cWackoSend; meanrecvwackiness = sumrecvwackiness / cWackoRecv; for (i=1; i<N; i++) { if (fWackoSend[i]) sumsqdevsendwackiness += ((sendgaps[i]-trimmeansendgap-meansendwackiness) * (sendgaps[i]-normalsendgapmean-meansendwackiness)); if (fWackoRecv[i]) sumsqdevrecvwackiness += ((recvgaps[i]-trimmeanrecvgap-meanrecvwackiness) * (recvgaps[i]-normalrecvgapmean-meanrecvwackiness)); } sdevsendwackiness = sqrt(sumsqdevsendwackiness / cWackoSend); sdevrecvwackiness = sqrt(sumsqdevrecvwackiness / cWackoRecv); // so if the fractional deviation is less than some set amount, we apply the fix
fractionaldevofsendwackiness = sdevsendwackiness / meansendwackiness; fractionaldevofrecvwackiness = sdevrecvwackiness / meanrecvwackiness; if (cWackoSend && (fractionaldevofsendwackiness < FixThreshold)) { // apply fix to send timestamps
CumulativeFixMagnitude = 0.0; cWackoSend = 0; for (i=0; i<N; i++) { if (fWackoSend[i]) { fWackySender = TRUE; CumulativeFixMagnitude += g_BadHalAdjustment; cWackoSend++; } sendstamps[i] -= (INT64)CumulativeFixMagnitude; } } if (cWackoRecv && (fractionaldevofrecvwackiness < FixThreshold)) { // apply fix to recv timestamps
CumulativeFixMagnitude = 0.0; cWackoRecv = 0; for (i=0; i<N; i++) { if (fWackoRecv[i]) { fWackyReceiver = TRUE; CumulativeFixMagnitude += g_BadHalAdjustment; cWackoRecv++; } recvstamps[i] -= (INT64)CumulativeFixMagnitude; } }
// set the globals to reflect our "fixed" values
for (i=0; i<N; i++) { if (fWackySender) { GetLogEntry(&g_log, ¤tRecord, i); currentRecord.TimeSent = sendstamps[i]; SetLogEntry(&g_log, ¤tRecord, i); } if (fWackyReceiver) { GetLogEntry(&g_log, ¤tRecord, i); currentRecord.TimeReceived = recvstamps[i]; SetLogEntry(&g_log, ¤tRecord, i); } } if (fWackySender || fWackyReceiver) { printf("WARNING: I noticed some oddities among the timestamps on the"); if (fWackySender) printf(" sender"); if (fWackySender && fWackyReceiver) printf(" and"); if (fWackyReceiver) printf(" receiver"); printf(".\n"); if (fWackySender) { printf("\t%d of them on the order of %fms each on the sender.\n", cWackoSend, meansendwackiness / 10000); } if (fWackyReceiver) { printf("\t%d of them on the order of %fms each on the receiver.\n", cWackoRecv, meanrecvwackiness / 10000); } printf("\tThey are caused by a malfunctioning clock on the afflicted machine.\n"); printf("\tI have tried to compensate for them in the .log file.\n"); NormalizeTimeStamps(); // we have to renormalize now
} return FALSE; }
DWORD WINAPI RSVPMonitor (LPVOID lpvThreadParm) { DWORD dwResult = 0; ULONG status; BOOLEAN confirmed = FALSE; UINT64 ui64LastHi = 0,ui64Now = 0; FILETIME filetime; ULARGE_INTEGER ulargeint; BOOLEAN fResvGood = FALSE;
// don't do anything until the control socket is established
while (g_sockControl == INVALID_SOCKET) { Sleep(10); }
while(TRUE){ // send a HELLO message every once in a while
GetSystemTimeAsFileTime(&filetime); memcpy(&ulargeint, &filetime, sizeof(FILETIME)); ui64Now = ulargeint.QuadPart; if (ui64LastHi + 10000000*SECONDS_BETWEEN_HELLOS < ui64Now) { SendControlMessage(g_sockControl,MSGST_HELLO); ui64LastHi = ui64Now; } // get the RSVP statuscode, waiting for as long as it takes
status = GetRsvpStatus(WSA_INFINITE,fd);
if (g_state.Done) { ExitThread(1); } switch (status) { case WSA_QOS_TRAFFIC_CTRL_ERROR: // sad if we get this
printf("RSVP-ERR: Reservation rejected by traffic control on server. Aborting.\n"); SendControlMessage(g_sockControl,MSGST_RSVPERR); g_state.Done = TRUE; exit(1); break; case WSA_QOS_REQUEST_CONFIRMED: // happy if we get this
if (!confirmed) { printf("RSVP: Reservation confirmed\n"); confirmed = TRUE; fResvGood = TRUE; } break; case WSA_QOS_SENDERS: if (!fResvGood && !trans) { printf("\nRSVP Monitor: WSA_QOS_SENDERS at t=%I64ds\n", (GetUserTime() - timeStart) / 10000000); fResvGood = TRUE; } break; case WSA_QOS_RECEIVERS: if (!fResvGood && trans) { printf("\nRSVP Monitor: WSA_QOS_RECEIVERS at t=%I64ds\n", (GetUserTime() - timeStart) / 10000000); fResvGood = TRUE; } break; case WSA_QOS_NO_SENDERS: // the sender is now gone, so we stop
if (fResvGood && !trans) { printf("\nRSVP Monitor: WSA_QOS_NO_SENDERS at t=%I64ds\n", (GetUserTime() - timeStart) / 10000000); fResvGood = FALSE; } break; case WSA_QOS_NO_RECEIVERS: // means the sender is done, so he should exit
if (fResvGood && trans) { printf("\nRSVP Monitor: WSA_QOS_NO_RECEIVERS at t=%I64ds\n", (GetUserTime() - timeStart) / 10000000); fResvGood = FALSE; } break; default: break; } Sleep(1000); // check at most once per second
} return dwResult; } // RSVPMonitor()
DWORD WINAPI KeyboardMonitor(LPVOID lpvThreadParm) { DWORD dwResult = 0; char ch; while (TRUE) { ch = (CHAR) getchar(); switch (ch) { case 'q': SendControlMessage(g_sockControl,MSGST_DONE); g_state.Done = TRUE; ExitThread(1); break; } } return 0; }
DWORD WINAPI ControlSocketMonitor(LPVOID lpvThreadParm) { DWORD dwResult = 0; DWORD dwError, cbBuf = 0; DWORD dwAddrSize = MAX_STRING; char szAddr[MAX_STRING]; char szBuf[MAX_STRING],szCommand[MAX_STRING], *pchStart, *pchEnd; int cch; char szT[MAX_STRING]; char szT2[MAX_STRING]; char * szHost; BOOL fSender; SOCKET sockControl, sockListen; SOCKADDR_IN sinmeControl, sinhimControl; PHOSTENT phostent; UINT64 ui64LastHello = 0; BOOL fDone = FALSE; BOOL fGotRate=FALSE, fGotSize=FALSE, fGotNum=FALSE; BOOL fSentReady =FALSE;
// find out if we're the sender or receiver
if (lpvThreadParm == NULL) fSender = FALSE; else fSender = TRUE;
// if sender, copy the host address into our local host string
if (fSender) { szHost = malloc(strlen((char *)lpvThreadParm) + 1); strcpy(szHost, (const char *)lpvThreadParm); }
// set up a control socket
if (fSender) { sockControl = socket(AF_INET, SOCK_STREAM, 0); } else { sockListen = socket(AF_INET, SOCK_STREAM, 0); } // bind properly
sinmeControl.sin_family = AF_INET; sinmeControl.sin_addr.s_addr = INADDR_ANY; sinhimControl.sin_family = AF_INET; if (fSender) { sinmeControl.sin_port = 0; // set up the sinhim structure
if (atoi(szHost) > 0 ) { sinhimControl.sin_addr.s_addr = inet_addr(szHost); } else{ if ((phostent=gethostbyname(szHost)) == NULL) { ErrorExit("bad host name",WSAGetLastError()); } sinhimControl.sin_family = phostent->h_addrtype; memcpy(&(sinhimControl.sin_addr.s_addr), phostent->h_addr, phostent->h_length); } sinhimControl.sin_port = htons(CONTROL_PORT); dwError = bind(sockControl,(SOCKADDR*)&sinmeControl,sizeof(sinmeControl)); } else { // receiver
sinmeControl.sin_port = htons(CONTROL_PORT); dwError = bind(sockListen,(SOCKADDR*)&sinmeControl,sizeof(sinmeControl)); } if (dwError == SOCKET_ERROR) ErrorExit("bind failed",WSAGetLastError());
// now connect the socket
sinhimControl.sin_family = AF_INET; if (fSender) { // if we're the sender, keep trying to connect until we get through
dwAddrSize = MAX_STRING; dwError = WSAAddressToString((SOCKADDR *)&(sinhimControl), sizeof(SOCKADDR_IN), NULL, szAddr, &dwAddrSize); if (dwError == SOCKET_ERROR) ErrorExit("WSAAddressToString failed", WSAGetLastError()); else strcpy(szHisAddr,szAddr); while (TRUE) { dwError = connect(sockControl,(SOCKADDR*)&sinhimControl,sizeof(sinhimControl)); if (!dwError) { printf("control socket: connected to %s\n",szAddr); break; } dwError = WSAGetLastError(); if (dwError != WSAECONNREFUSED) { ErrorExit("connect() failed",dwError); } Sleep(500); // wait a half second between attempts
} } else { // if we're the receiver, listen / accept
if (listen(sockListen, SOMAXCONN) == SOCKET_ERROR) { ErrorExit("listen() failed", WSAGetLastError()); }
sockControl = accept(sockListen, (SOCKADDR*)&sinhimControl, &dwAddrSize); // once we've accepted, close the listen socket
closesocket(sockListen); if ((INT_PTR)sockControl < 0) { ErrorExit("accept() failed",WSAGetLastError()); } dwAddrSize = MAX_STRING; dwError = WSAAddressToString((SOCKADDR *)&(sinhimControl), sizeof(SOCKADDR_IN), NULL, szAddr, &dwAddrSize); if (dwError == SOCKET_ERROR) ErrorExit("WSAAddressToString failed", WSAGetLastError()); else strcpy(szHisAddr, szAddr); printf("control socket: accepted connection from %s\n",szAddr); }
// set our global control socket variable
g_sockControl = sockControl;
// record my name
dwAddrSize = sizeof(SOCKADDR_IN); getsockname(sockControl,(SOCKADDR *)&(sinmeControl),&dwAddrSize); dwAddrSize = MAX_STRING; dwError = WSAAddressToString((SOCKADDR *)&(sinmeControl), sizeof(SOCKADDR_IN), NULL, szAddr, &dwAddrSize); if (dwError == SOCKET_ERROR) ErrorExit("WSAAddressToString failed", WSAGetLastError()); else strcpy(szMyAddr, szAddr); // exchange version information
sprintf(szBuf, "%s %s", MSGST_VER, VERSION_STRING); SendControlMessage(sockControl, szBuf); // now that we're all set, do the actual work of the control socket
while (!fDone) { ZeroMemory(szBuf,MAX_STRING); dwError = cbBuf = recv(sockControl, szBuf, MAX_STRING, 0); pchStart = szBuf; pchEnd = szBuf + cbBuf; if (dwError == 0) { // the connection's been gracefully closed
fDone = TRUE; closesocket(sockControl); g_fOtherSideFinished=TRUE; ExitThread(0); } if (dwError == SOCKET_ERROR) { dwError = WSAGetLastError(); if (dwError == WSAECONNRESET) { printf("\ncontrol socket: connection reset by peer"); printf("\n\t%I64us since last HELLO packet received", (GetUserTime() - ui64LastHello)/10000000); printf("\n\t%I64us since start", (GetUserTime() - timeStart)/10000000); g_state.Done = TRUE; fDone = TRUE; g_fOtherSideFinished = TRUE; closesocket(sockControl); ExitThread(1); } else { printf("\ncontrol socket: error in recv: %d\n",dwError); g_state.Done = TRUE; fDone = TRUE; g_fOtherSideFinished = TRUE; closesocket(sockControl); ExitThread(1); } continue; } while (pchStart < pchEnd) { ZeroMemory(szCommand,MAX_STRING); // consume the first command and act on it
if (pchEnd > szBuf + cbBuf) break; pchEnd = strchr(pchStart, MSGCH_DELIMITER); if (pchEnd == NULL) break; strncpy(szCommand,pchStart,pchEnd - pchStart); if (strcmp(szCommand,MSGST_HELLO) == 0) { // update last hello time
ui64LastHello = GetUserTime(); // i should do something like set a timer here that sleeps until a certain timeout
// passes, at which point it aborts our transfer
} if (strcmp(szCommand,MSGST_ERROR) == 0) { // the other guy's had an error, so we stop and tell him to abort
g_fOtherSideFinished = TRUE; g_state.Done = TRUE; fDone = TRUE; SendControlMessage(sockControl,MSGST_ABORT); closesocket(sockControl); ExitThread(1); } if (strcmp(szCommand,MSGST_ABORT) == 0) { // we're told to abort, so do so
g_fOtherSideFinished = TRUE; g_state.Done = TRUE; fDone = TRUE; closesocket(sockControl); ExitThread(1); } if (strcmp(szCommand,MSGST_DONE) == 0) { // we're told the other guy's done, so therefore are we
closesocket(sockControl); g_fOtherSideFinished = TRUE; g_state.Done = TRUE; fDone = TRUE; ExitThread(1); } if (strcmp(szCommand,MSGST_RSVPERR) == 0) { // we're told the other guy got an rsvp error, so we abort the whole program
closesocket(sockControl); g_fOtherSideFinished = TRUE; g_state.Done = TRUE; fDone = TRUE; exit(1); } if (strncmp(szCommand,MSGST_SIZE,4) == 0) { // the sender is telling us how big the buffers are
sscanf(szCommand,"%s %d",szT, &g_params.buflen); fGotSize = TRUE; } if (strncmp(szCommand,MSGST_RATE,4) == 0) { // the sender is telling us how fast the buffers are coming
sscanf(szCommand, "%s %d",szT, &g_params.TokenRate); fGotRate = TRUE; } if (strncmp(szCommand,MSGST_NUM,3) == 0) { // the sender is telling us how many buffers it's sending
sscanf(szCommand, "%s %d",szT, &g_params.nbuf); totalBuffers = g_params.nbuf; fGotNum = TRUE; } if (strncmp(szCommand,MSGST_VER,3) == 0) { sscanf(szCommand, "%s %s",szT, szT2); if (strcmp(szT2,VERSION_STRING) != 0) { printf("WARNING: remote machine using different version of qtcp: %s vs. %s\n", szT2,VERSION_STRING); } } if (trans) { if (strcmp(szCommand,MSGST_READY) == 0) { g_fReadyForXmit = TRUE; } } else { if (!fSentReady && fGotRate && fGotSize && fGotNum) { SendControlMessage(sockControl, MSGST_READY); fSentReady = TRUE; g_fReadyForXmit = TRUE; } } pchStart = pchEnd + 1; pchEnd = szBuf + cbBuf; } } return 0; }
int SendControlMessage(SOCKET sock, char * szMsg) { int iResult; char szBuf[MAX_STRING];
sprintf(szBuf,"%s%c",szMsg,MSGCH_DELIMITER); iResult = send (sock, szBuf, strlen(szBuf), 0);
if (iResult == SOCKET_ERROR) { return WSAGetLastError(); } return iResult; }
void ErrorExit(char *msg, DWORD dwErrorNumber) { fprintf(stderr,"ERROR: %d\n",dwErrorNumber); if (msg != NULL) fprintf(stderr,"\t%s\n",msg); else { switch(dwErrorNumber) { case WSAEFAULT: fprintf(stderr,"\tWSAEFAULT: Buffer too small to contain name\n"); break; case WSAEINVAL: fprintf(stderr,"\tWSAEINVAL: Invalid socket address\n"); break; case WSANOTINITIALISED: fprintf(stderr,"\tWSANOTINITIALIZED: WSA Not initialized\n"); break; default: fprintf(stderr,"\tUnknown error\n"); break; } } SendControlMessage(g_sockControl, MSGST_ABORT); DestroyLog(&g_log); WSACleanup(); exit(1); _exit(1); }
// some math utility functions
// comparison for doubles (to use in qsort)
int __cdecl compare( const void *arg1, const void *arg2 ) { DOUBLE dTemp; DOUBLE d1 = * (DOUBLE *) arg1; DOUBLE d2 = * (DOUBLE *) arg2; dTemp = d1 - d2; if (dTemp < 0) return -1; if (dTemp == 0) return 0; else return 1;
} // comparison for ints (to use in qsort)
int __cdecl compareint( const void *arg1, const void *arg2 ) { int nTemp; int n1 = * (int *) arg1; int n2 = * (int *) arg2; nTemp = n1 - n2; if (nTemp < 0) return -1; if (nTemp == 0) return 0; else return 1; } // comparison for int64s (to use in qsort)
int __cdecl compareI64( const void *arg1, const void *arg2 ) { INT64 nTemp; INT64 n1 = * (INT64 *) arg1; INT64 n2 = * (INT64 *) arg2; nTemp = n1 - n2; if (nTemp < 0) return -1; if (nTemp == 0) return 0; else return 1; }
#define EPS 1.0e-7
// sum up error function for given value of b
double rofunc(double b, int N, double yt[], double xt[], double * paa, double * pabdevt) { int i; double *pfT; double d, sum=0.0; double aa = *paa; double abdevt = *pabdevt;
pfT = malloc(sizeof(double) * N); for (i = 0; i < N; i++) pfT[i] = yt[i]-b*xt[i]; qsort(pfT, N, sizeof(DOUBLE), compare); if (N & 1) { // odd N
aa = pfT[(N+1) / 2]; } else { i = N / 2; aa = 0.5 * (pfT[i] + pfT[i+1]); } abdevt = 0.0; for (i = 0; i<N; i++) { d = yt[i] - (b*xt[i]+aa); abdevt += fabs(d); if (yt[i] != 0.0) d /= fabs(yt[i]); if (fabs(d) > EPS) sum += (d >= 0.0 ? xt[i]: -xt[i]); } *paa = aa; *pabdevt = abdevt; free(pfT); return sum; }
#define SIGN(a,b) ((b) >= 0 ? fabs(a) : fabs(-a))
void medfit(double x[], double y[], int N, double *a, double *b, double *abdev) { // fit y = a + bx to least absolute deviation. abdev is mean absolute deviation.
// incoming, a and b are treated as starting guesses
int i; double *xt = x; double *yt = y; double sx, sy, sxy, sxx, chisq; double del, sigb; double bb, b1, b2, aa, abdevt, f, f1, f2, temp;
sx = sy = sxy = sxx = chisq = 0.0; // we find chisq fit to use as starting guess
for (i=0; i<N; i++) { sx += x[i]; sy += y[i]; sxy += x[i]*y[i]; sxx += x[i]*x[i]; } del = N*sxx - sx*sx; aa = (sxx*sy-sx*sxy) / del; bb = (N*sxy - sx*sy) / del; // do the absolute deviation fit, if we're supposed to.
if (g_params.SkewFitMode == 2) { for (i=0; i<N; i++) chisq += (temp=y[i]-(aa+bb*x[i]), temp*temp); sigb = sqrt(chisq/del); b1 = bb; f1 = rofunc(b1, N, yt, xt, &aa, &abdevt); // guess the bracket as 3 sigma away in downhill direction from f1
b2 = bb + SIGN(3.0 * sigb, f1); f2 = rofunc(b2, N, yt, xt, &aa, &abdevt); if (b2 == b1) { *a = aa; *b = bb; *abdev = abdevt / N; return; } // Bracketing
while ((f1*f2) > 0.0) { if (fabs(f1) < fabs(f2)) f1 = rofunc(b1 += 1.6*(b1-b2),N,yt,xt,&aa,&abdevt); else f2 = rofunc(b2 += 1.6*(b2-b1),N,yt,xt,&aa,&abdevt); } sigb = 0.000001 * sigb; // refine
while (fabs(b2 - b1) > sigb) { bb = b1 + 0.5 * (b2 - b1); if (bb == b1 || bb == b2) break; f = rofunc(bb, N, yt, xt, &aa, &abdevt); if (f*f1 >= 0.0) { f1 = f; b1 = bb; } else { f2 = f; b2 = bb; } } } *a = aa; *b = bb; *abdev = abdevt / N; }
double mode(const double data[], const int N) { // finds and returns the mode of the N points in data
double * sorted; double mode, cur=0; int cMode, cCur; int i;
sorted = malloc(N * sizeof(double));
for (i=0; i<N; i++) sorted[i] = data[i]; qsort(sorted, N, sizeof(double), compare); mode = sorted[0]; cMode = cCur = 0; for (i=0; i<N; i++) { if (cCur > cMode) { mode = cur; cMode = cCur; } if (sorted[i] == mode) { cMode++; } else { if (sorted[i] == cur) cCur++; else { cur = sorted[i]; cCur = 1; } } } free(sorted); return mode; }
void RemoveDuplicates(int rg[], int * pN) { // this removes duplicates from the array passed in and returns it with *pN = #remaining
// it makes no guarantees about elements after rg[#remaining]
int *pNewArray; int cNew; int i; qsort(rg,*pN,sizeof(int),compareint); pNewArray = malloc(sizeof(int) * *pN); pNewArray[0] = rg[0]; cNew = 1; for (i=1; i<*pN; i++) { if (rg[i] != pNewArray[cNew - 1]) { pNewArray[cNew++] = rg[i]; } } *pN = cNew; for (i=0; i<cNew; i++) rg[i] = pNewArray[i]; }
void RemoveDuplicatesI64(INT64 rg[], int * pN) { // this removes duplicates from the array passed in and returns it with *pN = #remaining
// it makes no guarantees about elements after rg[#remaining]
INT64 *pNewArray; int cNew; int i; qsort(rg,*pN,sizeof(INT64),compareI64); pNewArray = malloc(sizeof(INT64) * *pN); pNewArray[0] = rg[0]; cNew = 1; for (i=1; i<*pN; i++) { if (rg[i] != pNewArray[cNew - 1]) { pNewArray[cNew++] = rg[i]; } } *pN = cNew; for (i=0; i<cNew; i++) rg[i] = pNewArray[i]; }
void PrintFlowspec(LPFLOWSPEC lpfs) { printf("TokenRate: %lu bytes/sec\n",lpfs->TokenRate); printf("TokenBucketSize: %lu bytes\n",lpfs->TokenBucketSize); printf("PeakBandwidth: %lu bytes/sec\n",lpfs->PeakBandwidth); printf("Latency: %lu microseconds\n",lpfs->Latency); printf("DelayVariation: %lu microseconds\n",lpfs->DelayVariation); printf("ServiceType: %X\n",lpfs->ServiceType); printf("MaxSduSize: %lu bytes\n",lpfs->MaxSduSize); printf("MinimumPolicedSize: %lu bytes\n",lpfs->MinimumPolicedSize); }
|