ostinato/server/pcaptxthread.cpp
Srivats P f98c8af594 Comment out L4Cksum code from pcap tx thread
We are not rewriting L4Cksum for ttag packets at the moment. See
comment in packetsequence.h

Commenting out this code doesn't seem to observably improve tx
performance though.

The latency code seems to reduce stream stats max tx performance
by around 3 to 5%. Recovering this may be done separately as part
of overall performance optimzation of Tx code.
2023-05-08 15:31:20 +05:30

698 lines
21 KiB
C++

/*
Copyright (C) 2010-2016 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#include "pcaptxthread.h"
#include "sign.h"
#include "statstuple.h"
#include "timestamp.h"
#include <QtDebug>
PcapTxThread::PcapTxThread(const char *device)
{
char errbuf[PCAP_ERRBUF_SIZE] = "";
setObjectName(QString("Tx:%1").arg(device));
#ifdef Q_OS_WIN32
LARGE_INTEGER freq;
if (QueryPerformanceFrequency(&freq))
gTicksFreq = freq.QuadPart;
else
Q_ASSERT_X(false, "PcapTxThread::PcapTxThread",
"This Win32 platform does not support performance counter");
#endif
state_ = kNotStarted;
stop_ = false;
trackStreamStats_ = false;
clearPacketList();
handle_ = pcap_open_live(device, 64 /* FIXME */, 0, 1000 /* ms */, errbuf);
if (handle_ == NULL)
goto _open_error;
usingInternalHandle_ = true;
stats_ = NULL;
return;
_open_error:
qDebug("%s: Error opening port %s: %s\n", __FUNCTION__, device, errbuf);
usingInternalHandle_ = false;
}
PcapTxThread::~PcapTxThread()
{
if (usingInternalHandle_)
pcap_close(handle_);
}
bool PcapTxThread::setRateAccuracy(
AbstractPort::Accuracy accuracy)
{
switch (accuracy) {
case AbstractPort::kHighAccuracy:
udelayFn_ = udelay;
qWarning("%s: rate accuracy set to High - busy wait", __FUNCTION__);
break;
case AbstractPort::kLowAccuracy:
udelayFn_ = QThread::usleep;
qWarning("%s: rate accuracy set to Low - usleep", __FUNCTION__);
break;
default:
qWarning("%s: unsupported rate accuracy value %d", __FUNCTION__,
accuracy);
return false;
}
return true;
}
bool PcapTxThread::setStreamStatsTracking(bool enable)
{
trackStreamStats_ = enable;
return true;
}
void PcapTxThread::clearPacketList()
{
Q_ASSERT(!isRunning());
// \todo lock for packetSequenceList
while(packetSequenceList_.size())
delete packetSequenceList_.takeFirst();
currentPacketSequence_ = NULL;
repeatSequenceStart_ = -1;
repeatSize_ = 0;
packetCount_ = 0;
packetListSize_ = 0;
returnToQIdx_ = -1;
setPacketListLoopMode(false, 0, 0);
}
void PcapTxThread::loopNextPacketSet(qint64 size, qint64 repeats,
long repeatDelaySec, long repeatDelayNsec)
{
#if 0 // Don't let implicit packet sets be created
// XXX: The below change was done as part of Turbo code
// implementation alongwith calls to this function from
// AbstractPort::updatePacketListSequential(). Turbo to
// have clean code requires explicit packet sets for all
// cases (except interleaved streams). The below change
// was done so that the base code should not be affected
// after the explict packet set creation calls.
// XXX: Since we create implicit packetset for this case, skip
// This case =>
// 1. Packet set for y when x = 0
// 2. n==1 in n*x+y
// These two cases were the result of the changes in
// updatePacketListSequential() as part of Turbo changes
// mentioned above
if (repeats == 1)
return;
#endif
currentPacketSequence_ = new PacketSequence(trackStreamStats_);
currentPacketSequence_->repeatCount_ = repeats;
currentPacketSequence_->usecDelay_ = repeatDelaySec * long(1e6)
+ repeatDelayNsec/1000;
repeatSequenceStart_ = packetSequenceList_.size();
repeatSize_ = size;
packetCount_ = 0;
packetSequenceList_.append(currentPacketSequence_);
}
bool PcapTxThread::appendToPacketList(long sec, long nsec,
const uchar *packet, int length)
{
bool op = true;
pcap_pkthdr pktHdr;
pktHdr.caplen = pktHdr.len = length;
pktHdr.ts.tv_sec = sec;
pktHdr.ts.tv_usec = nsec/1000;
// loopNextPacketSet should have created a seq
Q_ASSERT(currentPacketSequence_ != NULL);
// If not enough space, update usecDelay and alloc a new seq
if (!currentPacketSequence_->hasFreeSpace(2*sizeof(pcap_pkthdr)+length))
{
struct timeval diff;
timersub(&pktHdr.ts, &currentPacketSequence_->lastPacket_->ts, &diff);
currentPacketSequence_->usecDelay_ = diff.tv_usec;
if (diff.tv_sec)
currentPacketSequence_->usecDelay_ += diff.tv_sec*1e6;
//! \todo (LOW): calculate sendqueue size
currentPacketSequence_ = new PacketSequence(trackStreamStats_);
packetSequenceList_.append(currentPacketSequence_);
// Validate that the pkt will fit inside the new currentSendQueue_
Q_ASSERT(currentPacketSequence_->hasFreeSpace(
sizeof(pcap_pkthdr) + length));
}
if (currentPacketSequence_->appendPacket(&pktHdr, (u_char*) packet) < 0)
{
op = false;
}
packetCount_++;
packetListSize_ += repeatSize_ ?
currentPacketSequence_->repeatCount_ : 1;
// Last packet of packet-set?
if (repeatSize_ > 0 && packetCount_ == repeatSize_)
{
qDebug("repeatSequenceStart_=%d, repeatSize_ = %llu",
repeatSequenceStart_, repeatSize_);
// Set the packetSequence repeatSize
Q_ASSERT(repeatSequenceStart_ >= 0);
Q_ASSERT(repeatSequenceStart_ < packetSequenceList_.size());
if (currentPacketSequence_ != packetSequenceList_[repeatSequenceStart_])
{
PacketSequence *start = packetSequenceList_[repeatSequenceStart_];
currentPacketSequence_->usecDelay_ = start->usecDelay_;
start->usecDelay_ = 0;
start->repeatSize_ =
packetSequenceList_.size() - repeatSequenceStart_;
}
repeatSize_ = 0;
// End current pktSeq
currentPacketSequence_ = NULL;
}
return op;
}
void PcapTxThread::setPacketListLoopMode(
bool loop,
quint64 secDelay,
quint64 nsecDelay)
{
returnToQIdx_ = loop ? 0 : -1;
loopDelay_ = secDelay*long(1e6) + nsecDelay/1000;
}
void PcapTxThread::setPacketListTtagMarkers(
QList<uint> markers,
uint repeatInterval)
{
// XXX: Empty markers => no streams have Ttag
firstTtagPkt_ = markers.isEmpty() ? -1 : int(markers.first());
// Calculate delta markers
ttagDeltaMarkers_.clear();
for (int i = 1; i < markers.size(); i++)
ttagDeltaMarkers_.append(markers.at(i) - markers.at(i-1));
if (!markers.isEmpty()) {
ttagDeltaMarkers_.append(repeatInterval - markers.last()
+ markers.first());
qDebug() << "TtagRepeatInterval:" << repeatInterval;
qDebug() << "FirstTtagPkt:" << firstTtagPkt_;
qDebug() << "TtagMarkers:" << ttagDeltaMarkers_;
}
}
void PcapTxThread::setHandle(pcap_t *handle)
{
if (usingInternalHandle_)
pcap_close(handle_);
handle_ = handle;
usingInternalHandle_ = false;
}
void PcapTxThread::setStats(StatsTuple *stats)
{
stats_ = stats;
}
const StreamStats& PcapTxThread::streamStats()
{
return streamStats_;
}
void PcapTxThread::clearStreamStats()
{
streamStats_.clear();
}
void PcapTxThread::run()
{
//! \todo (MED) Stream Mode - continuous: define before implement
// NOTE1: We can't use pcap_sendqueue_transmit() directly even on Win32
// 'coz of 2 reasons - there's no way of stopping it before all packets
// in the sendQueue are sent out and secondly, stats are available only
// when all packets have been sent - no periodic updates
//
// NOTE2: Transmit on the Rx Handle so that we can receive it back
// on the Tx Handle to do stats
//
// NOTE3: Update pcapExtra counters - port TxStats will be updated in the
// 'stats callback' function so that both Rx and Tx stats are updated
// together
const int kSyncTransmit = 1;
int i;
long overHead = 0; // overHead should be negative or zero
TimeStamp startTime, endTime;
qDebug("packetSequenceList_.size = %d", packetSequenceList_.size());
if (packetSequenceList_.size() <= 0) {
lastTxDuration_ = 0.0;
goto _exit2;
}
for(i = 0; i < packetSequenceList_.size(); i++) {
qDebug("sendQ[%d]: rptCnt = %d, rptSz = %d, usecDelay = %ld", i,
packetSequenceList_.at(i)->repeatCount_,
packetSequenceList_.at(i)->repeatSize_,
packetSequenceList_.at(i)->usecDelay_);
qDebug("sendQ[%d]: pkts = %ld, usecDuration = %ld, ttagL4CksumOfs = %hu", i,
packetSequenceList_.at(i)->packets_,
packetSequenceList_.at(i)->usecDuration_,
packetSequenceList_.at(i)->ttagL4CksumOffset_);
}
qDebug() << "First Ttag: " << firstTtagPkt_
<< "Ttag Markers:" << ttagDeltaMarkers_;
lastStats_ = *stats_; // used for stream stats
// Init Ttag related vars. If no packets need ttag, firstTtagPkt_ is -1,
// so nextTagPkt_ is set to practically unreachable value (due to
// 64 bit counter wraparound time!)
ttagMarkerIndex_ = 0;
nextTtagPkt_ = stats_->pkts + firstTtagPkt_;
getTimeStamp(&startTime);
state_ = kRunning;
i = 0;
while (i < packetSequenceList_.size()) {
_restart:
int rptSz = packetSequenceList_.at(i)->repeatSize_;
int rptCnt = packetSequenceList_.at(i)->repeatCount_;
for (int j = 0; j < rptCnt; j++) {
for (int k = 0; k < rptSz; k++) {
int ret;
PacketSequence *seq = packetSequenceList_.at(i+k);
#ifdef Q_OS_WIN32
TimeStamp ovrStart, ovrEnd;
// Use Windows-only pcap_sendqueue_transmit() if duration < 1s
// and no stream timing is configured
if (seq->usecDuration_ <= long(1e6) && firstTtagPkt_ < 0) {
getTimeStamp(&ovrStart);
ret = pcap_sendqueue_transmit(handle_,
seq->sendQueue_, kSyncTransmit);
if (ret >= 0) {
stats_->pkts += seq->packets_;
stats_->bytes += seq->bytes_;
getTimeStamp(&ovrEnd);
overHead += seq->usecDuration_
- udiffTimeStamp(&ovrStart, &ovrEnd);
Q_ASSERT(overHead <= 0);
}
if (stop_)
ret = -2;
} else {
ret = sendQueueTransmit(handle_, seq,
overHead, kSyncTransmit);
}
#else
ret = sendQueueTransmit(handle_, seq,
overHead, kSyncTransmit);
#endif
if (ret >= 0) {
long usecs = seq->usecDelay_ + overHead;
if (usecs > 0) {
(*udelayFn_)(usecs);
overHead = 0;
} else
overHead = usecs;
} else {
qDebug("error %d in sendQueueTransmit()", ret);
qDebug("overHead = %ld", overHead);
stop_ = false;
goto _exit;
}
} // rptSz
} // rptCnt
// Move to the next Packet Set
i += rptSz;
}
if (returnToQIdx_ >= 0) {
long usecs = loopDelay_ + overHead;
if (usecs > 0) {
(*udelayFn_)(usecs);
overHead = 0;
} else
overHead = usecs;
i = returnToQIdx_;
goto _restart;
}
_exit:
getTimeStamp(&endTime);
lastTxDuration_ = udiffTimeStamp(&startTime, &endTime)/1e6;
_exit2:
qDebug("Tx duration = %fs", lastTxDuration_);
//Q_ASSERT(lastTxDuration_ >= 0);
if (trackStreamStats_)
updateTxStreamStats();
state_ = kFinished;
}
void PcapTxThread::start()
{
// FIXME: return error
if (state_ == kRunning) {
qWarning("Transmit start requested but is already running!");
return;
}
state_ = kNotStarted;
QThread::start();
while (state_ == kNotStarted)
QThread::msleep(10);
}
void PcapTxThread::stop()
{
if (state_ == kRunning) {
stop_ = true;
while (state_ == kRunning)
QThread::msleep(10);
}
else {
// FIXME: return error
qWarning("Transmit stop requested but is not running!");
return;
}
}
bool PcapTxThread::isRunning()
{
return (state_ == kRunning);
}
double PcapTxThread::lastTxDuration()
{
return lastTxDuration_;
}
int PcapTxThread::sendQueueTransmit(pcap_t *p, PacketSequence *seq,
long &overHead, int sync)
{
TimeStamp ovrStart, ovrEnd;
struct timeval ts;
pcap_send_queue *queue = seq->sendQueue_;
struct pcap_pkthdr *hdr = (struct pcap_pkthdr*) queue->buffer;
char *end = queue->buffer + queue->len;
ts = hdr->ts;
getTimeStamp(&ovrStart);
while((char*) hdr < end) {
uchar *pkt = (uchar*)hdr + sizeof(*hdr);
int pktLen = hdr->caplen;
bool ttagPkt = false;
#if 0
quint16 origCksum = 0;
#endif
// Time for a T-Tag packet?
if (stats_->pkts == nextTtagPkt_) {
ttagPkt = true;
// XXX: write 2xBytes instead of 1xHalf-word to avoid
// potential alignment problem
*(pkt+pktLen-5) = SignProtocol::kTypeLenTtag;
*(pkt+pktLen-6) = ttagId_;
#if 0
// Recalc L4 checksum; use incremental checksum as per RFC 1624
// HC' = ~(~HC + ~m + m')
if (seq->ttagL4CksumOffset_) {
quint16 *cksum = reinterpret_cast<quint16*>(
pkt + seq->ttagL4CksumOffset_);
origCksum = qFromBigEndian<quint16>(*cksum);
// XXX: SignProtocol trailer
// ... | <guid> | 0x61 | 0x00 | 0x22 | 0x1d10c0da
// ... | <guid> | 0x61 | <TtagId> | 0x23 | 0x1d10c0da
// For odd pkt Length, Ttag spans across 2 half-words
// XXX: Hardcoded values instead of sign protocol constants
// used below for readability
quint32 newCksum = pktLen & 1 ?
quint16(~origCksum) + quint16(~0x221d) + 0x231d
+ quint16(~0x6100) + (0x6100 | ttagId_) :
quint16(~origCksum) + quint16(~0x0022) + (ttagId_ << 8 | 0x23);
while (newCksum > 0xffff)
newCksum = (newCksum & 0xffff) + (newCksum >> 16);
// XXX: For IPv4/UDP, if ~newcksum is 0x0000 we are supposed to
// set the checksum as 0xffff since 0x0000 indicates no cksum
// is present - we choose not to do this to avoid extra cost
*cksum = qToBigEndian(quint16(~newCksum));
}
#endif
ttagId_++;
nextTtagPkt_ += ttagDeltaMarkers_.at(ttagMarkerIndex_);
ttagMarkerIndex_++;
if (ttagMarkerIndex_ >= ttagDeltaMarkers_.size())
ttagMarkerIndex_ = 0;
}
if (sync) {
long usec = (hdr->ts.tv_sec - ts.tv_sec) * 1000000 +
(hdr->ts.tv_usec - ts.tv_usec);
getTimeStamp(&ovrEnd);
overHead -= udiffTimeStamp(&ovrStart, &ovrEnd);
Q_ASSERT(overHead <= 0);
usec += overHead;
if (usec > 0) {
(*udelayFn_)(usec);
overHead = 0;
} else
overHead = usec;
ts = hdr->ts;
getTimeStamp(&ovrStart);
}
Q_ASSERT(pktLen > 0);
pcap_sendpacket(p, pkt, pktLen);
stats_->pkts++;
stats_->bytes += pktLen;
// Revert T-Tag packet changes
if (ttagPkt) {
*(pkt+pktLen-5) = SignProtocol::kTypeLenTtagPlaceholder;
*(pkt+pktLen-6) = 0;
#if 0
if (seq->ttagL4CksumOffset_) {
quint16 *cksum = reinterpret_cast<quint16*>(
pkt + seq->ttagL4CksumOffset_);
*cksum = qToBigEndian(origCksum);
}
#endif
}
// Step to the next packet in the buffer
hdr = (struct pcap_pkthdr*) (pkt + pktLen);
pkt = (uchar*) ((uchar*)hdr + sizeof(*hdr)); // FIXME: superfluous?
if (stop_) {
return -2;
}
}
return 0;
}
void PcapTxThread::updateTxStreamStats()
{
// If no packets in list, nothing to be done
if (!packetListSize_)
return;
// Get number of tx packets sent during last transmit
quint64 pkts = stats_->pkts > lastStats_.pkts ?
stats_->pkts - lastStats_.pkts :
stats_->pkts + (ULLONG_MAX - lastStats_.pkts);
// Calculate -
// number of complete repeats of packetList_
// => each PacketSet in the packetList is repeated these many times
// number of pkts sent in last partial repeat of packetList_
// - This encompasses 0 or more potentially partial PacketSets
// XXX: Note for the above, we consider a PacketSet to include its
// own repeats within itself
int c = pkts/packetListSize_;
int d = pkts%packetListSize_;
qDebug("%s:", __FUNCTION__);
qDebug("txPkts = %llu", pkts);
qDebug("packetListSize_ = %llu", packetListSize_);
qDebug("c = %d, d = %d\n", c, d);
int i;
if (!c)
goto _last_repeat;
i = 0;
while (i < packetSequenceList_.size()) {
PacketSequence *seq = packetSequenceList_.at(i);
int rptSz = seq->repeatSize_;
int rptCnt = seq->repeatCount_;
for (int k = 0; k < rptSz; k++) {
seq = packetSequenceList_.at(i+k);
StreamStatsIterator iter(seq->streamStatsMeta_);
while (iter.hasNext()) {
iter.next();
uint guid = iter.key();
StreamStatsTuple ssm = iter.value();
streamStats_[guid].tx_pkts += c * rptCnt * ssm.tx_pkts;
streamStats_[guid].tx_bytes += c * rptCnt * ssm.tx_bytes;
}
}
// Move to the next Packet Set
i += rptSz;
}
_last_repeat:
if (!d)
goto _done;
i = 0;
while (i < packetSequenceList_.size()) {
PacketSequence *seq = packetSequenceList_.at(i);
int rptSz = seq->repeatSize_;
int rptCnt = seq->repeatCount_;
for (int j = 0; j < rptCnt; j++) {
for (int k = 0; k < rptSz; k++) {
seq = packetSequenceList_.at(i+k);
Q_ASSERT(seq->packets_);
if (d >= seq->packets_) {
// All packets of this seq were sent
StreamStatsIterator iter(seq->streamStatsMeta_);
while (iter.hasNext()) {
iter.next();
uint guid = iter.key();
StreamStatsTuple ssm = iter.value();
streamStats_[guid].tx_pkts += ssm.tx_pkts;
streamStats_[guid].tx_bytes += ssm.tx_bytes;
}
d -= seq->packets_;
}
else { // (d < seq->packets_)
// not all packets of this seq were sent, so we need to
// traverse this seq upto 'd' pkts, parse guid from the
// packet and update streamStats
struct pcap_pkthdr *hdr =
(struct pcap_pkthdr*) seq->sendQueue_->buffer;
char *end = seq->sendQueue_->buffer + seq->sendQueue_->len;
while(d && ((char*) hdr < end)) {
uchar *pkt = (uchar*)hdr + sizeof(*hdr);
uint guid;
if (SignProtocol::packetGuid(pkt, hdr->caplen, &guid)) {
streamStats_[guid].tx_pkts++;
streamStats_[guid].tx_bytes += hdr->caplen;
}
// Step to the next packet in the buffer
hdr = (struct pcap_pkthdr*) (pkt + hdr->caplen);
d--;
}
Q_ASSERT(d == 0);
goto _done;
}
}
}
// Move to the next Packet Set
i += rptSz;
}
_done:
return;
}
void PcapTxThread::udelay(unsigned long usec)
{
#if defined(Q_OS_WIN32)
LARGE_INTEGER tgtTicks;
LARGE_INTEGER curTicks;
QueryPerformanceCounter(&curTicks);
tgtTicks.QuadPart = curTicks.QuadPart + (usec*gTicksFreq)/1000000;
while (curTicks.QuadPart < tgtTicks.QuadPart)
QueryPerformanceCounter(&curTicks);
#elif defined(Q_OS_LINUX)
struct timeval delay, target, now;
//qDebug("usec delay = %ld", usec);
delay.tv_sec = 0;
delay.tv_usec = usec;
while (delay.tv_usec >= 1000000)
{
delay.tv_sec++;
delay.tv_usec -= 1000000;
}
gettimeofday(&now, NULL);
timeradd(&now, &delay, &target);
do {
gettimeofday(&now, NULL);
} while (timercmp(&now, &target, <));
#else
QThread::usleep(usec);
#endif
}