summaryrefslogtreecommitdiff
path: root/lib/server/TcpNice.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/server/TcpNice.cpp')
-rw-r--r--lib/server/TcpNice.cpp204
1 files changed, 204 insertions, 0 deletions
diff --git a/lib/server/TcpNice.cpp b/lib/server/TcpNice.cpp
new file mode 100644
index 00000000..88d08bda
--- /dev/null
+++ b/lib/server/TcpNice.cpp
@@ -0,0 +1,204 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: TcpNice.cpp
+// Purpose: Calculator for adaptive TCP window sizing to support
+// low-priority background flows using the stochastic
+// algorithm, as described in
+// http://www.thlab.net/~lmassoul/p18-key.pdf
+// Created: 11/02/2012
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include "TcpNice.h"
+#include "Logging.h"
+#include "BoxTime.h"
+
+#ifdef HAVE_NETINET_TCP_H
+# include <netinet/tcp.h>
+#endif
+
+#include "MemLeakFindOn.h"
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: TcpNice::TcpNice()
+// Purpose: Initialise state of the calculator
+// Created: 11/02/2012
+//
+// --------------------------------------------------------------------------
+TcpNice::TcpNice()
+: mLastWindowSize(1),
+ mGammaPercent(100),
+ mAlphaStar(100),
+ mDeltaPercent(10)
+{ }
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: int GetNextWindowSize(int bytesChange,
+// box_time_t timeElapsed, int rttEstimateMillis)
+// Purpose: Calculate the next recommended window size, given the
+// number of bytes sent since the previous recommendation,
+// and the time elapsed.
+// Created: 11/02/2012
+//
+// --------------------------------------------------------------------------
+int TcpNice::GetNextWindowSize(int bytesChange, box_time_t timeElapsed,
+ int rttEstimateMicros)
+{
+ int epsilon = (mAlphaStar * 1000000) / rttEstimateMicros;
+
+ // timeElapsed is in microseconds, so this will fail for T > 2000 seconds
+ int rateLastPeriod = ((uint64_t)bytesChange * 1000000 / timeElapsed);
+
+ int rawAdjustment = epsilon + rateLastPeriod -
+ mRateEstimateMovingAverage[0];
+
+ int gammaAdjustment = (rawAdjustment * mGammaPercent) / 100;
+
+ int newWindowSize = mLastWindowSize + gammaAdjustment;
+
+ int newRateEstimateMovingAverage =
+ (((100 - mDeltaPercent) * mRateEstimateMovingAverage[1]) / 100) +
+ ((mDeltaPercent * rateLastPeriod) / 100);
+
+ BOX_TRACE("TcpNice: "
+ "b=" << bytesChange << ", "
+ "T=" << timeElapsed << ", "
+ "rtt=" << rttEstimateMicros << ", "
+ "e=" << epsilon << ", "
+ "rb=" << rateLastPeriod << ", "
+ "rbhat=" << newRateEstimateMovingAverage << ", "
+ "raw=" << rawAdjustment << ", "
+ "gamma=" << gammaAdjustment << ", "
+ "wb=" << newWindowSize);
+
+ mRateEstimateMovingAverage[0] = mRateEstimateMovingAverage[1];
+ mRateEstimateMovingAverage[1] = newRateEstimateMovingAverage;
+ mLastWindowSize = newWindowSize;
+
+ return newWindowSize;
+}
+
+// --------------------------------------------------------------------------
+//
+// Constructor
+// Name: NiceSocketStream::NiceSocketStream(
+// std::auto_ptr<SocketStream> apSocket)
+// Purpose: Initialise state of the socket wrapper
+// Created: 11/02/2012
+//
+// --------------------------------------------------------------------------
+
+NiceSocketStream::NiceSocketStream(std::auto_ptr<SocketStream> apSocket)
+: mapSocket(apSocket),
+ mTcpNice(),
+ mBytesWrittenThisPeriod(0),
+ mPeriodStartTime(GetCurrentBoxTime()),
+ mTimeIntervalMillis(1000),
+ mEnabled(false)
+{ }
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: NiceSocketStream::Write(const void *pBuffer, int NBytes)
+// Purpose: Writes bytes to the underlying stream, adjusting window size
+// using a TcpNice calculator.
+// Created: 2012/02/11
+//
+// --------------------------------------------------------------------------
+void NiceSocketStream::Write(const void *pBuffer, int NBytes)
+{
+#ifdef HAVE_DECL_SO_SNDBUF
+ if(mEnabled && mapTimer.get() && mapTimer->HasExpired())
+ {
+ box_time_t newPeriodStart = GetCurrentBoxTime();
+ box_time_t elapsed = newPeriodStart - mPeriodStartTime;
+ struct tcp_info info;
+ int socket = mapSocket->GetSocketHandle();
+ int rtt = 50; // WAG
+
+# if defined HAVE_DECL_SOL_TCP && defined HAVE_DECL_TCP_INFO && defined HAVE_STRUCT_TCP_INFO_TCPI_RTT
+ socklen_t optlen = sizeof(info);
+ if(getsockopt(socket, SOL_TCP, TCP_INFO, &info, &optlen) == -1)
+ {
+ BOX_LOG_SYS_WARNING("getsockopt(" << socket << ", SOL_TCP, "
+ "TCP_INFO) failed");
+ }
+ else if(optlen != sizeof(info))
+ {
+ BOX_WARNING("getsockopt(" << socket << ", SOL_TCP, "
+ "TCP_INFO) return structure size " << optlen << ", "
+ "expected " << sizeof(info));
+ }
+ else
+ {
+ rtt = info.tcpi_rtt;
+ }
+# endif
+
+ int newWindow = mTcpNice.GetNextWindowSize(mBytesWrittenThisPeriod,
+ elapsed, rtt);
+
+ if(setsockopt(socket, SOL_SOCKET, SO_SNDBUF, &newWindow,
+ sizeof(newWindow)) == -1)
+ {
+ BOX_LOG_SYS_WARNING("getsockopt(" << socket << ", SOL_SOCKET, "
+ "SO_SNDBUF, " << newWindow << ") failed");
+ }
+
+ StopTimer();
+ }
+
+ if(mEnabled && !mapTimer.get())
+ {
+ // Don't start the timer until we receive the first data to write,
+ // as diffing might take a long time and we don't want to bias
+ // the TcpNice algorithm by running while we don't have bulk data
+ // to send.
+ StartTimer();
+ mPeriodStartTime = GetCurrentBoxTime();
+ mBytesWrittenThisPeriod = 0;
+ }
+
+ mBytesWrittenThisPeriod += NBytes;
+#endif // HAVE_DECL_SO_SNDBUF
+
+ mapSocket->Write(pBuffer, NBytes);
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: NiceSocketStream::SetEnabled(bool enabled)
+// Purpose: Update the enabled status, and if disabling, cancel the
+// timer and set a sensible window size.
+// Created: 2012/02/12
+//
+// --------------------------------------------------------------------------
+
+void NiceSocketStream::SetEnabled(bool enabled)
+{
+ mEnabled = enabled;
+
+ if(!enabled)
+ {
+ StopTimer();
+#ifdef HAVE_DECL_SO_SNDBUF
+ int socket = mapSocket->GetSocketHandle();
+ int newWindow = 1<<17;
+ if(setsockopt(socket, SOL_SOCKET, SO_SNDBUF, &newWindow,
+ sizeof(newWindow)) == -1)
+ {
+ BOX_LOG_SYS_WARNING("getsockopt(" << socket << ", SOL_SOCKET, "
+ "SO_SNDBUF, " << newWindow << ") failed");
+ }
+#endif
+ }
+}