Merge branch 'sign'
This commit is contained in:
commit
5191b72f2b
111
binding/core.py
111
binding/core.py
@ -98,3 +98,114 @@ class DroneProxy(object):
|
||||
os.fsync(f.fileno())
|
||||
f.close()
|
||||
|
||||
def getStreamStatsDict(self, stream_guid_list):
|
||||
"""
|
||||
Convenience method for fetching stream stats. Returns an object
|
||||
containing port/sguid dictionaries for easier access e.g.
|
||||
|
||||
stream_stats = drone.getStreamStatsDict(guid_list)
|
||||
|
||||
stream_stats.sguid[101].port[1].tx_pkts
|
||||
stream_stats.port[1].sguid[101].rx_bytes
|
||||
|
||||
In addition, you can also retrieve totals across ports, e.g.
|
||||
|
||||
stream_stats.port[1].total.rx_pkts
|
||||
stream_stats.port[1].total.rx_bytes
|
||||
stream_stats.port[1].total.tx_pkts
|
||||
stream_stats.port[1].total.tx_bytes
|
||||
|
||||
and totals across sguids -
|
||||
|
||||
stream_stats.sguid[101].total.tx_pkts
|
||||
stream_stats.sguid[101].total.rx_pkts
|
||||
stream_stats.sguid[101].total.pkt_loss
|
||||
|
||||
This method is a wrapper around the getStreamStats() RPC
|
||||
"""
|
||||
class StreamStatsPortTotal:
|
||||
def __init__(self):
|
||||
self.tx_pkts = 0
|
||||
self.rx_pkts = 0
|
||||
self.tx_bytes = 0
|
||||
self.rx_bytes = 0
|
||||
def __repr__(self):
|
||||
s = ' total: { \n'
|
||||
s += ' rx_pkts: ' + str(self.rx_pkts) + ' \n'
|
||||
s += ' rx_bytes: ' + str(self.rx_bytes) + ' \n'
|
||||
s += ' tx_pkts: ' + str(self.tx_pkts) + ' \n'
|
||||
s += ' tx_bytes: ' + str(self.tx_bytes) + ' \n'
|
||||
s += ' }\n'
|
||||
return s
|
||||
class StreamStatsDictPort:
|
||||
def __init__(self):
|
||||
self.sguid = dict()
|
||||
self.total = StreamStatsPortTotal()
|
||||
def __repr__(self):
|
||||
s = ' sguid: { \n'
|
||||
for k, v in self.sguid.items():
|
||||
s += ' ' + str(k) + ': {\n ' \
|
||||
+ str(v).replace('\n', '\n ') + '}\n'
|
||||
s += ' }\n'
|
||||
s += str(self.total)
|
||||
return s
|
||||
class StreamStatsGuidTotal:
|
||||
def __init__(self):
|
||||
self.tx_pkts = 0
|
||||
self.rx_pkts = 0
|
||||
self.pkt_loss = 0
|
||||
def __repr__(self):
|
||||
s = ' total: { \n'
|
||||
s += ' tx_pkts: ' + str(self.tx_pkts) + ' \n'
|
||||
s += ' rx_pkts: ' + str(self.rx_pkts) + ' \n'
|
||||
s += ' pkt_loss: ' + str(self.pkt_loss) + ' \n'
|
||||
s += ' }\n'
|
||||
return s
|
||||
class StreamStatsDictGuid:
|
||||
def __init__(self):
|
||||
self.port = dict()
|
||||
self.total = StreamStatsGuidTotal()
|
||||
def __repr__(self):
|
||||
s = ' port: { \n'
|
||||
for k, v in self.port.items():
|
||||
s += ' ' + str(k) + ': {\n ' \
|
||||
+ str(v).replace('\n', '\n ') + '}\n'
|
||||
s += ' }\n'
|
||||
s += str(self.total)
|
||||
return s
|
||||
class StreamStatsDict:
|
||||
def __init__(self):
|
||||
self.port = dict()
|
||||
self.sguid = dict()
|
||||
def __repr__(self):
|
||||
s = 'port: {\n'
|
||||
for k, v in self.port.items():
|
||||
s += str(k) + ': {\n' + str(v) + '}\n'
|
||||
s += '}\n'
|
||||
s += 'sguid: {\n'
|
||||
for k, v in self.sguid.items():
|
||||
s += str(k) + ': {\n' + str(v) + '}\n'
|
||||
s += '}\n'
|
||||
return s
|
||||
ssl = self.getStreamStats(stream_guid_list)
|
||||
ssd = StreamStatsDict()
|
||||
for ss in ssl.stream_stats:
|
||||
if ss.port_id.id not in ssd.port:
|
||||
ssd.port[ss.port_id.id] = StreamStatsDictPort()
|
||||
assert ss.stream_guid.id not in ssd.port[ss.port_id.id].sguid
|
||||
ssd.port[ss.port_id.id].sguid[ss.stream_guid.id] = ss
|
||||
|
||||
ssd.port[ss.port_id.id].total.tx_pkts += ss.tx_pkts
|
||||
ssd.port[ss.port_id.id].total.rx_pkts += ss.rx_pkts
|
||||
ssd.port[ss.port_id.id].total.tx_bytes += ss.tx_bytes
|
||||
ssd.port[ss.port_id.id].total.rx_bytes += ss.rx_bytes
|
||||
|
||||
if ss.stream_guid.id not in ssd.sguid:
|
||||
ssd.sguid[ss.stream_guid.id] = StreamStatsDictGuid()
|
||||
assert ss.port_id.id not in ssd.sguid[ss.stream_guid.id].port
|
||||
ssd.sguid[ss.stream_guid.id].port[ss.port_id.id] = ss
|
||||
ssd.sguid[ss.stream_guid.id].total.tx_pkts += ss.tx_pkts
|
||||
ssd.sguid[ss.stream_guid.id].total.rx_pkts += ss.rx_pkts
|
||||
ssd.sguid[ss.stream_guid.id].total.pkt_loss += \
|
||||
ss.tx_pkts - ss.rx_pkts
|
||||
return ssd
|
||||
|
BIN
client/icons/stream_stats.png
Normal file
BIN
client/icons/stream_stats.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 714 B |
@ -96,7 +96,7 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
portsDock->setObjectName("portsDock");
|
||||
portsDock->setFeatures(
|
||||
portsDock->features() & ~QDockWidget::DockWidgetClosable);
|
||||
statsDock = new QDockWidget(tr("Statistics"), this);
|
||||
statsDock = new QDockWidget(tr("Port Statistics"), this);
|
||||
statsDock->setObjectName("statsDock");
|
||||
statsDock->setFeatures(
|
||||
statsDock->features() & ~QDockWidget::DockWidgetClosable);
|
||||
@ -166,6 +166,13 @@ MainWindow::~MainWindow()
|
||||
|
||||
delete pgl;
|
||||
|
||||
// We don't want to save state for Stream Stats Docks - so delete them
|
||||
QList<QDockWidget*> streamStatsDocks
|
||||
= findChildren<QDockWidget*>("streamStatsDock");
|
||||
foreach(QDockWidget *dock, streamStatsDocks)
|
||||
delete dock;
|
||||
Q_ASSERT(findChildren<QDockWidget*>("streamStatsDock").size() == 0);
|
||||
|
||||
QByteArray layout = saveState(0);
|
||||
appSettings->setValue(kApplicationWindowLayout, layout);
|
||||
appSettings->setValue(kApplicationWindowGeometryKey, geometry());
|
||||
@ -309,6 +316,16 @@ void MainWindow::on_actionViewRestoreDefaults_triggered()
|
||||
setGeometry(defaultGeometry_);
|
||||
restoreState(defaultLayout_, 0);
|
||||
|
||||
// Add streamStats as tabs
|
||||
QList<QDockWidget*> streamStatsDocks
|
||||
= findChildren<QDockWidget*>("streamStatsDock");
|
||||
foreach(QDockWidget *dock, streamStatsDocks) {
|
||||
dock->setFloating(false);
|
||||
tabifyDockWidget(statsDock, dock);
|
||||
}
|
||||
statsDock->show();
|
||||
statsDock->raise();
|
||||
|
||||
actionViewShowMyReservedPortsOnly->setChecked(false);
|
||||
}
|
||||
|
||||
|
@ -58,6 +58,9 @@ HEADERS += \
|
||||
streamconfigdialog.h \
|
||||
streamlistdelegate.h \
|
||||
streammodel.h \
|
||||
streamstatsfiltermodel.h \
|
||||
streamstatsmodel.h \
|
||||
streamstatswindow.h \
|
||||
variablefieldswidget.h
|
||||
|
||||
FORMS += \
|
||||
@ -71,6 +74,7 @@ FORMS += \
|
||||
portswindow.ui \
|
||||
preferences.ui \
|
||||
streamconfigdialog.ui \
|
||||
streamstatswindow.ui \
|
||||
variablefieldswidget.ui
|
||||
|
||||
SOURCES += \
|
||||
@ -100,6 +104,8 @@ SOURCES += \
|
||||
streamconfigdialog.cpp \
|
||||
streamlistdelegate.cpp \
|
||||
streammodel.cpp \
|
||||
streamstatsmodel.cpp \
|
||||
streamstatswindow.cpp \
|
||||
variablefieldswidget.cpp
|
||||
|
||||
|
||||
|
@ -42,5 +42,6 @@
|
||||
<file>icons/stream_delete.png</file>
|
||||
<file>icons/stream_duplicate.png</file>
|
||||
<file>icons/stream_edit.png</file>
|
||||
<file>icons/stream_stats.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
@ -100,6 +100,8 @@ public:
|
||||
{ return d.is_exclusive_control(); }
|
||||
OstProto::TransmitMode transmitMode() const
|
||||
{ return d.transmit_mode(); }
|
||||
bool trackStreamStats() const
|
||||
{ return d.is_tracking_stream_stats(); }
|
||||
double averagePacketRate() const
|
||||
{ return avgPacketsPerSec_; }
|
||||
double averageBitRate() const
|
||||
|
@ -20,7 +20,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#include "portconfigdialog.h"
|
||||
#include "settings.h"
|
||||
|
||||
PortConfigDialog::PortConfigDialog(OstProto::Port &portConfig, QWidget *parent)
|
||||
PortConfigDialog::PortConfigDialog(
|
||||
OstProto::Port &portConfig,
|
||||
const OstProto::PortState &portState,
|
||||
QWidget *parent)
|
||||
: QDialog(parent), portConfig_(portConfig)
|
||||
{
|
||||
QString currentUser(portConfig_.user_name().c_str());
|
||||
@ -64,6 +67,13 @@ PortConfigDialog::PortConfigDialog(OstProto::Port &portConfig, QWidget *parent)
|
||||
qDebug("reservedBy_ = %d", reservedBy_);
|
||||
|
||||
exclusiveControlButton->setChecked(portConfig_.is_exclusive_control());
|
||||
streamStatsButton->setChecked(portConfig_.is_tracking_stream_stats());
|
||||
|
||||
// Disable UI elements based on portState
|
||||
if (portState.is_transmit_on()) {
|
||||
transmitModeBox->setDisabled(true);
|
||||
streamStatsButton->setDisabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void PortConfigDialog::accept()
|
||||
@ -95,6 +105,7 @@ void PortConfigDialog::accept()
|
||||
}
|
||||
|
||||
pc.set_is_exclusive_control(exclusiveControlButton->isChecked());
|
||||
pc.set_is_tracking_stream_stats(streamStatsButton->isChecked());
|
||||
|
||||
// Update fields that have changed, clear the rest
|
||||
if (pc.transmit_mode() != portConfig_.transmit_mode())
|
||||
@ -112,5 +123,10 @@ void PortConfigDialog::accept()
|
||||
else
|
||||
portConfig_.clear_is_exclusive_control();
|
||||
|
||||
if (pc.is_tracking_stream_stats() != portConfig_.is_tracking_stream_stats())
|
||||
portConfig_.set_is_tracking_stream_stats(pc.is_tracking_stream_stats());
|
||||
else
|
||||
portConfig_.clear_is_tracking_stream_stats();
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
@ -27,7 +27,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
class PortConfigDialog : public QDialog, public Ui::PortConfigDialog
|
||||
{
|
||||
public:
|
||||
PortConfigDialog(OstProto::Port &portConfig, QWidget *parent);
|
||||
PortConfigDialog(OstProto::Port &portConfig,
|
||||
const OstProto::PortState& portState,
|
||||
QWidget *parent);
|
||||
|
||||
private:
|
||||
virtual void accept();
|
||||
|
@ -6,7 +6,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>244</width>
|
||||
<height>233</height>
|
||||
<height>257</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
@ -69,6 +69,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="streamStatsButton" >
|
||||
<property name="text" >
|
||||
<string>Stream Statistics</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation" >
|
||||
|
@ -1593,13 +1593,14 @@ void PortGroup::clearPortStats(QList<uint> *portList)
|
||||
}
|
||||
|
||||
serviceStub->clearStats(controller, portIdList, ack,
|
||||
NewCallback(this, &PortGroup::processClearStatsAck, controller));
|
||||
NewCallback(this, &PortGroup::processClearPortStatsAck,
|
||||
controller));
|
||||
}
|
||||
_exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void PortGroup::processClearStatsAck(PbRpcController *controller)
|
||||
void PortGroup::processClearPortStatsAck(PbRpcController *controller)
|
||||
{
|
||||
qDebug("In %s", __FUNCTION__);
|
||||
|
||||
@ -1609,3 +1610,78 @@ void PortGroup::processClearStatsAck(PbRpcController *controller)
|
||||
delete controller;
|
||||
}
|
||||
|
||||
bool PortGroup::clearStreamStats(QList<uint> *portList)
|
||||
{
|
||||
qDebug("In %s", __FUNCTION__);
|
||||
|
||||
if (state() != QAbstractSocket::ConnectedState)
|
||||
return false;
|
||||
|
||||
OstProto::StreamGuidList *guidList = new OstProto::StreamGuidList;
|
||||
OstProto::Ack *ack = new OstProto::Ack;
|
||||
PbRpcController *controller = new PbRpcController(guidList, ack);
|
||||
|
||||
if (portList == NULL)
|
||||
guidList->mutable_port_id_list()->CopyFrom(*portIdList_);
|
||||
else
|
||||
for (int i = 0; i < portList->size(); i++)
|
||||
guidList->mutable_port_id_list()->add_port_id()
|
||||
->set_id(portList->at(i));
|
||||
|
||||
serviceStub->clearStreamStats(controller, guidList, ack,
|
||||
NewCallback(this, &PortGroup::processClearStreamStatsAck,
|
||||
controller));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PortGroup::processClearStreamStatsAck(PbRpcController *controller)
|
||||
{
|
||||
qDebug("In %s", __FUNCTION__);
|
||||
|
||||
delete controller;
|
||||
}
|
||||
|
||||
bool PortGroup::getStreamStats(QList<uint> *portList)
|
||||
{
|
||||
qDebug("In %s", __FUNCTION__);
|
||||
|
||||
if (state() != QAbstractSocket::ConnectedState)
|
||||
return false;
|
||||
|
||||
OstProto::StreamGuidList *guidList = new OstProto::StreamGuidList;
|
||||
OstProto::StreamStatsList *statsList = new OstProto::StreamStatsList;
|
||||
PbRpcController *controller = new PbRpcController(guidList, statsList);
|
||||
|
||||
if (portList == NULL)
|
||||
guidList->mutable_port_id_list()->CopyFrom(*portIdList_);
|
||||
else
|
||||
for (int i = 0; i < portList->size(); i++)
|
||||
guidList->mutable_port_id_list()->add_port_id()
|
||||
->set_id(portList->at(i));
|
||||
|
||||
serviceStub->getStreamStats(controller, guidList, statsList,
|
||||
NewCallback(this, &PortGroup::processStreamStatsList, controller));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PortGroup::processStreamStatsList(PbRpcController *controller)
|
||||
{
|
||||
using OstProto::StreamStatsList;
|
||||
|
||||
qDebug("In %s", __FUNCTION__);
|
||||
|
||||
StreamStatsList *streamStatsList =
|
||||
static_cast<StreamStatsList*>(controller->response());
|
||||
|
||||
// XXX: It is required to emit the signal even if the returned
|
||||
// streamStatsList contains no records since the recipient
|
||||
// StreamStatsModel slot needs to disconnect this signal-slot
|
||||
// connection to prevent future stream stats for this portgroup
|
||||
// to be sent to it
|
||||
emit streamStatsReceived(mPortGroupId, streamStatsList);
|
||||
|
||||
delete controller;
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,7 @@ LOW
|
||||
namespace OstProto {
|
||||
class PortContent;
|
||||
class PortGroupContent;
|
||||
class StreamStatsList;
|
||||
}
|
||||
|
||||
class QFile;
|
||||
@ -163,13 +164,19 @@ public:
|
||||
void getPortStats();
|
||||
void processPortStatsList();
|
||||
void clearPortStats(QList<uint> *portList = NULL);
|
||||
void processClearStatsAck(PbRpcController *controller);
|
||||
void processClearPortStatsAck(PbRpcController *controller);
|
||||
bool clearStreamStats(QList<uint> *portList = NULL);
|
||||
void processClearStreamStatsAck(PbRpcController *controller);
|
||||
bool getStreamStats(QList<uint> *portList = NULL);
|
||||
void processStreamStatsList(PbRpcController *controller);
|
||||
|
||||
signals:
|
||||
void portGroupDataChanged(int portGroupId, int portId = 0xFFFF);
|
||||
void portListAboutToBeChanged(quint32 portGroupId);
|
||||
void portListChanged(quint32 portGroupId);
|
||||
void statsChanged(quint32 portGroupId);
|
||||
void streamStatsReceived(quint32 portGroupId,
|
||||
const OstProto::StreamStatsList *stats);
|
||||
|
||||
private slots:
|
||||
void on_reconnectTimer_timeout();
|
||||
|
@ -170,6 +170,7 @@ QVariant PortStatsModel::data(const QModelIndex &index, int role) const
|
||||
case e_STAT_BYTES_SENT_NIC:
|
||||
return stats.tx_bytes_nic();
|
||||
#endif
|
||||
|
||||
case e_STAT_RX_DROPS : return quint64(stats.rx_drops());
|
||||
case e_STAT_RX_ERRORS: return quint64(stats.rx_errors());
|
||||
case e_STAT_RX_FIFO_ERRORS: return quint64(stats.rx_fifo_errors());
|
||||
|
@ -66,6 +66,7 @@ typedef enum {
|
||||
e_STAT_RX_FIFO_ERRORS,
|
||||
e_STAT_RX_FRAME_ERRORS,
|
||||
|
||||
|
||||
e_STATISTICS_END = e_STAT_RX_FRAME_ERRORS,
|
||||
|
||||
e_STAT_MAX
|
||||
@ -92,6 +93,7 @@ static QStringList PortStatName = (QStringList()
|
||||
<< "Bytes Received (NIC)"
|
||||
<< "Bytes Sent (NIC)"
|
||||
#endif
|
||||
|
||||
<< "Receive Drops"
|
||||
<< "Receive Errors"
|
||||
<< "Receive Fifo Errors"
|
||||
|
@ -23,9 +23,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#include "portstatsfilterdialog.h"
|
||||
#include "portstatsmodel.h"
|
||||
#include "portstatsproxymodel.h"
|
||||
#include "streamstatsmodel.h"
|
||||
#include "streamstatswindow.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include <QDockWidget>
|
||||
#include <QHeaderView>
|
||||
#include <QMainWindow>
|
||||
|
||||
extern QMainWindow *mainWindow;
|
||||
|
||||
PortStatsWindow::PortStatsWindow(PortGroupList *pgl, QWidget *parent)
|
||||
: QWidget(parent), proxyStatsModel(NULL)
|
||||
@ -227,6 +233,8 @@ void PortStatsWindow::on_tbClear_clicked()
|
||||
{
|
||||
pgl->portGroupByIndex(portList.at(i).portGroupId).
|
||||
clearPortStats(&portList[i].portList);
|
||||
pgl->portGroupByIndex(portList.at(i).portGroupId).
|
||||
clearStreamStats(&portList[i].portList);
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,6 +261,44 @@ void PortStatsWindow::on_tbClearAll_clicked()
|
||||
{
|
||||
pgl->portGroupByIndex(portList.at(i).portGroupId)
|
||||
.clearPortStats(&portList[i].portList);
|
||||
pgl->portGroupByIndex(portList.at(i).portGroupId)
|
||||
.clearStreamStats(&portList[i].portList);
|
||||
}
|
||||
}
|
||||
|
||||
void PortStatsWindow::on_tbGetStreamStats_clicked()
|
||||
{
|
||||
QList<PortStatsModel::PortGroupAndPortList> portList;
|
||||
StreamStatsModel *streamStatsModel;
|
||||
|
||||
// Get selected ports
|
||||
model->portListFromIndex(selectedColumns, portList);
|
||||
|
||||
if (portList.size()) {
|
||||
QDockWidget *dock = new QDockWidget(mainWindow);
|
||||
streamStatsModel = new StreamStatsModel(dock);
|
||||
dock->setWidget(new StreamStatsWindow(streamStatsModel, dock));
|
||||
dock->setWindowTitle(dock->widget()->windowTitle());
|
||||
dock->setObjectName("streamStatsDock");
|
||||
dock->setAttribute(Qt::WA_DeleteOnClose);
|
||||
QDockWidget *statsDock = mainWindow->findChild<QDockWidget*>(
|
||||
"statsDock");
|
||||
mainWindow->addDockWidget(Qt::BottomDockWidgetArea, dock);
|
||||
mainWindow->tabifyDockWidget(statsDock, dock);
|
||||
dock->show();
|
||||
dock->raise();
|
||||
}
|
||||
|
||||
// Get stream stats for selected ports, portgroup by portgroup
|
||||
for (int i = 0; i < portList.size(); i++)
|
||||
{
|
||||
PortGroup &pg = pgl->portGroupByIndex(portList.at(i).portGroupId);
|
||||
if (pg.getStreamStats(&portList[i].portList)) {
|
||||
connect(&pg,SIGNAL(streamStatsReceived(
|
||||
quint32, const OstProto::StreamStatsList*)),
|
||||
streamStatsModel, SLOT(appendStreamStatsList(
|
||||
quint32, const OstProto::StreamStatsList*)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,6 +52,7 @@ private slots:
|
||||
|
||||
void on_tbClear_clicked();
|
||||
void on_tbClearAll_clicked();
|
||||
void on_tbGetStreamStats_clicked();
|
||||
|
||||
void on_tbResolveNeighbors_clicked();
|
||||
void on_tbClearNeighbors_clicked();
|
||||
|
@ -112,6 +112,22 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="tbGetStreamStats" >
|
||||
<property name="toolTip" >
|
||||
<string>Fetch Selected Port Stream Stats</string>
|
||||
</property>
|
||||
<property name="statusTip" >
|
||||
<string>Fetches stream statistics from the selected port(s)</string>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Fetch Stream Stats</string>
|
||||
</property>
|
||||
<property name="icon" >
|
||||
<iconset resource="ostinato.qrc" >:/icons/stream_stats.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_3">
|
||||
<property name="orientation">
|
||||
@ -127,8 +143,8 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="tbStartCapture">
|
||||
<property name="toolTip">
|
||||
<widget class="QToolButton" name="tbStartCapture" >
|
||||
<property name="toolTip" >
|
||||
<string>Start Capture</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
|
@ -755,12 +755,19 @@ void PortsWindow::on_actionPort_Configuration_triggered()
|
||||
if (!plm->isPort(current))
|
||||
return;
|
||||
|
||||
Port &port = plm->port(current);
|
||||
OstProto::Port config;
|
||||
config.set_transmit_mode(plm->port(current).transmitMode());
|
||||
config.set_is_exclusive_control(plm->port(current).hasExclusiveControl());
|
||||
config.set_user_name(plm->port(current).userName().toStdString());
|
||||
// XXX: we don't call Port::protoDataCopyInto() to get config b'coz
|
||||
// we want only the modifiable fields populated to send to Drone
|
||||
// TODO: extend Port::protoDataCopyInto() to accept an optional param
|
||||
// which says copy only modifiable fields
|
||||
//plm->port(current).protoDataCopyInto(&config);
|
||||
config.set_transmit_mode(port.transmitMode());
|
||||
config.set_is_tracking_stream_stats(port.trackStreamStats());
|
||||
config.set_is_exclusive_control(port.hasExclusiveControl());
|
||||
config.set_user_name(port.userName().toStdString());
|
||||
|
||||
PortConfigDialog dialog(config, this);
|
||||
PortConfigDialog dialog(config, port.getStats().state(), this);
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
plm->portGroup(current.parent()).modifyPort(current.row(), config);
|
||||
|
@ -85,13 +85,14 @@ StreamConfigDialog::StreamConfigDialog(
|
||||
|
||||
// Time to play match the signals and slots!
|
||||
|
||||
// If L1/L2(FT)/L3 = None, force subsequent protocol level(s) also to None
|
||||
// If L1/L2(FT)/L3/L4 = None,
|
||||
// force subsequent protocol level(s) also to None
|
||||
connect(rbL1None, SIGNAL(toggled(bool)), SLOT(forceProtocolNone(bool)));
|
||||
connect(rbFtNone, SIGNAL(toggled(bool)), SLOT(forceProtocolNone(bool)));
|
||||
connect(rbL3None, SIGNAL(toggled(bool)), SLOT(forceProtocolNone(bool)));
|
||||
connect(rbL4None, SIGNAL(toggled(bool)), SLOT(forceProtocolNone(bool)));
|
||||
|
||||
// If L1/L2(FT)/L3/L4 = Other, force subsequent protocol to Other and
|
||||
// If L1/L2(FT)/L3/L4/L5 = Other, force subsequent protocol to Other and
|
||||
// disable the subsequent protocol group as well
|
||||
connect(rbL1Other, SIGNAL(toggled(bool)), rbFtOther, SLOT(setChecked(bool)));
|
||||
connect(rbL1Other, SIGNAL(toggled(bool)), gbFrameType, SLOT(setDisabled(bool)));
|
||||
@ -99,8 +100,10 @@ StreamConfigDialog::StreamConfigDialog(
|
||||
connect(rbFtOther, SIGNAL(toggled(bool)), gbL3Proto, SLOT(setDisabled(bool)));
|
||||
connect(rbL3Other, SIGNAL(toggled(bool)), rbL4Other, SLOT(setChecked(bool)));
|
||||
connect(rbL3Other, SIGNAL(toggled(bool)), gbL4Proto, SLOT(setDisabled(bool)));
|
||||
connect(rbL4Other, SIGNAL(toggled(bool)), rbPayloadOther, SLOT(setChecked(bool)));
|
||||
connect(rbL4Other, SIGNAL(toggled(bool)), gbPayloadProto, SLOT(setDisabled(bool)));
|
||||
connect(rbL4Other, SIGNAL(toggled(bool)), rbL5Other, SLOT(setChecked(bool)));
|
||||
connect(rbL4Other, SIGNAL(toggled(bool)), gbL5Proto, SLOT(setDisabled(bool)));
|
||||
connect(rbL5Other, SIGNAL(toggled(bool)), rbPayloadOther, SLOT(setChecked(bool)));
|
||||
connect(rbL5Other, SIGNAL(toggled(bool)), gbPayloadProto, SLOT(setDisabled(bool)));
|
||||
|
||||
// Setup valid subsequent protocols for L2 to L4 protocols
|
||||
for (int i = ProtoL2; i <= ProtoL4; i++)
|
||||
@ -294,6 +297,17 @@ void StreamConfigDialog::setupUiExtra()
|
||||
bgProto[ProtoPayload]->addButton(rbPayloadHexDump, OstProto::Protocol::kHexDumpFieldNumber);
|
||||
bgProto[ProtoPayload]->addButton(rbPayloadOther, ButtonIdOther);
|
||||
#endif
|
||||
|
||||
// Special
|
||||
bgProto[ProtoSign] = new QButtonGroup();
|
||||
bgProto[ProtoSign]->addButton(rbSpecialNone, ButtonIdNone);
|
||||
bgProto[ProtoSign]->addButton(rbSignature,
|
||||
OstProto::Protocol::kSignFieldNumber);
|
||||
// Trailer
|
||||
bgProto[ProtoTrailer] = new QButtonGroup();
|
||||
bgProto[ProtoTrailer]->addButton(rbTrailerNone, ButtonIdNone);
|
||||
bgProto[ProtoTrailer]->addButton(rbTrailerOther, ButtonIdOther);
|
||||
|
||||
/*
|
||||
** Setup Validators
|
||||
*/
|
||||
@ -788,6 +802,8 @@ void StreamConfigDialog::forceProtocolNone(bool checked)
|
||||
}
|
||||
}
|
||||
|
||||
// Button 'newId' has been clicked
|
||||
// - update the protocol list correspondingly
|
||||
void StreamConfigDialog::updateProtocol(int newId)
|
||||
{
|
||||
int level;
|
||||
@ -802,6 +818,8 @@ void StreamConfigDialog::updateProtocol(int newId)
|
||||
__updateProtocol(level, newId);
|
||||
}
|
||||
|
||||
// Button 'newId' belonging to layer 'level' has been clicked
|
||||
// - update the protocol list correspondingly
|
||||
void StreamConfigDialog::__updateProtocol(int level, int newId)
|
||||
{
|
||||
int oldId;
|
||||
@ -849,7 +867,7 @@ void StreamConfigDialog::__updateProtocol(int level, int newId)
|
||||
// Free both protocol and associated widget
|
||||
delete _protocolWidgets.take(p);
|
||||
delete p;
|
||||
if (level == ProtoPayload)
|
||||
if (level == ProtoTrailer)
|
||||
{
|
||||
while (_iter->hasNext())
|
||||
{
|
||||
@ -868,6 +886,7 @@ void StreamConfigDialog::__updateProtocol(int level, int newId)
|
||||
return;
|
||||
}
|
||||
|
||||
//! Traverse the ProtocolList and update the SelectProtocols (Simple) widget
|
||||
void StreamConfigDialog::updateSelectProtocolsSimpleWidget()
|
||||
{
|
||||
int i;
|
||||
@ -893,7 +912,7 @@ void StreamConfigDialog::updateSelectProtocolsSimpleWidget()
|
||||
id = _iter->next()->protocolNumber();
|
||||
btn = bgProto[i]->button(id);
|
||||
|
||||
if (btn)
|
||||
if (btn) // we have a button for this protocol
|
||||
{
|
||||
if (btn->isEnabled())
|
||||
btn->click();
|
||||
@ -903,23 +922,32 @@ void StreamConfigDialog::updateSelectProtocolsSimpleWidget()
|
||||
__updateProtocol(i, id);
|
||||
}
|
||||
}
|
||||
else
|
||||
else // we don't have a button for this protocol
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case ProtoVlan:
|
||||
// No vlan - proto may belong to next layer
|
||||
_iter->previous();
|
||||
break;
|
||||
|
||||
case ProtoSign:
|
||||
// No sign - but we have a trailer
|
||||
_iter->previous();
|
||||
break;
|
||||
|
||||
case ProtoPayload:
|
||||
goto _other;
|
||||
|
||||
default:
|
||||
default: // viz. L1, L2, L3, L4, L5, Trailer
|
||||
// Is this a Payload layer protocol?
|
||||
// (maybe intermediate layers are not present)
|
||||
btn = bgProto[ProtoPayload]->button(id);
|
||||
if (btn && btn->isEnabled())
|
||||
{
|
||||
btn->click();
|
||||
break;
|
||||
i = ProtoPayload;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
goto _other;
|
||||
@ -927,20 +955,22 @@ void StreamConfigDialog::updateSelectProtocolsSimpleWidget()
|
||||
}
|
||||
}
|
||||
|
||||
// If more protocol(s) beyond payload ...
|
||||
// If more protocol(s) beyond trailer ...
|
||||
if (_iter->hasNext())
|
||||
{
|
||||
i = ProtoPayload;
|
||||
i = ProtoTrailer;
|
||||
goto _other;
|
||||
}
|
||||
|
||||
Q_ASSERT(!_iter->hasNext()); // At end of the ProtocolList
|
||||
goto _done;
|
||||
|
||||
_other:
|
||||
// Set remaining protocols as 'Other'
|
||||
for (int j = i; j < ProtoMax; j++)
|
||||
{
|
||||
// VLAN doesn't have a "Other" button
|
||||
if (j == ProtoVlan)
|
||||
// VLAN/Sign doesn't have a "Other" button
|
||||
if ((j == ProtoVlan) || (j == ProtoSign))
|
||||
continue;
|
||||
|
||||
bgProto[j]->button(ButtonIdOther)->setChecked(true);
|
||||
@ -1212,6 +1242,13 @@ bool StreamConfigDialog::isCurrentStreamValid()
|
||||
"varying fields at transmit time may not be same as configured");
|
||||
}
|
||||
|
||||
if (!mPort.trackStreamStats()
|
||||
&& mpStream->hasProtocol(OstProto::Protocol::kSignFieldNumber))
|
||||
{
|
||||
log << tr("Stream contains special signature, but per stream statistics "
|
||||
"will not be available till it is enabled on the port");
|
||||
}
|
||||
|
||||
mpStream->preflightCheck(log);
|
||||
|
||||
if (log.size())
|
||||
|
@ -67,6 +67,8 @@ private:
|
||||
ProtoL4 = 4,
|
||||
ProtoL5 = 5,
|
||||
ProtoPayload = 6,
|
||||
ProtoSign = 7,
|
||||
ProtoTrailer = 8,
|
||||
ProtoMax
|
||||
};
|
||||
|
||||
|
@ -9,8 +9,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>634</width>
|
||||
<height>507</height>
|
||||
<width>647</width>
|
||||
<height>549</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -164,8 +164,8 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-colo
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>592</width>
|
||||
<height>269</height>
|
||||
<width>605</width>
|
||||
<height>311</height>
|
||||
</rect>
|
||||
</property>
|
||||
<attribute name="label">
|
||||
@ -211,9 +211,9 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-colo
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" rowspan="2">
|
||||
<widget class="QGroupBox" name="gbFrameType">
|
||||
<property name="enabled">
|
||||
<item rowspan="3" row="0" column="1" >
|
||||
<widget class="QGroupBox" name="gbFrameType" >
|
||||
<property name="enabled" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="title">
|
||||
@ -394,51 +394,9 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-colo
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QGroupBox" name="gbL5Proto">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>L5</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="rbL5None">
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="rbL5Text">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Text</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="rbL5Other">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Other</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QGroupBox" name="gbVlan">
|
||||
<property name="enabled">
|
||||
<item rowspan="2" row="1" column="0" >
|
||||
<widget class="QGroupBox" name="gbVlan" >
|
||||
<property name="enabled" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="title">
|
||||
@ -560,9 +518,51 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-colo
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QGroupBox" name="gbPayloadProto">
|
||||
<property name="enabled">
|
||||
<item row="2" column="2" >
|
||||
<widget class="QGroupBox" name="gbL5Proto" >
|
||||
<property name="enabled" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="title" >
|
||||
<string>L5</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" >
|
||||
<item>
|
||||
<widget class="QRadioButton" name="rbL5None" >
|
||||
<property name="text" >
|
||||
<string>None</string>
|
||||
</property>
|
||||
<property name="checked" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="rbL5Text" >
|
||||
<property name="enabled" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Text</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="rbL5Other" >
|
||||
<property name="enabled" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Other</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3" >
|
||||
<widget class="QGroupBox" name="gbPayloadProto" >
|
||||
<property name="enabled" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="title">
|
||||
@ -609,6 +609,67 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-colo
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3" >
|
||||
<widget class="QGroupBox" name="gbSpecial" >
|
||||
<property name="enabled" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="title" >
|
||||
<string>Special</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<item>
|
||||
<widget class="QRadioButton" name="rbSpecialNone" >
|
||||
<property name="text" >
|
||||
<string>None</string>
|
||||
</property>
|
||||
<property name="checked" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="rbSignature" >
|
||||
<property name="text" >
|
||||
<string>Signature</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3" >
|
||||
<widget class="QGroupBox" name="gbTrailer" >
|
||||
<property name="enabled" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="title" >
|
||||
<string>Trailer</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<item>
|
||||
<widget class="QRadioButton" name="rbTrailerNone" >
|
||||
<property name="text" >
|
||||
<string>None</string>
|
||||
</property>
|
||||
<property name="checked" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="rbTrailerOther" >
|
||||
<property name="enabled" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Other</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page_2">
|
||||
|
50
client/streamstatsfiltermodel.h
Normal file
50
client/streamstatsfiltermodel.h
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright (C) 2017 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/>
|
||||
*/
|
||||
|
||||
#ifndef _STREAM_STATS_FILTER_MODEL_H
|
||||
#define _STREAM_STATS_FILTER_MODEL_H
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
class StreamStatsFilterModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
StreamStatsFilterModel(QObject *parent = 0)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
bool filterAcceptsColumn(int sourceColumn,
|
||||
const QModelIndex &/*sourceParent*/) const
|
||||
{
|
||||
QString title = sourceModel()->headerData(sourceColumn, Qt::Horizontal)
|
||||
.toString();
|
||||
return filterRegExp().exactMatch(title) ? true : false;
|
||||
}
|
||||
bool filterAcceptsRow(int /*sourceRow*/,
|
||||
const QModelIndex &/*sourceParent*/) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
234
client/streamstatsmodel.cpp
Normal file
234
client/streamstatsmodel.cpp
Normal file
@ -0,0 +1,234 @@
|
||||
/*
|
||||
Copyright (C) 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 "streamstatsmodel.h"
|
||||
|
||||
#include "protocol.pb.h"
|
||||
|
||||
#include <QBrush>
|
||||
|
||||
// XXX: Keep the enum in sync with it's string
|
||||
enum {
|
||||
kTxPkts,
|
||||
kRxPkts,
|
||||
kTxBytes,
|
||||
kRxBytes,
|
||||
kMaxStreamStats
|
||||
};
|
||||
static QStringList statTitles = QStringList()
|
||||
<< "Tx Pkts"
|
||||
<< "Rx Pkts"
|
||||
<< "Tx Bytes"
|
||||
<< "Rx Bytes";
|
||||
|
||||
// XXX: Keep the enum in sync with it's string
|
||||
enum {
|
||||
kAggrTxPkts,
|
||||
kAggrRxPkts,
|
||||
kAggrPktLoss,
|
||||
kMaxAggrStreamStats
|
||||
};
|
||||
static QStringList aggrStatTitles = QStringList()
|
||||
<< "Total\nTx Pkts"
|
||||
<< "Total\nRx Pkts"
|
||||
<< "Total\nPkt Loss";
|
||||
|
||||
static const uint kAggrGuid = 0xffffffff;
|
||||
|
||||
StreamStatsModel::StreamStatsModel(QObject *parent)
|
||||
: QAbstractTableModel(parent)
|
||||
{
|
||||
clearStats();
|
||||
}
|
||||
|
||||
int StreamStatsModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return guidList_.size();
|
||||
}
|
||||
|
||||
int StreamStatsModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (!portList_.size())
|
||||
return 0;
|
||||
|
||||
return kMaxAggrStreamStats + portList_.size() * kMaxStreamStats;
|
||||
}
|
||||
|
||||
QVariant StreamStatsModel::headerData(
|
||||
int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (role != Qt::DisplayRole)
|
||||
return QVariant();
|
||||
|
||||
switch (orientation) {
|
||||
case Qt::Horizontal: // Column Header
|
||||
if (section < kMaxAggrStreamStats)
|
||||
return aggrStatTitles.at(section % kMaxAggrStreamStats);
|
||||
|
||||
section -= kMaxAggrStreamStats;
|
||||
return QString("Port %1-%2\n%3")
|
||||
.arg(portList_.at(section/kMaxStreamStats).first)
|
||||
.arg(portList_.at(section/kMaxStreamStats).second)
|
||||
.arg(statTitles.at(section % kMaxStreamStats));
|
||||
case Qt::Vertical: // Row Header
|
||||
if (section == (guidList_.size() - 1))
|
||||
return QString("GUID Total");
|
||||
return QString("Stream GUID %1")
|
||||
.arg(guidList_.at(section));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant StreamStatsModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (role == Qt::TextAlignmentRole)
|
||||
return Qt::AlignRight;
|
||||
|
||||
int portColumn = index.column() - kMaxAggrStreamStats;
|
||||
if (role == Qt::BackgroundRole) {
|
||||
if (portColumn < 0)
|
||||
return QBrush(QColor("lavender")); // Aggregate Column
|
||||
if (index.row() == (guidList_.size() - 1))
|
||||
return QBrush(QColor("burlywood")); // Aggregate Row
|
||||
else if ((portColumn/kMaxStreamStats) & 1)
|
||||
return QBrush(QColor("beige")); // Color alternate Ports
|
||||
}
|
||||
|
||||
Guid guid = guidList_.at(index.row());
|
||||
if (role == Qt::ForegroundRole) {
|
||||
if ((index.column() == kAggrPktLoss)
|
||||
&& aggrGuidStats_.value(guid).pktLoss)
|
||||
return QBrush(QColor("firebrick"));
|
||||
}
|
||||
|
||||
if (role != Qt::DisplayRole)
|
||||
return QVariant();
|
||||
|
||||
if (index.column() < kMaxAggrStreamStats) {
|
||||
int stat = index.column() % kMaxAggrStreamStats;
|
||||
switch (stat) {
|
||||
case kAggrRxPkts:
|
||||
return QString("%L1").arg(aggrGuidStats_.value(guid).rxPkts);
|
||||
case kAggrTxPkts:
|
||||
return QString("%L1").arg(aggrGuidStats_.value(guid).txPkts);
|
||||
case kAggrPktLoss:
|
||||
return QString("%L1").arg(aggrGuidStats_.value(guid).pktLoss);
|
||||
default:
|
||||
break;
|
||||
};
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
PortGroupPort pgp = portList_.at(portColumn/kMaxStreamStats);
|
||||
int stat = portColumn % kMaxStreamStats;
|
||||
|
||||
switch (stat) {
|
||||
case kRxPkts:
|
||||
return QString("%L1").arg(streamStats_.value(guid).value(pgp).rxPkts);
|
||||
case kTxPkts:
|
||||
return QString("%L1").arg(streamStats_.value(guid).value(pgp).txPkts);
|
||||
case kRxBytes:
|
||||
return QString("%L1").arg(streamStats_.value(guid).value(pgp).rxBytes);
|
||||
case kTxBytes:
|
||||
return QString("%L1").arg(streamStats_.value(guid).value(pgp).txBytes);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
// --------------------------------------------- //
|
||||
// Slots
|
||||
// --------------------------------------------- //
|
||||
void StreamStatsModel::clearStats()
|
||||
{
|
||||
#if QT_VERSION >= 0x040600
|
||||
beginResetModel();
|
||||
#endif
|
||||
|
||||
guidList_.clear();
|
||||
portList_.clear();
|
||||
streamStats_.clear();
|
||||
aggrGuidStats_.clear();
|
||||
|
||||
#if QT_VERSION >= 0x040600
|
||||
endResetModel();
|
||||
#else
|
||||
reset();
|
||||
#endif
|
||||
}
|
||||
|
||||
void StreamStatsModel::appendStreamStatsList(
|
||||
quint32 portGroupId,
|
||||
const OstProto::StreamStatsList *stats)
|
||||
{
|
||||
int n = stats->stream_stats_size();
|
||||
|
||||
#if QT_VERSION >= 0x040600
|
||||
beginResetModel();
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
const OstProto::StreamStats &s = stats->stream_stats(i);
|
||||
PortGroupPort pgp = PortGroupPort(portGroupId, s.port_id().id());
|
||||
Guid guid = s.stream_guid().id();
|
||||
StreamStats &ss = streamStats_[guid][pgp];
|
||||
StreamStats &aggrPort = streamStats_[kAggrGuid][pgp];
|
||||
AggrGuidStats &aggrGuid = aggrGuidStats_[guid];
|
||||
AggrGuidStats &aggrAggr = aggrGuidStats_[kAggrGuid];
|
||||
|
||||
ss.rxPkts = s.rx_pkts();
|
||||
ss.txPkts = s.tx_pkts();
|
||||
ss.rxBytes = s.rx_bytes();
|
||||
ss.txBytes = s.tx_bytes();
|
||||
|
||||
aggrPort.rxPkts += ss.rxPkts;
|
||||
aggrPort.txPkts += ss.txPkts;
|
||||
aggrPort.rxBytes += ss.rxBytes;
|
||||
aggrPort.txBytes += ss.txBytes;
|
||||
|
||||
aggrGuid.rxPkts += ss.rxPkts;
|
||||
aggrGuid.txPkts += ss.txPkts;
|
||||
aggrGuid.pktLoss += ss.txPkts - ss.rxPkts;
|
||||
|
||||
aggrAggr.rxPkts += ss.rxPkts;
|
||||
aggrAggr.txPkts += ss.txPkts;
|
||||
aggrAggr.pktLoss += ss.txPkts - ss.rxPkts;
|
||||
|
||||
if (!portList_.contains(pgp))
|
||||
portList_.append(pgp);
|
||||
if (!guidList_.contains(guid))
|
||||
guidList_.append(guid);
|
||||
}
|
||||
|
||||
if (guidList_.size())
|
||||
guidList_.append(kAggrGuid);
|
||||
|
||||
#if QT_VERSION >= 0x040600
|
||||
endResetModel();
|
||||
#else
|
||||
reset();
|
||||
#endif
|
||||
|
||||
// Prevent receiving any future updates from this sender
|
||||
disconnect(sender(), 0, this, 0);
|
||||
}
|
70
client/streamstatsmodel.h
Normal file
70
client/streamstatsmodel.h
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
Copyright (C) 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/>
|
||||
*/
|
||||
|
||||
#ifndef _STREAM_STATS_MODEL_H
|
||||
#define _STREAM_STATS_MODEL_H
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QPair>
|
||||
#include <QStringList>
|
||||
|
||||
namespace OstProto {
|
||||
class StreamStatsList;
|
||||
}
|
||||
|
||||
class StreamStatsModel: public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
StreamStatsModel(QObject *parent = 0);
|
||||
|
||||
int rowCount(const QModelIndex &parent=QModelIndex()) const;
|
||||
int columnCount(const QModelIndex &parent=QModelIndex()) const;
|
||||
|
||||
QVariant headerData(int section, Qt::Orientation orientation,
|
||||
int role = Qt::DisplayRole) const;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
|
||||
|
||||
public slots:
|
||||
void clearStats();
|
||||
void appendStreamStatsList(quint32 portGroupId,
|
||||
const OstProto::StreamStatsList *stats);
|
||||
private:
|
||||
typedef QPair<uint, uint> PortGroupPort; // Pair = (PortGroupId, PortId)
|
||||
typedef uint Guid;
|
||||
struct StreamStats {
|
||||
quint64 rxPkts;
|
||||
quint64 txPkts;
|
||||
quint64 rxBytes;
|
||||
quint64 txBytes;
|
||||
};
|
||||
struct AggrGuidStats {
|
||||
quint64 rxPkts;
|
||||
quint64 txPkts;
|
||||
qint64 pktLoss;
|
||||
};
|
||||
QList<Guid> guidList_;
|
||||
QList<PortGroupPort> portList_;
|
||||
QHash<Guid, QHash<PortGroupPort, StreamStats> > streamStats_;
|
||||
QHash<Guid, AggrGuidStats> aggrGuidStats_;
|
||||
};
|
||||
#endif
|
||||
|
65
client/streamstatswindow.cpp
Normal file
65
client/streamstatswindow.cpp
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
Copyright (C) 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 "streamstatswindow.h"
|
||||
|
||||
#include "streamstatsfiltermodel.h"
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QHeaderView>
|
||||
|
||||
static int id;
|
||||
static int count;
|
||||
|
||||
StreamStatsWindow::StreamStatsWindow(QAbstractItemModel *model, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
setupUi(this);
|
||||
streamStats->addAction(actionShowByteCounters);
|
||||
|
||||
if (id)
|
||||
setWindowTitle(windowTitle() + QString("(%1)").arg(id));
|
||||
id++;
|
||||
count++;
|
||||
|
||||
filterModel_ = new StreamStatsFilterModel(this);
|
||||
filterModel_->setFilterRegExp(QRegExp(".*Pkt.*"));
|
||||
filterModel_->setSourceModel(model);
|
||||
streamStats->setModel(filterModel_);
|
||||
|
||||
streamStats->verticalHeader()->setHighlightSections(false);
|
||||
streamStats->verticalHeader()->setDefaultSectionSize(
|
||||
streamStats->verticalHeader()->minimumSectionSize());
|
||||
}
|
||||
|
||||
StreamStatsWindow::~StreamStatsWindow()
|
||||
{
|
||||
delete filterModel_;
|
||||
count--;
|
||||
if (count == 0)
|
||||
id = 0;
|
||||
}
|
||||
|
||||
void StreamStatsWindow::on_actionShowByteCounters_triggered(bool checked)
|
||||
{
|
||||
if (checked)
|
||||
filterModel_->setFilterRegExp(QRegExp(".*"));
|
||||
else
|
||||
filterModel_->setFilterRegExp(QRegExp(".*Pkt.*"));
|
||||
}
|
43
client/streamstatswindow.h
Normal file
43
client/streamstatswindow.h
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright (C) 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/>
|
||||
*/
|
||||
|
||||
#ifndef _STREAM_STATS_WINDOW_H
|
||||
#define _STREAM_STATS_WINDOW_H
|
||||
|
||||
#include "ui_streamstatswindow.h"
|
||||
|
||||
class QAbstractItemModel;
|
||||
class QSortFilterProxyModel;
|
||||
|
||||
class StreamStatsWindow: public QWidget, private Ui::StreamStatsWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
StreamStatsWindow(QAbstractItemModel *model, QWidget *parent = 0);
|
||||
~StreamStatsWindow();
|
||||
|
||||
private slots:
|
||||
void on_actionShowByteCounters_triggered(bool checked);
|
||||
|
||||
private:
|
||||
QSortFilterProxyModel *filterModel_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
35
client/streamstatswindow.ui
Normal file
35
client/streamstatswindow.ui
Normal file
@ -0,0 +1,35 @@
|
||||
<ui version="4.0" >
|
||||
<class>StreamStatsWindow</class>
|
||||
<widget class="QWidget" name="StreamStatsWindow" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>551</width>
|
||||
<height>452</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Stream Statistics</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" >
|
||||
<item>
|
||||
<widget class="QTableView" name="streamStats" >
|
||||
<property name="contextMenuPolicy" >
|
||||
<enum>Qt::ActionsContextMenu</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<action name="actionShowByteCounters" >
|
||||
<property name="checkable" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Show Byte Counters</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -22,6 +22,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#include "protocollistiterator.h"
|
||||
#include "streambase.h"
|
||||
|
||||
#include "bswap.h"
|
||||
|
||||
#include <qendian.h>
|
||||
|
||||
/*!
|
||||
@ -972,18 +974,29 @@ out:
|
||||
quint32 AbstractProtocol::protocolFramePayloadCksum(int streamIndex,
|
||||
CksumType cksumType, CksumScope cksumScope) const
|
||||
{
|
||||
quint32 sum = 0;
|
||||
quint32 sum;
|
||||
quint16 cksum;
|
||||
AbstractProtocol *p = next;
|
||||
|
||||
Q_ASSERT(cksumType == CksumIp);
|
||||
|
||||
if (!p)
|
||||
return 0xFFFF;
|
||||
|
||||
cksum = p->protocolFrameCksum(streamIndex, cksumType);
|
||||
sum = (quint16) ~cksum;
|
||||
if (cksumScope == CksumScopeAdjacentProtocol)
|
||||
goto out;
|
||||
|
||||
p = p->next;
|
||||
while (p)
|
||||
{
|
||||
// when combining cksums, a non-first protocol starting at odd offset
|
||||
// needs a byte swap (see RFC 1071 section(s) 2A, 2B)
|
||||
cksum = p->protocolFrameCksum(streamIndex, cksumType);
|
||||
if (p->protocolFrameOffset(streamIndex) & 0x1)
|
||||
cksum = swap16(cksum);
|
||||
sum += (quint16) ~cksum;
|
||||
if (cksumScope == CksumScopeAdjacentProtocol)
|
||||
goto out;
|
||||
p = p->next;
|
||||
}
|
||||
|
||||
@ -998,7 +1011,9 @@ out:
|
||||
while(sum>>16)
|
||||
sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
|
||||
return (quint16) ~sum;
|
||||
cksum = (quint16) ~sum;
|
||||
qDebug("%s: cksum = %u", __FUNCTION__, cksum);
|
||||
return cksum;
|
||||
}
|
||||
|
||||
// Stein's binary GCD algo - from wikipedia
|
||||
|
39
common/bswap.h
Normal file
39
common/bswap.h
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
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/>
|
||||
*/
|
||||
|
||||
#ifndef _B_SWAP_H
|
||||
#define _B_SWAP_H
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
static inline quint32 swap32(quint32 val)
|
||||
{
|
||||
return (((val >> 24) & 0x000000FF) |
|
||||
((val >> 16) & 0x0000FF00) |
|
||||
((val << 16) & 0x00FF0000) |
|
||||
((val << 24) & 0xFF000000));
|
||||
}
|
||||
|
||||
static inline quint16 swap16(quint16 val)
|
||||
{
|
||||
return (((val >> 8) & 0x00FF) |
|
||||
((val << 8) & 0xFF00));
|
||||
}
|
||||
|
||||
#endif
|
@ -200,7 +200,10 @@ int HexDumpProtocol::protocolFrameSize(int streamIndex) const
|
||||
if (data.pad_until_end())
|
||||
{
|
||||
int pad = mpStream->frameLen(streamIndex)
|
||||
- (protocolFrameOffset(streamIndex) + len + kFcsSize);
|
||||
- (protocolFrameOffset(streamIndex)
|
||||
+ len
|
||||
+ protocolFramePayloadSize(streamIndex)
|
||||
+ kFcsSize);
|
||||
if (pad < 0)
|
||||
pad = 0;
|
||||
len += pad;
|
||||
|
@ -38,7 +38,8 @@ PROTOS += \
|
||||
textproto.proto \
|
||||
userscript.proto \
|
||||
hexdump.proto \
|
||||
sample.proto
|
||||
sample.proto \
|
||||
sign.proto
|
||||
|
||||
HEADERS = \
|
||||
abstractprotocol.h \
|
||||
@ -77,6 +78,7 @@ HEADERS += \
|
||||
hexdump.h \
|
||||
payload.h \
|
||||
sample.h \
|
||||
sign.h \
|
||||
userscript.h
|
||||
|
||||
SOURCES = \
|
||||
@ -110,6 +112,7 @@ SOURCES += \
|
||||
hexdump.cpp \
|
||||
payload.cpp \
|
||||
sample.cpp \
|
||||
sign.cpp \
|
||||
userscript.cpp
|
||||
|
||||
QMAKE_DISTCLEAN += object_script.*
|
||||
|
@ -27,6 +27,7 @@ FORMS += \
|
||||
hexdump.ui \
|
||||
payload.ui \
|
||||
sample.ui \
|
||||
sign.ui \
|
||||
userscript.ui
|
||||
|
||||
PROTOS = \
|
||||
@ -79,6 +80,7 @@ HEADERS += \
|
||||
hexdumpconfig.h \
|
||||
payloadconfig.h \
|
||||
sampleconfig.h \
|
||||
signconfig.h \
|
||||
userscriptconfig.h
|
||||
|
||||
SOURCES += \
|
||||
@ -118,6 +120,7 @@ SOURCES += \
|
||||
hexdumpconfig.cpp \
|
||||
payloadconfig.cpp \
|
||||
sampleconfig.cpp \
|
||||
signconfig.cpp \
|
||||
userscriptconfig.cpp
|
||||
|
||||
SOURCES += \
|
||||
|
@ -68,7 +68,7 @@ int PayloadProtocol::protocolFrameSize(int streamIndex) const
|
||||
int len;
|
||||
|
||||
len = mpStream->frameLen(streamIndex) - protocolFrameOffset(streamIndex)
|
||||
- kFcsSize;
|
||||
- protocolFramePayloadSize(streamIndex) - kFcsSize;
|
||||
|
||||
if (len < 0)
|
||||
len = 0;
|
||||
|
@ -31,20 +31,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#include <QTemporaryFile>
|
||||
#include <QtGlobal>
|
||||
|
||||
static inline quint32 swap32(quint32 val)
|
||||
{
|
||||
return (((val >> 24) && 0x000000FF) |
|
||||
((val >> 16) && 0x0000FF00) |
|
||||
((val << 16) && 0x00FF0000) |
|
||||
((val << 24) && 0xFF000000));
|
||||
}
|
||||
|
||||
static inline quint16 swap16(quint16 val)
|
||||
{
|
||||
return (((val >> 8) && 0x00FF) |
|
||||
((val << 8) && 0xFF00));
|
||||
}
|
||||
|
||||
const quint32 kPcapFileMagic = 0xa1b2c3d4;
|
||||
const quint32 kPcapFileMagicSwapped = 0xd4c3b2a1;
|
||||
const quint16 kPcapFileVersionMajor = 2;
|
||||
|
@ -128,6 +128,7 @@ message Protocol {
|
||||
kSampleFieldNumber = 102;
|
||||
kUserScriptFieldNumber = 103;
|
||||
kHexDumpFieldNumber = 104;
|
||||
kSignFieldNumber = 105;
|
||||
|
||||
kEth2FieldNumber = 200;
|
||||
kDot3FieldNumber = 201;
|
||||
@ -204,6 +205,7 @@ message Port {
|
||||
optional bool is_exclusive_control = 6;
|
||||
optional TransmitMode transmit_mode = 7 [default = kSequentialTransmit];
|
||||
optional string user_name = 8;
|
||||
optional bool is_tracking_stream_stats = 9;
|
||||
}
|
||||
|
||||
message PortConfigList {
|
||||
@ -265,6 +267,29 @@ message PortStatsList {
|
||||
repeated PortStats port_stats = 1;
|
||||
}
|
||||
|
||||
message StreamGuid {
|
||||
required uint32 id = 1;
|
||||
}
|
||||
|
||||
message StreamGuidList {
|
||||
required PortIdList port_id_list = 1;
|
||||
repeated StreamGuid stream_guid = 2;
|
||||
}
|
||||
|
||||
message StreamStats {
|
||||
required PortId port_id = 1;
|
||||
required StreamGuid stream_guid = 2;
|
||||
|
||||
optional uint64 rx_pkts = 11;
|
||||
optional uint64 rx_bytes = 12;
|
||||
optional uint64 tx_pkts = 13;
|
||||
optional uint64 tx_bytes = 14;
|
||||
}
|
||||
|
||||
message StreamStatsList {
|
||||
repeated StreamStats stream_stats = 1;
|
||||
}
|
||||
|
||||
enum NotifType {
|
||||
portConfigChanged = 1;
|
||||
}
|
||||
@ -358,5 +383,11 @@ service OstService {
|
||||
rpc resolveDeviceNeighbors(PortIdList) returns (Ack);
|
||||
rpc clearDeviceNeighbors(PortIdList) returns (Ack);
|
||||
rpc getDeviceNeighbors(PortId) returns (PortNeighborList);
|
||||
|
||||
// Stream Stats
|
||||
rpc getStreamStats(StreamGuidList) returns (StreamStatsList);
|
||||
rpc clearStreamStats(StreamGuidList) returns (Ack);
|
||||
|
||||
// XXX: Add new RPCs at the end only to preserve backward compatibility
|
||||
}
|
||||
|
||||
|
@ -59,6 +59,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#include "hexdump.h"
|
||||
#include "payload.h"
|
||||
#include "sample.h"
|
||||
#include "sign.h"
|
||||
#include "userscript.h"
|
||||
|
||||
ProtocolManager *OstProtocolManager;
|
||||
@ -133,6 +134,8 @@ ProtocolManager::ProtocolManager()
|
||||
(void*) PayloadProtocol::createInstance);
|
||||
registerProtocol(OstProto::Protocol::kSampleFieldNumber,
|
||||
(void*) SampleProtocol::createInstance);
|
||||
registerProtocol(OstProto::Protocol::kSignFieldNumber,
|
||||
(void*) SignProtocol::createInstance);
|
||||
registerProtocol(OstProto::Protocol::kUserScriptFieldNumber,
|
||||
(void*) UserScriptProtocol::createInstance);
|
||||
|
||||
|
@ -51,6 +51,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#include "hexdumpconfig.h"
|
||||
#include "payloadconfig.h"
|
||||
#include "sampleconfig.h"
|
||||
#include "signconfig.h"
|
||||
#include "userscriptconfig.h"
|
||||
|
||||
ProtocolWidgetFactory *OstProtocolWidgetFactory;
|
||||
@ -154,6 +155,9 @@ ProtocolWidgetFactory::ProtocolWidgetFactory()
|
||||
OstProtocolWidgetFactory->registerProtocolConfigWidget(
|
||||
OstProto::Protocol::kSampleFieldNumber,
|
||||
(void*) SampleConfigForm::createInstance);
|
||||
OstProtocolWidgetFactory->registerProtocolConfigWidget(
|
||||
OstProto::Protocol::kSignFieldNumber,
|
||||
(void*) SignConfigForm::createInstance);
|
||||
OstProtocolWidgetFactory->registerProtocolConfigWidget(
|
||||
OstProto::Protocol::kUserScriptFieldNumber,
|
||||
(void*) UserScriptConfigForm::createInstance);
|
||||
|
219
common/sign.cpp
Normal file
219
common/sign.cpp
Normal file
@ -0,0 +1,219 @@
|
||||
/*
|
||||
Copyright (C) 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 "sign.h"
|
||||
|
||||
SignProtocol::SignProtocol(StreamBase *stream, AbstractProtocol *parent)
|
||||
: AbstractProtocol(stream, parent)
|
||||
{
|
||||
}
|
||||
|
||||
SignProtocol::~SignProtocol()
|
||||
{
|
||||
}
|
||||
|
||||
AbstractProtocol* SignProtocol::createInstance(StreamBase *stream,
|
||||
AbstractProtocol *parent)
|
||||
{
|
||||
return new SignProtocol(stream, parent);
|
||||
}
|
||||
|
||||
quint32 SignProtocol::protocolNumber() const
|
||||
{
|
||||
return OstProto::Protocol::kSignFieldNumber;
|
||||
}
|
||||
|
||||
void SignProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const
|
||||
{
|
||||
protocol.MutableExtension(OstProto::sign)->CopyFrom(data);
|
||||
protocol.mutable_protocol_id()->set_id(protocolNumber());
|
||||
}
|
||||
|
||||
void SignProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol)
|
||||
{
|
||||
if (protocol.protocol_id().id() == protocolNumber() &&
|
||||
protocol.HasExtension(OstProto::sign))
|
||||
data.MergeFrom(protocol.GetExtension(OstProto::sign));
|
||||
}
|
||||
|
||||
QString SignProtocol::name() const
|
||||
{
|
||||
return QString("Signature");
|
||||
}
|
||||
|
||||
QString SignProtocol::shortName() const
|
||||
{
|
||||
return QString("SIGN");
|
||||
}
|
||||
|
||||
int SignProtocol::fieldCount() const
|
||||
{
|
||||
return sign_fieldCount;
|
||||
}
|
||||
|
||||
AbstractProtocol::FieldFlags SignProtocol::fieldFlags(int index) const
|
||||
{
|
||||
AbstractProtocol::FieldFlags flags;
|
||||
|
||||
flags = AbstractProtocol::fieldFlags(index);
|
||||
|
||||
switch (index)
|
||||
{
|
||||
case sign_magic:
|
||||
case sign_tlv_guid:
|
||||
case sign_tlv_end:
|
||||
break;
|
||||
|
||||
default:
|
||||
qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__,
|
||||
index);
|
||||
break;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
QVariant SignProtocol::fieldData(int index, FieldAttrib attrib,
|
||||
int streamIndex) const
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case sign_magic:
|
||||
{
|
||||
switch(attrib)
|
||||
{
|
||||
case FieldName:
|
||||
return QString("Magic");
|
||||
case FieldValue:
|
||||
return kSignMagic;
|
||||
case FieldTextValue:
|
||||
return QString("%1").arg(kSignMagic);
|
||||
case FieldFrameValue:
|
||||
{
|
||||
QByteArray fv;
|
||||
fv.resize(4);
|
||||
qToBigEndian(kSignMagic, (uchar*) fv.data());
|
||||
return fv;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case sign_tlv_guid:
|
||||
{
|
||||
quint32 guid = data.stream_guid() & 0xFFFFFF;
|
||||
switch(attrib)
|
||||
{
|
||||
case FieldName:
|
||||
return QString("Stream GUID");
|
||||
case FieldValue:
|
||||
return guid;
|
||||
case FieldTextValue:
|
||||
return QString("%1").arg(guid);
|
||||
case FieldFrameValue:
|
||||
{
|
||||
QByteArray fv;
|
||||
fv.resize(4);
|
||||
fv[0] = (guid >> 16) & 0xff;
|
||||
fv[1] = (guid >> 8) & 0xff;
|
||||
fv[2] = (guid >> 0) & 0xff;
|
||||
fv[3] = kTypeLenGuid;
|
||||
return fv;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case sign_tlv_end:
|
||||
{
|
||||
switch(attrib)
|
||||
{
|
||||
case FieldName:
|
||||
return QString("End TLV");
|
||||
case FieldValue:
|
||||
return 0;
|
||||
case FieldTextValue:
|
||||
return QString("NA");
|
||||
case FieldFrameValue:
|
||||
return QByteArray(1, kTypeLenEnd);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__,
|
||||
index);
|
||||
break;
|
||||
}
|
||||
|
||||
return AbstractProtocol::fieldData(index, attrib, streamIndex);
|
||||
}
|
||||
|
||||
bool SignProtocol::setFieldData(int index, const QVariant &value,
|
||||
FieldAttrib attrib)
|
||||
{
|
||||
bool isOk = false;
|
||||
|
||||
if (attrib != FieldValue)
|
||||
goto _exit;
|
||||
|
||||
switch (index)
|
||||
{
|
||||
case sign_tlv_guid:
|
||||
{
|
||||
uint guid = value.toUInt(&isOk);
|
||||
if (isOk)
|
||||
data.set_stream_guid(guid & 0xFFFFFF);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__,
|
||||
index);
|
||||
break;
|
||||
}
|
||||
|
||||
_exit:
|
||||
return isOk;
|
||||
}
|
||||
|
||||
quint32 SignProtocol::magic()
|
||||
{
|
||||
return kSignMagic;
|
||||
}
|
||||
|
||||
bool SignProtocol::packetGuid(const uchar *pkt, int pktLen, uint *guid)
|
||||
{
|
||||
const uchar *p = pkt + pktLen - sizeof(kSignMagic);
|
||||
quint32 magic = qFromBigEndian<quint32>(p);
|
||||
if (magic != kSignMagic)
|
||||
return false;
|
||||
|
||||
p--;
|
||||
while (*p != kTypeLenEnd) {
|
||||
if (*p == kTypeLenGuid) {
|
||||
*guid = qFromBigEndian<quint32>(p - 3) >> 8;
|
||||
return true;
|
||||
}
|
||||
p -= 1 + (*p >> 5); // move to next TLV
|
||||
}
|
||||
return false;
|
||||
}
|
93
common/sign.h
Normal file
93
common/sign.h
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
Copyright (C) 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/>
|
||||
*/
|
||||
|
||||
#ifndef _SIGN_H
|
||||
#define _SIGN_H
|
||||
|
||||
#include "abstractprotocol.h"
|
||||
#include "sign.pb.h"
|
||||
|
||||
/*
|
||||
Sign Protocol is expected at the end of the frame (just before the Eth FCS)
|
||||
---+--------+-------+
|
||||
. . .| TLV(s) | Magic |
|
||||
| (8+) | (32) |
|
||||
---+--------+-------+
|
||||
Figures in brackets represent field width in bits
|
||||
|
||||
TLVs are encoded as
|
||||
+-------+-----+------+
|
||||
| Value | Len | Type |
|
||||
| (...) | (3) | (5) |
|
||||
+-------+-----+------+
|
||||
Len does NOT include the one byte of TypeLen
|
||||
Size of the value field varies between 0 to 7 bytes
|
||||
|
||||
Defined TLVs
|
||||
Type = 0, Len = 0 (0x00): End of TLVs
|
||||
Type = 1, Len = 3 (0x61): Stream GUID
|
||||
*/
|
||||
|
||||
class SignProtocol : public AbstractProtocol
|
||||
{
|
||||
public:
|
||||
enum samplefield
|
||||
{
|
||||
// Frame Fields
|
||||
sign_tlv_end = 0,
|
||||
sign_tlv_guid,
|
||||
sign_magic,
|
||||
|
||||
// Meta Fields
|
||||
// - None
|
||||
|
||||
sign_fieldCount
|
||||
};
|
||||
|
||||
SignProtocol(StreamBase *stream, AbstractProtocol *parent = 0);
|
||||
virtual ~SignProtocol();
|
||||
|
||||
static AbstractProtocol* createInstance(StreamBase *stream,
|
||||
AbstractProtocol *parent = 0);
|
||||
virtual quint32 protocolNumber() const;
|
||||
|
||||
virtual void protoDataCopyInto(OstProto::Protocol &protocol) const;
|
||||
virtual void protoDataCopyFrom(const OstProto::Protocol &protocol);
|
||||
|
||||
virtual QString name() const;
|
||||
virtual QString shortName() const;
|
||||
|
||||
virtual int fieldCount() const;
|
||||
|
||||
virtual AbstractProtocol::FieldFlags fieldFlags(int index) const;
|
||||
virtual QVariant fieldData(int index, FieldAttrib attrib,
|
||||
int streamIndex = 0) const;
|
||||
virtual bool setFieldData(int index, const QVariant &value,
|
||||
FieldAttrib attrib = FieldValue);
|
||||
|
||||
static quint32 magic();
|
||||
static bool packetGuid(const uchar *pkt, int pktLen, uint *guid);
|
||||
private:
|
||||
static const quint32 kSignMagic = 0x1d10c0da; // coda! (unicode - 0x1d10c)
|
||||
static const quint8 kTypeLenEnd = 0x00;
|
||||
static const quint8 kTypeLenGuid = 0x61;
|
||||
OstProto::Sign data;
|
||||
};
|
||||
|
||||
#endif
|
31
common/sign.proto
Normal file
31
common/sign.proto
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright (C) 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/>
|
||||
*/
|
||||
|
||||
import "protocol.proto";
|
||||
|
||||
package OstProto;
|
||||
|
||||
// Sign Protocol
|
||||
message Sign {
|
||||
optional uint32 stream_guid = 1;
|
||||
}
|
||||
|
||||
extend Protocol {
|
||||
optional Sign sign = 105;
|
||||
}
|
59
common/sign.ui
Normal file
59
common/sign.ui
Normal file
@ -0,0 +1,59 @@
|
||||
<ui version="4.0" >
|
||||
<class>Sign</class>
|
||||
<widget class="QWidget" name="Sign" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>64</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" >
|
||||
<item row="0" column="0" >
|
||||
<widget class="QLabel" name="label" >
|
||||
<property name="text" >
|
||||
<string>Stream GUID</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>guid</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" >
|
||||
<widget class="QLineEdit" name="guid" />
|
||||
</item>
|
||||
<item row="0" column="2" >
|
||||
<spacer>
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" >
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="1" >
|
||||
<spacer>
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" >
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
53
common/signconfig.cpp
Normal file
53
common/signconfig.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright (C) 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 "signconfig.h"
|
||||
#include "sign.h"
|
||||
|
||||
SignConfigForm::SignConfigForm(QWidget *parent)
|
||||
: AbstractProtocolConfigForm(parent)
|
||||
{
|
||||
setupUi(this);
|
||||
}
|
||||
|
||||
SignConfigForm::~SignConfigForm()
|
||||
{
|
||||
}
|
||||
|
||||
SignConfigForm* SignConfigForm::createInstance()
|
||||
{
|
||||
return new SignConfigForm;
|
||||
}
|
||||
|
||||
void SignConfigForm::loadWidget(AbstractProtocol *proto)
|
||||
{
|
||||
guid->setText(
|
||||
proto->fieldData(
|
||||
SignProtocol::sign_tlv_guid,
|
||||
AbstractProtocol::FieldValue
|
||||
).toString());
|
||||
}
|
||||
|
||||
void SignConfigForm::storeWidget(AbstractProtocol *proto)
|
||||
{
|
||||
proto->setFieldData(
|
||||
SignProtocol::sign_tlv_guid,
|
||||
guid->text());
|
||||
}
|
||||
|
43
common/signconfig.h
Normal file
43
common/signconfig.h
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright (C) 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/>
|
||||
*/
|
||||
|
||||
#ifndef _SIGN_CONFIG_H
|
||||
#define _SIGN_CONFIG_H
|
||||
|
||||
#include "abstractprotocolconfig.h"
|
||||
#include "ui_sign.h"
|
||||
|
||||
class SignConfigForm :
|
||||
public AbstractProtocolConfigForm,
|
||||
private Ui::Sign
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SignConfigForm(QWidget *parent = 0);
|
||||
virtual ~SignConfigForm();
|
||||
|
||||
static SignConfigForm* createInstance();
|
||||
|
||||
virtual void loadWidget(AbstractProtocol *proto);
|
||||
virtual void storeWidget(AbstractProtocol *proto);
|
||||
|
||||
private slots:
|
||||
};
|
||||
|
||||
#endif
|
@ -140,6 +140,15 @@ void StreamBase::setFrameProtocol(ProtocolList protocolList)
|
||||
}
|
||||
#endif
|
||||
|
||||
bool StreamBase::hasProtocol(quint32 protocolNumber)
|
||||
{
|
||||
foreach(const AbstractProtocol *proto, *currentFrameProtocols)
|
||||
if (proto->protocolNumber() == protocolNumber)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ProtocolListIterator* StreamBase::createProtocolListIterator() const
|
||||
{
|
||||
return new ProtocolListIterator(*currentFrameProtocols);
|
||||
@ -581,32 +590,45 @@ quint64 StreamBase::neighborMacAddress(int frameIndex) const
|
||||
return getNeighborMacAddress(portId_, int(mStreamId->id()), frameIndex);
|
||||
}
|
||||
|
||||
/*!
|
||||
Checks for any potential errors with the packets generated by this
|
||||
stream. Returns true if no problems are found, false otherwise. Details
|
||||
of the error(s) are available in the INOUT param result
|
||||
|
||||
All errors found are returned. However, each type of error is reported
|
||||
only once, even if multiple packets may have that error.
|
||||
*/
|
||||
bool StreamBase::preflightCheck(QStringList &result) const
|
||||
{
|
||||
bool pass = true;
|
||||
bool chkTrunc = true;
|
||||
bool chkJumbo = true;
|
||||
int count = isFrameSizeVariable() ? frameCount() : 1;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (frameLen(i) < (frameProtocolLength(i) + kFcsSize))
|
||||
int pktLen = frameLen(i);
|
||||
|
||||
if (chkTrunc && (pktLen < (frameProtocolLength(i) + kFcsSize)))
|
||||
{
|
||||
result << QObject::tr("One or more frames may be truncated - "
|
||||
"frame length should be at least %1")
|
||||
.arg(frameProtocolLength(i) + kFcsSize);
|
||||
chkTrunc = false;
|
||||
pass = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (frameLen(i) > 1522)
|
||||
if (chkJumbo && (pktLen > 1522))
|
||||
{
|
||||
result << QObject::tr("Jumbo frames may be truncated or dropped "
|
||||
"if not supported by the hardware");
|
||||
pass = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Break out of loop if we've seen at least one instance of all
|
||||
// the above errors
|
||||
if (!chkTrunc && !chkJumbo)
|
||||
break;
|
||||
}
|
||||
|
||||
if (frameCount() <= averagePacketRate() && nextWhat() != e_nw_goto_id)
|
||||
|
@ -40,6 +40,7 @@ public:
|
||||
void protoDataCopyFrom(const OstProto::Stream &stream);
|
||||
void protoDataCopyInto(OstProto::Stream &stream) const;
|
||||
|
||||
bool hasProtocol(quint32 protocolNumber);
|
||||
ProtocolListIterator* createProtocolListIterator() const;
|
||||
|
||||
//! \todo (LOW) should we have a copy constructor??
|
||||
|
@ -65,6 +65,29 @@ void AbstractPort::init()
|
||||
{
|
||||
}
|
||||
|
||||
/*! Can we modify Port with these params? Should modify cause port dirty? */
|
||||
bool AbstractPort::canModify(const OstProto::Port &port, bool *dirty)
|
||||
{
|
||||
bool allow = true;
|
||||
|
||||
*dirty = false;
|
||||
|
||||
if (port.has_transmit_mode()
|
||||
&& (port.transmit_mode() != data_.transmit_mode())) {
|
||||
*dirty = true;
|
||||
allow = !isTransmitOn();
|
||||
}
|
||||
|
||||
if (port.has_is_tracking_stream_stats()
|
||||
&& (port.is_tracking_stream_stats()
|
||||
!= data_.is_tracking_stream_stats())) {
|
||||
*dirty = true;
|
||||
allow = !isTransmitOn();
|
||||
}
|
||||
|
||||
return allow;
|
||||
}
|
||||
|
||||
bool AbstractPort::modify(const OstProto::Port &port)
|
||||
{
|
||||
bool ret = true;
|
||||
@ -82,6 +105,9 @@ bool AbstractPort::modify(const OstProto::Port &port)
|
||||
if (port.has_transmit_mode())
|
||||
data_.set_transmit_mode(port.transmit_mode());
|
||||
|
||||
if (port.has_is_tracking_stream_stats())
|
||||
ret |= setTrackStreamStats(port.is_tracking_stream_stats());
|
||||
|
||||
if (port.has_user_name()) {
|
||||
data_.set_user_name(port.user_name());
|
||||
}
|
||||
@ -155,6 +181,13 @@ void AbstractPort::addNote(QString note)
|
||||
data_.set_notes(notes.toStdString());
|
||||
}
|
||||
|
||||
bool AbstractPort::setTrackStreamStats(bool enable)
|
||||
{
|
||||
data_.set_is_tracking_stream_stats(enable);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AbstractPort::Accuracy AbstractPort::rateAccuracy()
|
||||
{
|
||||
return rateAccuracy_;
|
||||
@ -622,6 +655,54 @@ void AbstractPort::stats(PortStats *stats)
|
||||
stats_.rxFrameErrors + (maxStatsValue_ - epochStats_.rxFrameErrors);
|
||||
}
|
||||
|
||||
void AbstractPort::streamStats(uint guid, OstProto::StreamStatsList *stats)
|
||||
{
|
||||
if (streamStats_.contains(guid))
|
||||
{
|
||||
StreamStatsTuple sst = streamStats_.value(guid);
|
||||
OstProto::StreamStats *s = stats->add_stream_stats();
|
||||
|
||||
s->mutable_stream_guid()->set_id(guid);
|
||||
s->mutable_port_id()->set_id(id());
|
||||
|
||||
s->set_tx_pkts(sst.tx_pkts);
|
||||
s->set_tx_bytes(sst.tx_bytes);
|
||||
s->set_rx_pkts(sst.rx_pkts);
|
||||
s->set_rx_bytes(sst.rx_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractPort::streamStatsAll(OstProto::StreamStatsList *stats)
|
||||
{
|
||||
// FIXME: change input param to a non-OstProto type and/or have
|
||||
// a getFirst/Next like API?
|
||||
StreamStatsIterator i(streamStats_);
|
||||
while (i.hasNext())
|
||||
{
|
||||
i.next();
|
||||
StreamStatsTuple sst = i.value();
|
||||
OstProto::StreamStats *s = stats->add_stream_stats();
|
||||
|
||||
s->mutable_stream_guid()->set_id(i.key());
|
||||
s->mutable_port_id()->set_id(id());
|
||||
|
||||
s->set_tx_pkts(sst.tx_pkts);
|
||||
s->set_tx_bytes(sst.tx_bytes);
|
||||
s->set_rx_pkts(sst.rx_pkts);
|
||||
s->set_rx_bytes(sst.rx_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractPort::resetStreamStats(uint guid)
|
||||
{
|
||||
streamStats_.remove(guid);
|
||||
}
|
||||
|
||||
void AbstractPort::resetStreamStatsAll()
|
||||
{
|
||||
streamStats_.clear();
|
||||
}
|
||||
|
||||
void AbstractPort::clearDeviceNeighbors()
|
||||
{
|
||||
deviceManager_->clearDeviceNeighbors();
|
||||
|
@ -20,6 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#ifndef _SERVER_ABSTRACT_PORT_H
|
||||
#define _SERVER_ABSTRACT_PORT_H
|
||||
|
||||
#include "streamstats.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QtGlobal>
|
||||
|
||||
@ -72,6 +74,7 @@ public:
|
||||
const char* name() { return data_.name().c_str(); }
|
||||
void protoDataCopyInto(OstProto::Port *port) { port->CopyFrom(data_); }
|
||||
|
||||
bool canModify(const OstProto::Port &port, bool *dirty);
|
||||
bool modify(const OstProto::Port &port);
|
||||
|
||||
virtual OstProto::LinkState linkState() { return linkState_; }
|
||||
@ -87,6 +90,8 @@ public:
|
||||
bool isDirty() { return isSendQueueDirty_; }
|
||||
void setDirty() { isSendQueueDirty_ = true; }
|
||||
|
||||
virtual bool setTrackStreamStats(bool enable);
|
||||
|
||||
Accuracy rateAccuracy();
|
||||
virtual bool setRateAccuracy(Accuracy accuracy);
|
||||
|
||||
@ -111,6 +116,12 @@ public:
|
||||
void stats(PortStats *stats);
|
||||
void resetStats() { epochStats_ = stats_; }
|
||||
|
||||
// FIXME: combine single and All calls?
|
||||
void streamStats(uint guid, OstProto::StreamStatsList *stats);
|
||||
void streamStatsAll(OstProto::StreamStatsList *stats);
|
||||
void resetStreamStats(uint guid);
|
||||
void resetStreamStatsAll();
|
||||
|
||||
DeviceManager* deviceManager();
|
||||
virtual void startDeviceEmulation() = 0;
|
||||
virtual void stopDeviceEmulation() = 0;
|
||||
@ -123,6 +134,7 @@ public:
|
||||
quint64 neighborMacAddress(int streamId, int frameIndex);
|
||||
|
||||
protected:
|
||||
|
||||
void addNote(QString note);
|
||||
|
||||
void updatePacketListSequential();
|
||||
@ -136,6 +148,7 @@ protected:
|
||||
|
||||
quint64 maxStatsValue_;
|
||||
struct PortStats stats_;
|
||||
StreamStats streamStats_;
|
||||
//! \todo Need lock for stats access/update
|
||||
|
||||
DeviceManager *deviceManager_;
|
||||
|
@ -31,6 +31,7 @@ win32 {
|
||||
LIBS += -lm
|
||||
LIBS += -lprotobuf
|
||||
HEADERS += drone.h \
|
||||
pcaptransmitter.h \
|
||||
myservice.h
|
||||
SOURCES += \
|
||||
devicemanager.cpp \
|
||||
@ -40,6 +41,10 @@ SOURCES += \
|
||||
portmanager.cpp \
|
||||
abstractport.cpp \
|
||||
pcapport.cpp \
|
||||
pcaptransmitter.cpp \
|
||||
pcaprxstats.cpp \
|
||||
pcaptxstats.cpp \
|
||||
pcaptxthread.cpp \
|
||||
bsdport.cpp \
|
||||
linuxport.cpp \
|
||||
winpcapport.cpp
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2010-2015 Srivats P.
|
||||
Copyright (C) 2010-2016 Srivats P.
|
||||
|
||||
This file is part of "Ostinato"
|
||||
|
||||
@ -133,10 +133,20 @@ void MyService::modifyPort(::google::protobuf::RpcController* /*controller*/,
|
||||
id = port.port_id().id();
|
||||
if (id < portInfo.size())
|
||||
{
|
||||
bool dirty;
|
||||
|
||||
if (!portInfo[id]->canModify(port, &dirty)) {
|
||||
qDebug("Port %d cannot be modified - stop tx and retry", id);
|
||||
continue; //! \todo(LOW): Partial status of RPC
|
||||
}
|
||||
|
||||
portLock[id]->lockForWrite();
|
||||
portInfo[id]->modify(port);
|
||||
if (dirty)
|
||||
portInfo[id]->updatePacketList();
|
||||
portLock[id]->unlock();
|
||||
|
||||
|
||||
notif->mutable_port_id_list()->add_port_id()->set_id(id);
|
||||
}
|
||||
}
|
||||
@ -148,6 +158,8 @@ void MyService::modifyPort(::google::protobuf::RpcController* /*controller*/,
|
||||
notif->set_notif_type(OstProto::portConfigChanged);
|
||||
emit notification(notif->notif_type(), SharedProtobufMessage(notif));
|
||||
}
|
||||
else
|
||||
delete notif;
|
||||
}
|
||||
|
||||
void MyService::getStreamIdList(::google::protobuf::RpcController* controller,
|
||||
@ -558,6 +570,66 @@ void MyService::clearStats(::google::protobuf::RpcController* /*controller*/,
|
||||
done->Run();
|
||||
}
|
||||
|
||||
void MyService::getStreamStats(
|
||||
::google::protobuf::RpcController* /*controller*/,
|
||||
const ::OstProto::StreamGuidList* request,
|
||||
::OstProto::StreamStatsList* response,
|
||||
::google::protobuf::Closure* done)
|
||||
{
|
||||
qDebug("In %s", __PRETTY_FUNCTION__);
|
||||
|
||||
for (int i = 0; i < request->port_id_list().port_id_size(); i++)
|
||||
{
|
||||
int portId;
|
||||
|
||||
portId = request->port_id_list().port_id(i).id();
|
||||
if ((portId < 0) || (portId >= portInfo.size()))
|
||||
continue; //! \todo(LOW): partial rpc?
|
||||
|
||||
portLock[portId]->lockForRead();
|
||||
if (request->stream_guid_size())
|
||||
for (int j = 0; j < request->stream_guid_size(); j++)
|
||||
portInfo[portId]->streamStats(request->stream_guid(j).id(),
|
||||
response);
|
||||
else
|
||||
portInfo[portId]->streamStatsAll(response);
|
||||
portLock[portId]->unlock();
|
||||
}
|
||||
|
||||
done->Run();
|
||||
}
|
||||
|
||||
void MyService::clearStreamStats(
|
||||
::google::protobuf::RpcController* /*controller*/,
|
||||
const ::OstProto::StreamGuidList* request,
|
||||
::OstProto::Ack* /*response*/,
|
||||
::google::protobuf::Closure* done)
|
||||
{
|
||||
qDebug("In %s", __PRETTY_FUNCTION__);
|
||||
|
||||
for (int i = 0; i < request->port_id_list().port_id_size(); i++)
|
||||
{
|
||||
int portId;
|
||||
|
||||
portId = request->port_id_list().port_id(i).id();
|
||||
if ((portId < 0) || (portId >= portInfo.size()))
|
||||
continue; //! \todo (LOW): partial RPC?
|
||||
|
||||
portLock[portId]->lockForWrite();
|
||||
if (request->stream_guid_size())
|
||||
for (int j = 0; j < request->stream_guid_size(); j++)
|
||||
portInfo[portId]->resetStreamStats(
|
||||
request->stream_guid(j).id());
|
||||
else
|
||||
portInfo[portId]->resetStreamStatsAll();
|
||||
portLock[portId]->unlock();
|
||||
}
|
||||
|
||||
//! \todo (LOW): fill-in response "Ack"????
|
||||
|
||||
done->Run();
|
||||
}
|
||||
|
||||
void MyService::checkVersion(::google::protobuf::RpcController* controller,
|
||||
const ::OstProto::VersionInfo* request,
|
||||
::OstProto::VersionCompatibility* response,
|
||||
|
@ -100,6 +100,16 @@ public:
|
||||
const ::OstProto::PortIdList* request,
|
||||
::OstProto::Ack* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
virtual void getStreamStats(::google::protobuf::RpcController* controller,
|
||||
const ::OstProto::StreamGuidList* request,
|
||||
::OstProto::StreamStatsList* response,
|
||||
::google::protobuf::Closure* done);
|
||||
virtual void clearStreamStats(::google::protobuf::RpcController* controller,
|
||||
const ::OstProto::StreamGuidList* request,
|
||||
::OstProto::Ack* response,
|
||||
::google::protobuf::Closure* done);
|
||||
|
||||
virtual void checkVersion(::google::protobuf::RpcController* controller,
|
||||
const ::OstProto::VersionInfo* request,
|
||||
::OstProto::VersionCompatibility* response,
|
||||
|
88
server/packetsequence.h
Normal file
88
server/packetsequence.h
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
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/>
|
||||
*/
|
||||
|
||||
#ifndef _PACKET_SEQUENCE_H
|
||||
#define _PACKET_SEQUENCE_H
|
||||
|
||||
#include "pcapextra.h"
|
||||
#include "../common/sign.h"
|
||||
#include "streamstats.h"
|
||||
|
||||
class PacketSequence
|
||||
{
|
||||
public:
|
||||
PacketSequence(bool trackGuidStats) {
|
||||
trackGuidStats_ = trackGuidStats;
|
||||
sendQueue_ = pcap_sendqueue_alloc(1*1024*1024);
|
||||
lastPacket_ = NULL;
|
||||
packets_ = 0;
|
||||
bytes_ = 0;
|
||||
usecDuration_ = 0;
|
||||
repeatCount_ = 1;
|
||||
repeatSize_ = 1;
|
||||
usecDelay_ = 0;
|
||||
}
|
||||
~PacketSequence() {
|
||||
pcap_sendqueue_destroy(sendQueue_);
|
||||
}
|
||||
bool hasFreeSpace(int size) {
|
||||
if ((sendQueue_->len + size) <= sendQueue_->maxlen)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
int appendPacket(const struct pcap_pkthdr *pktHeader,
|
||||
const uchar *pktData) {
|
||||
int ret;
|
||||
if (lastPacket_)
|
||||
{
|
||||
usecDuration_ += (pktHeader->ts.tv_sec
|
||||
- lastPacket_->ts.tv_sec) * long(1e6);
|
||||
usecDuration_ += (pktHeader->ts.tv_usec
|
||||
- lastPacket_->ts.tv_usec);
|
||||
}
|
||||
packets_++;
|
||||
bytes_ += pktHeader->caplen;
|
||||
lastPacket_ = (struct pcap_pkthdr *)
|
||||
(sendQueue_->buffer + sendQueue_->len);
|
||||
ret = pcap_sendqueue_queue(sendQueue_, pktHeader, pktData);
|
||||
if (trackGuidStats_ && (ret >= 0)) {
|
||||
uint guid;
|
||||
if (SignProtocol::packetGuid(pktData, pktHeader->caplen, &guid)) {
|
||||
streamStatsMeta_[guid].tx_pkts++;
|
||||
streamStatsMeta_[guid].tx_bytes += pktHeader->caplen;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
pcap_send_queue *sendQueue_;
|
||||
struct pcap_pkthdr *lastPacket_;
|
||||
long packets_;
|
||||
long bytes_;
|
||||
ulong usecDuration_;
|
||||
int repeatCount_;
|
||||
int repeatSize_;
|
||||
long usecDelay_;
|
||||
StreamStats streamStatsMeta_;
|
||||
|
||||
private:
|
||||
bool trackGuidStats_;
|
||||
};
|
||||
|
||||
#endif
|
@ -24,67 +24,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
pcap_if_t *PcapPort::deviceList_ = NULL;
|
||||
|
||||
|
||||
#if defined(Q_OS_LINUX)
|
||||
typedef struct timeval TimeStamp;
|
||||
static void inline getTimeStamp(TimeStamp *stamp)
|
||||
{
|
||||
gettimeofday(stamp, NULL);
|
||||
}
|
||||
|
||||
// Returns time diff in usecs between end and start
|
||||
static long inline udiffTimeStamp(const TimeStamp *start, const TimeStamp *end)
|
||||
{
|
||||
struct timeval diff;
|
||||
long usecs;
|
||||
|
||||
timersub(end, start, &diff);
|
||||
|
||||
usecs = diff.tv_usec;
|
||||
if (diff.tv_sec)
|
||||
usecs += diff.tv_sec*1e6;
|
||||
|
||||
return usecs;
|
||||
}
|
||||
#elif defined(Q_OS_WIN32)
|
||||
static quint64 gTicksFreq;
|
||||
typedef LARGE_INTEGER TimeStamp;
|
||||
static void inline getTimeStamp(TimeStamp* stamp)
|
||||
{
|
||||
QueryPerformanceCounter(stamp);
|
||||
}
|
||||
|
||||
static long inline udiffTimeStamp(const TimeStamp *start, const TimeStamp *end)
|
||||
{
|
||||
if (end->QuadPart >= start->QuadPart)
|
||||
return (end->QuadPart - start->QuadPart)*long(1e6)/gTicksFreq;
|
||||
else
|
||||
{
|
||||
// FIXME: incorrect! what's the max value for this counter before
|
||||
// it rolls over?
|
||||
return (start->QuadPart)*long(1e6)/gTicksFreq;
|
||||
}
|
||||
}
|
||||
#else
|
||||
typedef int TimeStamp;
|
||||
static void inline getTimeStamp(TimeStamp*) {}
|
||||
static long inline udiffTimeStamp(const TimeStamp*, const TimeStamp*) { return 0; }
|
||||
#endif
|
||||
|
||||
PcapPort::PcapPort(int id, const char *device)
|
||||
: AbstractPort(id, device)
|
||||
{
|
||||
monitorRx_ = new PortMonitor(device, kDirectionRx, &stats_);
|
||||
monitorTx_ = new PortMonitor(device, kDirectionTx, &stats_);
|
||||
transmitter_ = new PortTransmitter(device);
|
||||
transmitter_ = new PcapTransmitter(device, streamStats_);
|
||||
capturer_ = new PortCapturer(device);
|
||||
emulXcvr_ = new EmulationTransceiver(device, deviceManager_);
|
||||
rxStatsPoller_ = new PcapRxStats(device, streamStats_);
|
||||
|
||||
if (!monitorRx_->handle() || !monitorTx_->handle())
|
||||
isUsable_ = false;
|
||||
@ -133,6 +83,9 @@ PcapPort::~PcapPort()
|
||||
if (monitorTx_)
|
||||
monitorTx_->stop();
|
||||
|
||||
rxStatsPoller_->stop();
|
||||
delete rxStatsPoller_;
|
||||
|
||||
delete emulXcvr_;
|
||||
delete capturer_;
|
||||
delete transmitter_;
|
||||
@ -168,6 +121,16 @@ void PcapPort::updateNotes()
|
||||
arg(notes).toStdString());
|
||||
}
|
||||
|
||||
bool PcapPort::setTrackStreamStats(bool enable)
|
||||
{
|
||||
bool val = enable ? startStreamStatsTracking() : stopStreamStatsTracking();
|
||||
|
||||
if (val)
|
||||
AbstractPort::setTrackStreamStats(enable);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
bool PcapPort::setRateAccuracy(AbstractPort::Accuracy accuracy)
|
||||
{
|
||||
if (transmitter_->setRateAccuracy(accuracy)) {
|
||||
@ -192,6 +155,44 @@ int PcapPort::sendEmulationPacket(PacketBuffer *pktBuf)
|
||||
return emulXcvr_->transmitPacket(pktBuf);
|
||||
}
|
||||
|
||||
bool PcapPort::startStreamStatsTracking()
|
||||
{
|
||||
if (!transmitter_->setStreamStatsTracking(true))
|
||||
goto _tx_fail;
|
||||
if (!rxStatsPoller_->start())
|
||||
goto _rx_fail;
|
||||
/*
|
||||
* If RxPoller receives both IN and OUT packets, packets Tx on this
|
||||
* port will also be received by it and we consider it to be a Rx (IN)
|
||||
* packet incorrectly - so adjust Rx stats for this case
|
||||
* XXX - ideally, RxPoller should do this adjustment, but given our
|
||||
* design, it is easier to implement in transmitter
|
||||
*/
|
||||
transmitter_->adjustRxStreamStats(!rxStatsPoller_->isDirectional());
|
||||
return true;
|
||||
|
||||
_rx_fail:
|
||||
transmitter_->setStreamStatsTracking(false);
|
||||
_tx_fail:
|
||||
qWarning("failed to start stream stats tracking");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PcapPort::stopStreamStatsTracking()
|
||||
{
|
||||
if (!transmitter_->setStreamStatsTracking(false))
|
||||
goto _tx_fail;
|
||||
if (!rxStatsPoller_->stop())
|
||||
goto _rx_fail;
|
||||
return true;
|
||||
|
||||
_rx_fail:
|
||||
transmitter_->setStreamStatsTracking(true);
|
||||
_tx_fail:
|
||||
qWarning("failed to stop stream stats tracking");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* ------------------------------------------------------------------- *
|
||||
* Port Monitor
|
||||
@ -342,440 +343,6 @@ void PcapPort::PortMonitor::stop()
|
||||
pcap_breakloop(handle());
|
||||
}
|
||||
|
||||
/*
|
||||
* ------------------------------------------------------------------- *
|
||||
* Port Transmitter
|
||||
* ------------------------------------------------------------------- *
|
||||
*/
|
||||
PcapPort::PortTransmitter::PortTransmitter(const char *device)
|
||||
{
|
||||
char errbuf[PCAP_ERRBUF_SIZE] = "";
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
LARGE_INTEGER freq;
|
||||
if (QueryPerformanceFrequency(&freq))
|
||||
gTicksFreq = freq.QuadPart;
|
||||
else
|
||||
Q_ASSERT_X(false, "PortTransmitter::PortTransmitter",
|
||||
"This Win32 platform does not support performance counter");
|
||||
#endif
|
||||
state_ = kNotStarted;
|
||||
returnToQIdx_ = -1;
|
||||
loopDelay_ = 0;
|
||||
stop_ = false;
|
||||
stats_ = new AbstractPort::PortStats;
|
||||
usingInternalStats_ = true;
|
||||
handle_ = pcap_open_live(device, 64 /* FIXME */, 0, 1000 /* ms */, errbuf);
|
||||
|
||||
if (handle_ == NULL)
|
||||
goto _open_error;
|
||||
|
||||
usingInternalHandle_ = true;
|
||||
|
||||
return;
|
||||
|
||||
_open_error:
|
||||
qDebug("%s: Error opening port %s: %s\n", __FUNCTION__, device, errbuf);
|
||||
usingInternalHandle_ = false;
|
||||
}
|
||||
|
||||
PcapPort::PortTransmitter::~PortTransmitter()
|
||||
{
|
||||
if (usingInternalStats_)
|
||||
delete stats_;
|
||||
if (usingInternalHandle_)
|
||||
pcap_close(handle_);
|
||||
}
|
||||
|
||||
bool PcapPort::PortTransmitter::setRateAccuracy(
|
||||
AbstractPort::Accuracy accuracy)
|
||||
{
|
||||
switch (accuracy) {
|
||||
case kHighAccuracy:
|
||||
udelayFn_ = udelay;
|
||||
qWarning("%s: rate accuracy set to High - busy wait", __FUNCTION__);
|
||||
break;
|
||||
case 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;
|
||||
}
|
||||
|
||||
void PcapPort::PortTransmitter::clearPacketList()
|
||||
{
|
||||
Q_ASSERT(!isRunning());
|
||||
// \todo lock for packetSequenceList
|
||||
while(packetSequenceList_.size())
|
||||
delete packetSequenceList_.takeFirst();
|
||||
|
||||
currentPacketSequence_ = NULL;
|
||||
repeatSequenceStart_ = -1;
|
||||
repeatSize_ = 0;
|
||||
packetCount_ = 0;
|
||||
|
||||
returnToQIdx_ = -1;
|
||||
|
||||
setPacketListLoopMode(false, 0, 0);
|
||||
}
|
||||
|
||||
void PcapPort::PortTransmitter::loopNextPacketSet(qint64 size, qint64 repeats,
|
||||
long repeatDelaySec, long repeatDelayNsec)
|
||||
{
|
||||
currentPacketSequence_ = new PacketSequence;
|
||||
currentPacketSequence_->repeatCount_ = repeats;
|
||||
currentPacketSequence_->usecDelay_ = repeatDelaySec * long(1e6)
|
||||
+ repeatDelayNsec/1000;
|
||||
|
||||
repeatSequenceStart_ = packetSequenceList_.size();
|
||||
repeatSize_ = size;
|
||||
packetCount_ = 0;
|
||||
|
||||
packetSequenceList_.append(currentPacketSequence_);
|
||||
}
|
||||
|
||||
bool PcapPort::PortTransmitter::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;
|
||||
|
||||
if (currentPacketSequence_ == NULL ||
|
||||
!currentPacketSequence_->hasFreeSpace(2*sizeof(pcap_pkthdr)+length))
|
||||
{
|
||||
if (currentPacketSequence_ != NULL)
|
||||
{
|
||||
long usecs;
|
||||
|
||||
usecs = (pktHdr.ts.tv_sec
|
||||
- currentPacketSequence_->lastPacket_->ts.tv_sec)
|
||||
* long(1e6);
|
||||
usecs += (pktHdr.ts.tv_usec
|
||||
- currentPacketSequence_->lastPacket_->ts.tv_usec);
|
||||
currentPacketSequence_->usecDelay_ = usecs;
|
||||
}
|
||||
|
||||
//! \todo (LOW): calculate sendqueue size
|
||||
currentPacketSequence_ = new PacketSequence;
|
||||
|
||||
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_++;
|
||||
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 and trigger a new pktSeq allocation for next pkt
|
||||
currentPacketSequence_ = NULL;
|
||||
}
|
||||
|
||||
return op;
|
||||
}
|
||||
|
||||
void PcapPort::PortTransmitter::setHandle(pcap_t *handle)
|
||||
{
|
||||
if (usingInternalHandle_)
|
||||
pcap_close(handle_);
|
||||
handle_ = handle;
|
||||
usingInternalHandle_ = false;
|
||||
}
|
||||
|
||||
void PcapPort::PortTransmitter::useExternalStats(AbstractPort::PortStats *stats)
|
||||
{
|
||||
if (usingInternalStats_)
|
||||
delete stats_;
|
||||
stats_ = stats;
|
||||
usingInternalStats_ = false;
|
||||
}
|
||||
|
||||
void PcapPort::PortTransmitter::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
|
||||
|
||||
qDebug("packetSequenceList_.size = %d", packetSequenceList_.size());
|
||||
if (packetSequenceList_.size() <= 0)
|
||||
goto _exit;
|
||||
|
||||
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", i,
|
||||
packetSequenceList_.at(i)->packets_,
|
||||
packetSequenceList_.at(i)->usecDuration_);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (seq->usecDuration_ <= long(1e6)) // 1s
|
||||
{
|
||||
getTimeStamp(&ovrStart);
|
||||
ret = pcap_sendqueue_transmit(handle_,
|
||||
seq->sendQueue_, kSyncTransmit);
|
||||
if (ret >= 0)
|
||||
{
|
||||
stats_->txPkts += seq->packets_;
|
||||
stats_->txBytes += seq->bytes_;
|
||||
|
||||
getTimeStamp(&ovrEnd);
|
||||
overHead += seq->usecDuration_
|
||||
- udiffTimeStamp(&ovrStart, &ovrEnd);
|
||||
Q_ASSERT(overHead <= 0);
|
||||
}
|
||||
if (stop_)
|
||||
ret = -2;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = sendQueueTransmit(handle_, seq->sendQueue_,
|
||||
overHead, kSyncTransmit);
|
||||
}
|
||||
#else
|
||||
ret = sendQueueTransmit(handle_, seq->sendQueue_,
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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:
|
||||
state_ = kFinished;
|
||||
}
|
||||
|
||||
void PcapPort::PortTransmitter::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 PcapPort::PortTransmitter::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 PcapPort::PortTransmitter::isRunning()
|
||||
{
|
||||
return (state_ == kRunning);
|
||||
}
|
||||
|
||||
int PcapPort::PortTransmitter::sendQueueTransmit(pcap_t *p,
|
||||
pcap_send_queue *queue, long &overHead, int sync)
|
||||
{
|
||||
TimeStamp ovrStart, ovrEnd;
|
||||
struct timeval ts;
|
||||
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;
|
||||
|
||||
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_->txPkts++;
|
||||
stats_->txBytes += pktLen;
|
||||
|
||||
// Step to the next packet in the buffer
|
||||
hdr = (struct pcap_pkthdr*) (pkt + pktLen);
|
||||
pkt = (uchar*) ((uchar*)hdr + sizeof(*hdr));
|
||||
|
||||
if (stop_)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PcapPort::PortTransmitter::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
|
||||
}
|
||||
|
||||
/*
|
||||
* ------------------------------------------------------------------- *
|
||||
* Port Capturer
|
||||
|
@ -26,6 +26,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
#include "abstractport.h"
|
||||
#include "pcapextra.h"
|
||||
#include "pcaprxstats.h"
|
||||
#include "pcaptransmitter.h"
|
||||
|
||||
class PcapPort : public AbstractPort
|
||||
{
|
||||
@ -38,6 +40,7 @@ public:
|
||||
virtual bool hasExclusiveControl() { return false; }
|
||||
virtual bool setExclusiveControl(bool /*exclusive*/) { return false; }
|
||||
|
||||
virtual bool setTrackStreamStats(bool enable);
|
||||
virtual bool setRateAccuracy(AbstractPort::Accuracy accuracy);
|
||||
|
||||
virtual void clearPacketList() {
|
||||
@ -103,107 +106,6 @@ protected:
|
||||
bool isPromisc_;
|
||||
};
|
||||
|
||||
class PortTransmitter: public QThread
|
||||
{
|
||||
public:
|
||||
PortTransmitter(const char *device);
|
||||
~PortTransmitter();
|
||||
|
||||
bool setRateAccuracy(AbstractPort::Accuracy accuracy);
|
||||
|
||||
void clearPacketList();
|
||||
void loopNextPacketSet(qint64 size, qint64 repeats,
|
||||
long repeatDelaySec, long repeatDelayNsec);
|
||||
bool appendToPacketList(long sec, long usec, const uchar *packet,
|
||||
int length);
|
||||
void setPacketListLoopMode(bool loop, quint64 secDelay, quint64 nsecDelay) {
|
||||
returnToQIdx_ = loop ? 0 : -1;
|
||||
loopDelay_ = secDelay*long(1e6) + nsecDelay/1000;
|
||||
}
|
||||
void setHandle(pcap_t *handle);
|
||||
void useExternalStats(AbstractPort::PortStats *stats);
|
||||
void run();
|
||||
void start();
|
||||
void stop();
|
||||
bool isRunning();
|
||||
private:
|
||||
enum State
|
||||
{
|
||||
kNotStarted,
|
||||
kRunning,
|
||||
kFinished
|
||||
};
|
||||
|
||||
class PacketSequence
|
||||
{
|
||||
public:
|
||||
PacketSequence() {
|
||||
sendQueue_ = pcap_sendqueue_alloc(1*1024*1024);
|
||||
lastPacket_ = NULL;
|
||||
packets_ = 0;
|
||||
bytes_ = 0;
|
||||
usecDuration_ = 0;
|
||||
repeatCount_ = 1;
|
||||
repeatSize_ = 1;
|
||||
usecDelay_ = 0;
|
||||
}
|
||||
~PacketSequence() {
|
||||
pcap_sendqueue_destroy(sendQueue_);
|
||||
}
|
||||
bool hasFreeSpace(int size) {
|
||||
if ((sendQueue_->len + size) <= sendQueue_->maxlen)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
int appendPacket(const struct pcap_pkthdr *pktHeader,
|
||||
const uchar *pktData) {
|
||||
if (lastPacket_)
|
||||
{
|
||||
usecDuration_ += (pktHeader->ts.tv_sec
|
||||
- lastPacket_->ts.tv_sec) * long(1e6);
|
||||
usecDuration_ += (pktHeader->ts.tv_usec
|
||||
- lastPacket_->ts.tv_usec);
|
||||
}
|
||||
packets_++;
|
||||
bytes_ += pktHeader->caplen;
|
||||
lastPacket_ = (struct pcap_pkthdr *)
|
||||
(sendQueue_->buffer + sendQueue_->len);
|
||||
return pcap_sendqueue_queue(sendQueue_, pktHeader, pktData);
|
||||
}
|
||||
pcap_send_queue *sendQueue_;
|
||||
struct pcap_pkthdr *lastPacket_;
|
||||
long packets_;
|
||||
long bytes_;
|
||||
ulong usecDuration_;
|
||||
int repeatCount_;
|
||||
int repeatSize_;
|
||||
long usecDelay_;
|
||||
};
|
||||
|
||||
static void udelay(unsigned long usec);
|
||||
int sendQueueTransmit(pcap_t *p, pcap_send_queue *queue, long &overHead,
|
||||
int sync);
|
||||
|
||||
QList<PacketSequence*> packetSequenceList_;
|
||||
PacketSequence *currentPacketSequence_;
|
||||
int repeatSequenceStart_;
|
||||
quint64 repeatSize_;
|
||||
quint64 packetCount_;
|
||||
|
||||
int returnToQIdx_;
|
||||
quint64 loopDelay_;
|
||||
|
||||
void (*udelayFn_)(unsigned long);
|
||||
|
||||
bool usingInternalStats_;
|
||||
AbstractPort::PortStats *stats_;
|
||||
bool usingInternalHandle_;
|
||||
pcap_t *handle_;
|
||||
volatile bool stop_;
|
||||
volatile State state_;
|
||||
};
|
||||
|
||||
class PortCapturer: public QThread
|
||||
{
|
||||
public:
|
||||
@ -263,9 +165,13 @@ protected:
|
||||
void updateNotes();
|
||||
|
||||
private:
|
||||
PortTransmitter *transmitter_;
|
||||
bool startStreamStatsTracking();
|
||||
bool stopStreamStatsTracking();
|
||||
|
||||
PcapTransmitter *transmitter_;
|
||||
PortCapturer *capturer_;
|
||||
EmulationTransceiver *emulXcvr_;
|
||||
PcapRxStats *rxStatsPoller_;
|
||||
|
||||
static pcap_if_t *deviceList_;
|
||||
};
|
||||
|
186
server/pcaprxstats.cpp
Normal file
186
server/pcaprxstats.cpp
Normal file
@ -0,0 +1,186 @@
|
||||
/*
|
||||
Copyright (C) 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 "pcaprxstats.h"
|
||||
|
||||
#include "pcapextra.h"
|
||||
#include "../common/sign.h"
|
||||
|
||||
#define notify qWarning // FIXME
|
||||
|
||||
PcapRxStats::PcapRxStats(const char *device, StreamStats &portStreamStats)
|
||||
: streamStats_(portStreamStats)
|
||||
{
|
||||
device_ = QString::fromAscii(device);
|
||||
stop_ = false;
|
||||
state_ = kNotStarted;
|
||||
isDirectional_ = true;
|
||||
|
||||
handle_ = NULL;
|
||||
}
|
||||
|
||||
pcap_t* PcapRxStats::handle()
|
||||
{
|
||||
return handle_;
|
||||
}
|
||||
|
||||
void PcapRxStats::run()
|
||||
{
|
||||
int flags = PCAP_OPENFLAG_PROMISCUOUS;
|
||||
char errbuf[PCAP_ERRBUF_SIZE] = "";
|
||||
struct bpf_program bpf;
|
||||
const int optimize = 1;
|
||||
QString capture_filter = QString("(ether[len - 4:4] == 0x%1)").arg(
|
||||
SignProtocol::magic(), 0, BASE_HEX);
|
||||
// XXX: Exclude ICMP packets which contain an embedded signed packet
|
||||
// For now we check upto 4 vlan tags
|
||||
capture_filter.append(
|
||||
"and not ("
|
||||
"icmp or "
|
||||
"(vlan and icmp) or "
|
||||
"(vlan and icmp) or "
|
||||
"(vlan and icmp) or "
|
||||
"(vlan and icmp) "
|
||||
")");
|
||||
|
||||
qDebug("In %s", __PRETTY_FUNCTION__);
|
||||
|
||||
handle_ = pcap_open_live(qPrintable(device_), 65535,
|
||||
flags, 100 /* ms */, errbuf);
|
||||
if (handle_ == NULL) {
|
||||
if (flags && QString(errbuf).contains("promiscuous")) {
|
||||
notify("Unable to set promiscuous mode on <%s> - "
|
||||
"stream stats rx will not work", qPrintable(device_));
|
||||
goto _exit;
|
||||
}
|
||||
else {
|
||||
notify("Unable to open <%s> [%s] - stream stats rx will not work",
|
||||
qPrintable(device_), errbuf);
|
||||
goto _exit;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
// pcap_setdirection() API is not supported in Windows.
|
||||
// NOTE: WinPcap 4.1.1 and above exports a dummy API that returns -1
|
||||
// but since we would like to work with previous versions of WinPcap
|
||||
// also, we assume the API does not exist
|
||||
isDirectional_ = false;
|
||||
#else
|
||||
if (pcap_setdirection(handle_, PCAP_D_IN) < 0) {
|
||||
qDebug("RxStats: Error setting IN direction %s: %s\n",
|
||||
qPrintable(device_), pcap_geterr(handle_));
|
||||
isDirectional_ = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (pcap_compile(handle_, &bpf, qPrintable(capture_filter),
|
||||
optimize, 0) < 0) {
|
||||
qWarning("%s: error compiling filter: %s", qPrintable(device_),
|
||||
pcap_geterr(handle_));
|
||||
goto _skip_filter;
|
||||
}
|
||||
|
||||
if (pcap_setfilter(handle_, &bpf) < 0) {
|
||||
qWarning("%s: error setting filter: %s", qPrintable(device_),
|
||||
pcap_geterr(handle_));
|
||||
goto _skip_filter;
|
||||
}
|
||||
|
||||
_skip_filter:
|
||||
state_ = kRunning;
|
||||
while (1) {
|
||||
int ret;
|
||||
struct pcap_pkthdr *hdr;
|
||||
const uchar *data;
|
||||
|
||||
ret = pcap_next_ex(handle_, &hdr, &data);
|
||||
switch (ret) {
|
||||
case 1: {
|
||||
uint guid;
|
||||
if (SignProtocol::packetGuid(data, hdr->caplen, &guid)) {
|
||||
streamStats_[guid].rx_pkts++;
|
||||
streamStats_[guid].rx_bytes += hdr->caplen;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0:
|
||||
// timeout: just go back to the loop
|
||||
break;
|
||||
case -1:
|
||||
qWarning("%s: error reading packet (%d): %s",
|
||||
__PRETTY_FUNCTION__, ret, pcap_geterr(handle_));
|
||||
break;
|
||||
case -2:
|
||||
default:
|
||||
qFatal("%s: Unexpected return value %d", __PRETTY_FUNCTION__,
|
||||
ret);
|
||||
}
|
||||
|
||||
if (stop_) {
|
||||
qDebug("user requested receiver stop\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
pcap_close(handle_);
|
||||
handle_ = NULL;
|
||||
stop_ = false;
|
||||
|
||||
_exit:
|
||||
state_ = kFinished;
|
||||
}
|
||||
|
||||
bool PcapRxStats::start()
|
||||
{
|
||||
if (state_ == kRunning) {
|
||||
qWarning("RxStats start requested but is already running!");
|
||||
goto _exit;
|
||||
}
|
||||
|
||||
state_ = kNotStarted;
|
||||
QThread::start();
|
||||
|
||||
while (state_ == kNotStarted)
|
||||
QThread::msleep(10);
|
||||
_exit:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PcapRxStats::stop()
|
||||
{
|
||||
if (state_ == kRunning) {
|
||||
stop_ = true;
|
||||
while (state_ == kRunning)
|
||||
QThread::msleep(10);
|
||||
}
|
||||
else
|
||||
qWarning("RxStats stop requested but is not running!");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PcapRxStats::isRunning()
|
||||
{
|
||||
return (state_ == kRunning);
|
||||
}
|
||||
|
||||
bool PcapRxStats::isDirectional()
|
||||
{
|
||||
return isDirectional_;
|
||||
}
|
54
server/pcaprxstats.h
Normal file
54
server/pcaprxstats.h
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
Copyright (C) 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/>
|
||||
*/
|
||||
|
||||
#ifndef _PCAP_RX_STATS_H
|
||||
#define _PCAP_RX_STATS_H
|
||||
|
||||
#include "streamstats.h"
|
||||
|
||||
#include <QThread>
|
||||
#include <pcap.h>
|
||||
|
||||
class PcapRxStats: public QThread
|
||||
{
|
||||
public:
|
||||
PcapRxStats(const char *device, StreamStats &portStreamStats);
|
||||
pcap_t* handle();
|
||||
void run();
|
||||
bool start();
|
||||
bool stop();
|
||||
bool isRunning();
|
||||
bool isDirectional();
|
||||
|
||||
private:
|
||||
enum State {
|
||||
kNotStarted,
|
||||
kRunning,
|
||||
kFinished
|
||||
};
|
||||
|
||||
QString device_;
|
||||
StreamStats &streamStats_;
|
||||
volatile bool stop_;
|
||||
pcap_t *handle_;
|
||||
volatile State state_;
|
||||
bool isDirectional_;
|
||||
};
|
||||
|
||||
#endif
|
130
server/pcaptransmitter.cpp
Normal file
130
server/pcaptransmitter.cpp
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
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 "pcaptransmitter.h"
|
||||
|
||||
PcapTransmitter::PcapTransmitter(
|
||||
const char *device,
|
||||
StreamStats &portStreamStats)
|
||||
: streamStats_(portStreamStats), txThread_(device)
|
||||
{
|
||||
adjustRxStreamStats_ = false;
|
||||
memset(&stats_, 0, sizeof(stats_));
|
||||
txStats_.setTxThreadStats(&stats_);
|
||||
txStats_.start(); // TODO: alongwith user transmit start
|
||||
|
||||
txThread_.setStats(&stats_);
|
||||
connect(&txThread_, SIGNAL(finished()), SLOT(updateTxThreadStreamStats()));
|
||||
}
|
||||
|
||||
PcapTransmitter::~PcapTransmitter()
|
||||
{
|
||||
txStats_.stop(); // TODO: alongwith user transmit stop
|
||||
}
|
||||
|
||||
bool PcapTransmitter::setRateAccuracy(
|
||||
AbstractPort::Accuracy accuracy)
|
||||
{
|
||||
return txThread_.setRateAccuracy(accuracy);
|
||||
}
|
||||
|
||||
void PcapTransmitter::adjustRxStreamStats(bool enable)
|
||||
{
|
||||
adjustRxStreamStats_ = enable;
|
||||
}
|
||||
|
||||
bool PcapTransmitter::setStreamStatsTracking(bool enable)
|
||||
{
|
||||
return txThread_.setStreamStatsTracking(enable);
|
||||
}
|
||||
|
||||
void PcapTransmitter::clearPacketList()
|
||||
{
|
||||
txThread_.clearPacketList();
|
||||
}
|
||||
|
||||
void PcapTransmitter::loopNextPacketSet(
|
||||
qint64 size,
|
||||
qint64 repeats,
|
||||
long repeatDelaySec,
|
||||
long repeatDelayNsec)
|
||||
{
|
||||
txThread_.loopNextPacketSet(size, repeats, repeatDelaySec, repeatDelayNsec);
|
||||
}
|
||||
|
||||
bool PcapTransmitter::appendToPacketList(long sec, long nsec,
|
||||
const uchar *packet, int length)
|
||||
{
|
||||
return txThread_.appendToPacketList(sec, nsec, packet, length);
|
||||
}
|
||||
|
||||
void PcapTransmitter::setHandle(pcap_t *handle)
|
||||
{
|
||||
txThread_.setHandle(handle);
|
||||
}
|
||||
|
||||
void PcapTransmitter::setPacketListLoopMode(
|
||||
bool loop,
|
||||
quint64 secDelay,
|
||||
quint64 nsecDelay)
|
||||
{
|
||||
txThread_.setPacketListLoopMode(loop, secDelay, nsecDelay);
|
||||
}
|
||||
|
||||
void PcapTransmitter::useExternalStats(AbstractPort::PortStats *stats)
|
||||
{
|
||||
txStats_.useExternalStats(stats);
|
||||
}
|
||||
|
||||
void PcapTransmitter::start()
|
||||
{
|
||||
txThread_.start();
|
||||
}
|
||||
|
||||
void PcapTransmitter::stop()
|
||||
{
|
||||
txThread_.stop();
|
||||
}
|
||||
|
||||
bool PcapTransmitter::isRunning()
|
||||
{
|
||||
return txThread_.isRunning();
|
||||
}
|
||||
|
||||
void PcapTransmitter::updateTxThreadStreamStats()
|
||||
{
|
||||
PcapTxThread *txThread = dynamic_cast<PcapTxThread*>(sender());
|
||||
const StreamStats& threadStreamStats = txThread->streamStats();
|
||||
StreamStatsIterator i(threadStreamStats);
|
||||
|
||||
while (i.hasNext())
|
||||
{
|
||||
i.next();
|
||||
uint guid = i.key();
|
||||
StreamStatsTuple sst = i.value();
|
||||
|
||||
streamStats_[guid].tx_pkts += sst.tx_pkts;
|
||||
streamStats_[guid].tx_bytes += sst.tx_bytes;
|
||||
if (adjustRxStreamStats_) {
|
||||
streamStats_[guid].rx_pkts -= sst.tx_pkts;
|
||||
streamStats_[guid].rx_bytes -= sst.tx_bytes;
|
||||
}
|
||||
}
|
||||
txThread->clearStreamStats();
|
||||
}
|
63
server/pcaptransmitter.h
Normal file
63
server/pcaptransmitter.h
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
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/>
|
||||
*/
|
||||
|
||||
#ifndef _PCAP_TRANSMITTER_H
|
||||
#define _PCAP_TRANSMITTER_H
|
||||
|
||||
#include "abstractport.h"
|
||||
#include "pcaptxstats.h"
|
||||
#include "pcaptxthread.h"
|
||||
#include "statstuple.h"
|
||||
|
||||
class PcapTransmitter : QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PcapTransmitter(const char *device, StreamStats &portStreamStats);
|
||||
~PcapTransmitter();
|
||||
|
||||
bool setRateAccuracy(AbstractPort::Accuracy accuracy);
|
||||
bool setStreamStatsTracking(bool enable);
|
||||
void adjustRxStreamStats(bool enable);
|
||||
|
||||
void clearPacketList();
|
||||
void loopNextPacketSet(qint64 size, qint64 repeats,
|
||||
long repeatDelaySec, long repeatDelayNsec);
|
||||
bool appendToPacketList(long sec, long usec, const uchar *packet,
|
||||
int length);
|
||||
void setPacketListLoopMode(bool loop, quint64 secDelay, quint64 nsecDelay);
|
||||
|
||||
void setHandle(pcap_t *handle);
|
||||
void useExternalStats(AbstractPort::PortStats *stats);
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
bool isRunning();
|
||||
private slots:
|
||||
void updateTxThreadStreamStats();
|
||||
private:
|
||||
StreamStats &streamStats_;
|
||||
PcapTxThread txThread_;
|
||||
PcapTxStats txStats_;
|
||||
StatsTuple stats_;
|
||||
bool adjustRxStreamStats_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
86
server/pcaptxstats.cpp
Normal file
86
server/pcaptxstats.cpp
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
Copyright (C) 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 "pcaptxstats.h"
|
||||
|
||||
#include "pcaptxstats.h"
|
||||
#include "statstuple.h"
|
||||
|
||||
PcapTxStats::PcapTxStats()
|
||||
{
|
||||
txThreadStats_ = NULL;
|
||||
|
||||
stats_ = new AbstractPort::PortStats;
|
||||
usingInternalStats_ = true;
|
||||
|
||||
stop_ = false;
|
||||
}
|
||||
|
||||
PcapTxStats::~PcapTxStats()
|
||||
{
|
||||
if (usingInternalStats_)
|
||||
delete stats_;
|
||||
}
|
||||
|
||||
void PcapTxStats::setTxThreadStats(StatsTuple *stats)
|
||||
{
|
||||
txThreadStats_ = stats;
|
||||
}
|
||||
|
||||
void PcapTxStats::useExternalStats(AbstractPort::PortStats *stats)
|
||||
{
|
||||
if (usingInternalStats_)
|
||||
delete stats_;
|
||||
stats_ = stats;
|
||||
usingInternalStats_ = false;
|
||||
}
|
||||
|
||||
void PcapTxStats::start()
|
||||
{
|
||||
QThread::start();
|
||||
|
||||
while (!isRunning())
|
||||
QThread::msleep(10);
|
||||
}
|
||||
|
||||
void PcapTxStats::stop()
|
||||
{
|
||||
stop_ = true;
|
||||
|
||||
while (isRunning())
|
||||
QThread::msleep(10);
|
||||
}
|
||||
|
||||
void PcapTxStats::run()
|
||||
{
|
||||
Q_ASSERT(txThreadStats_);
|
||||
|
||||
qDebug("txStats: collection start");
|
||||
|
||||
while (1) {
|
||||
stats_->txPkts = txThreadStats_->pkts;
|
||||
stats_->txBytes = txThreadStats_->bytes;
|
||||
|
||||
if (stop_)
|
||||
break;
|
||||
QThread::msleep(1000);
|
||||
}
|
||||
stop_ = false;
|
||||
qDebug("txStats: collection end");
|
||||
}
|
53
server/pcaptxstats.h
Normal file
53
server/pcaptxstats.h
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright (C) 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/>
|
||||
*/
|
||||
|
||||
#ifndef _PCAP_TX_STATS_H
|
||||
#define _PCAP_TX_STATS_H
|
||||
|
||||
#include "abstractport.h"
|
||||
|
||||
#include <QThread>
|
||||
|
||||
class StatsTuple;
|
||||
|
||||
class PcapTxStats : public QThread
|
||||
{
|
||||
public:
|
||||
PcapTxStats();
|
||||
~PcapTxStats();
|
||||
|
||||
void setTxThreadStats(StatsTuple *stats);
|
||||
|
||||
void useExternalStats(AbstractPort::PortStats *stats);
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
private:
|
||||
void run();
|
||||
|
||||
StatsTuple *txThreadStats_;
|
||||
|
||||
bool usingInternalStats_;
|
||||
AbstractPort::PortStats *stats_;
|
||||
|
||||
volatile bool stop_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
596
server/pcaptxthread.cpp
Normal file
596
server/pcaptxthread.cpp
Normal file
@ -0,0 +1,596 @@
|
||||
/*
|
||||
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 "pcaptransmitter.h"
|
||||
|
||||
#include "statstuple.h"
|
||||
#include "timestamp.h"
|
||||
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#include <inttypes.h>
|
||||
|
||||
PcapTxThread::PcapTxThread(const char *device)
|
||||
{
|
||||
char errbuf[PCAP_ERRBUF_SIZE] = "";
|
||||
|
||||
#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)
|
||||
{
|
||||
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;
|
||||
|
||||
if (currentPacketSequence_ == NULL ||
|
||||
!currentPacketSequence_->hasFreeSpace(2*sizeof(pcap_pkthdr)+length))
|
||||
{
|
||||
if (currentPacketSequence_ != NULL)
|
||||
{
|
||||
long usecs;
|
||||
|
||||
usecs = (pktHdr.ts.tv_sec
|
||||
- currentPacketSequence_->lastPacket_->ts.tv_sec)
|
||||
* long(1e6);
|
||||
usecs += (pktHdr.ts.tv_usec
|
||||
- currentPacketSequence_->lastPacket_->ts.tv_usec);
|
||||
currentPacketSequence_->usecDelay_ = usecs;
|
||||
}
|
||||
|
||||
//! \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;
|
||||
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 and trigger a new pktSeq allocation for next pkt
|
||||
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::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
|
||||
|
||||
qDebug("packetSequenceList_.size = %d", packetSequenceList_.size());
|
||||
if (packetSequenceList_.size() <= 0)
|
||||
goto _exit;
|
||||
|
||||
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", i,
|
||||
packetSequenceList_.at(i)->packets_,
|
||||
packetSequenceList_.at(i)->usecDuration_);
|
||||
}
|
||||
|
||||
lastStats_ = *stats_; // used for stream stats
|
||||
|
||||
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;
|
||||
|
||||
if (seq->usecDuration_ <= long(1e6)) // 1s
|
||||
{
|
||||
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->sendQueue_,
|
||||
overHead, kSyncTransmit);
|
||||
}
|
||||
#else
|
||||
ret = sendQueueTransmit(handle_, seq->sendQueue_,
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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:
|
||||
if (trackStreamStats_)
|
||||
updateStreamStats();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
int PcapTxThread::sendQueueTransmit(pcap_t *p,
|
||||
pcap_send_queue *queue, long &overHead, int sync)
|
||||
{
|
||||
TimeStamp ovrStart, ovrEnd;
|
||||
struct timeval ts;
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
// 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::updateStreamStats()
|
||||
{
|
||||
// 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 = %" PRIu64, pkts);
|
||||
qDebug("packetListSize_ = %" PRIu64, 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
|
||||
}
|
||||
|
98
server/pcaptxthread.h
Normal file
98
server/pcaptxthread.h
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
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/>
|
||||
*/
|
||||
|
||||
#ifndef _PCAP_TX_THREAD_H
|
||||
#define _PCAP_TX_THREAD_H
|
||||
|
||||
#include "abstractport.h"
|
||||
#include "packetsequence.h"
|
||||
#include "statstuple.h"
|
||||
|
||||
#include <QThread>
|
||||
#include <pcap.h>
|
||||
|
||||
class PcapTxThread: public QThread
|
||||
{
|
||||
public:
|
||||
PcapTxThread(const char *device);
|
||||
~PcapTxThread();
|
||||
|
||||
bool setRateAccuracy(AbstractPort::Accuracy accuracy);
|
||||
bool setStreamStatsTracking(bool enable);
|
||||
|
||||
void clearPacketList();
|
||||
void loopNextPacketSet(qint64 size, qint64 repeats,
|
||||
long repeatDelaySec, long repeatDelayNsec);
|
||||
bool appendToPacketList(long sec, long usec, const uchar *packet,
|
||||
int length);
|
||||
void setPacketListLoopMode(bool loop, quint64 secDelay, quint64 nsecDelay);
|
||||
|
||||
void setHandle(pcap_t *handle);
|
||||
|
||||
void setStats(StatsTuple *stats);
|
||||
|
||||
const StreamStats& streamStats();
|
||||
void clearStreamStats();
|
||||
|
||||
void run();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
bool isRunning();
|
||||
|
||||
private:
|
||||
enum State
|
||||
{
|
||||
kNotStarted,
|
||||
kRunning,
|
||||
kFinished
|
||||
};
|
||||
|
||||
static void udelay(unsigned long usec);
|
||||
int sendQueueTransmit(pcap_t *p, pcap_send_queue *queue, long &overHead,
|
||||
int sync);
|
||||
void updateStreamStats();
|
||||
|
||||
// Intermediate state variables used while building the packet list
|
||||
PacketSequence *currentPacketSequence_;
|
||||
int repeatSequenceStart_;
|
||||
quint64 repeatSize_;
|
||||
quint64 packetCount_;
|
||||
|
||||
QList<PacketSequence*> packetSequenceList_;
|
||||
quint64 packetListSize_; // count of pkts in packet List including repeats
|
||||
|
||||
int returnToQIdx_;
|
||||
quint64 loopDelay_;
|
||||
|
||||
void (*udelayFn_)(unsigned long);
|
||||
|
||||
bool usingInternalHandle_;
|
||||
pcap_t *handle_;
|
||||
volatile bool stop_;
|
||||
volatile State state_;
|
||||
|
||||
bool trackStreamStats_;
|
||||
StatsTuple *stats_;
|
||||
StatsTuple lastStats_;
|
||||
StreamStats streamStats_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
31
server/statstuple.h
Normal file
31
server/statstuple.h
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright (C) 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/>
|
||||
*/
|
||||
|
||||
#ifndef _STATS_TUPLE_H
|
||||
#define _STATS_TUPLE_H
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
struct StatsTuple
|
||||
{
|
||||
quint64 pkts;
|
||||
quint64 bytes;
|
||||
};
|
||||
|
||||
#endif
|
36
server/streamstats.h
Normal file
36
server/streamstats.h
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright (C) 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/>
|
||||
*/
|
||||
|
||||
#ifndef _STREAM_STATS_H
|
||||
#define _STREAM_STATS_H
|
||||
|
||||
#include <QHash>
|
||||
|
||||
struct StreamStatsTuple
|
||||
{
|
||||
quint64 rx_pkts;
|
||||
quint64 rx_bytes;
|
||||
quint64 tx_pkts;
|
||||
quint64 tx_bytes;
|
||||
};
|
||||
|
||||
typedef QHash<uint, StreamStatsTuple> StreamStats;
|
||||
typedef QHashIterator<uint, StreamStatsTuple> StreamStatsIterator;
|
||||
|
||||
#endif
|
72
server/timestamp.h
Normal file
72
server/timestamp.h
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
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/>
|
||||
*/
|
||||
|
||||
#ifndef _TIMESTAMP_H
|
||||
#define _TIMESTAMP_H
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#if defined(Q_OS_LINUX)
|
||||
typedef struct timeval TimeStamp;
|
||||
static void inline getTimeStamp(TimeStamp *stamp)
|
||||
{
|
||||
gettimeofday(stamp, NULL);
|
||||
}
|
||||
|
||||
// Returns time diff in usecs between end and start
|
||||
static long inline udiffTimeStamp(const TimeStamp *start, const TimeStamp *end)
|
||||
{
|
||||
struct timeval diff;
|
||||
long usecs;
|
||||
|
||||
timersub(end, start, &diff);
|
||||
|
||||
usecs = diff.tv_usec;
|
||||
if (diff.tv_sec)
|
||||
usecs += diff.tv_sec*1e6;
|
||||
|
||||
return usecs;
|
||||
}
|
||||
#elif defined(Q_OS_WIN32)
|
||||
static quint64 gTicksFreq;
|
||||
typedef LARGE_INTEGER TimeStamp;
|
||||
static void inline getTimeStamp(TimeStamp* stamp)
|
||||
{
|
||||
QueryPerformanceCounter(stamp);
|
||||
}
|
||||
|
||||
static long inline udiffTimeStamp(const TimeStamp *start, const TimeStamp *end)
|
||||
{
|
||||
if (end->QuadPart >= start->QuadPart)
|
||||
return (end->QuadPart - start->QuadPart)*long(1e6)/gTicksFreq;
|
||||
else
|
||||
{
|
||||
// FIXME: incorrect! what's the max value for this counter before
|
||||
// it rolls over?
|
||||
return (start->QuadPart)*long(1e6)/gTicksFreq;
|
||||
}
|
||||
}
|
||||
#else
|
||||
typedef int TimeStamp;
|
||||
static void inline getTimeStamp(TimeStamp*) {}
|
||||
static long inline udiffTimeStamp(const TimeStamp*, const TimeStamp*) { return 0; }
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
601
test/streamstatstest.py
Normal file
601
test/streamstatstest.py
Normal file
@ -0,0 +1,601 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
# standard modules
|
||||
import ipaddress
|
||||
import logging
|
||||
import os
|
||||
import pytest
|
||||
import random
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
import pytest
|
||||
from fabric.api import run, env, sudo
|
||||
|
||||
from utils import get_tshark
|
||||
|
||||
sys.path.insert(1, '../binding')
|
||||
from core import ost_pb, emul, DroneProxy
|
||||
from rpc import RpcError
|
||||
from protocols.mac_pb2 import mac, Mac
|
||||
from protocols.ip4_pb2 import ip4, Ip4
|
||||
from protocols.ip6_pb2 import ip6, Ip6
|
||||
from protocols.payload_pb2 import payload, Payload
|
||||
from protocols.sign_pb2 import sign
|
||||
|
||||
# Convenience class to interwork with OstEmul::Ip6Address() and
|
||||
# the python ipaddress module
|
||||
# FIXME: move to a common module for reuse and remove duplication in other
|
||||
# scripts
|
||||
class ip6_address(ipaddress.IPv6Interface):
|
||||
def __init__(self, addr):
|
||||
if type(addr) is str:
|
||||
super(ip6_address, self).__init__(unicode(addr))
|
||||
elif type(addr) is int:
|
||||
super(ip6_address, self).__init__(addr)
|
||||
else:
|
||||
super(ip6_address, self).__init__(addr.hi << 64 | addr.lo)
|
||||
self.ip6 = emul.Ip6Address()
|
||||
self.ip6.hi = int(self) >> 64
|
||||
self.ip6.lo = int(self) & 0xffffffffffffffff
|
||||
|
||||
self.prefixlen = self.network.prefixlen
|
||||
|
||||
# we assume gateway is the lowest IP host address in the network
|
||||
gateway = self.network.network_address + 1
|
||||
self.gateway = emul.Ip6Address()
|
||||
self.gateway.hi = int(gateway) >> 64
|
||||
self.gateway.lo = int(gateway) & 0xffffffffffffffff
|
||||
|
||||
use_defaults = True
|
||||
|
||||
# initialize defaults
|
||||
host_name = '127.0.0.1'
|
||||
|
||||
# initialize defaults - DUT
|
||||
env.use_shell = False
|
||||
env.user = 'tc'
|
||||
env.password = 'tc'
|
||||
env.host_string = 'localhost:50022'
|
||||
|
||||
tshark = get_tshark(minversion = '2')
|
||||
|
||||
# setup logging
|
||||
log = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# command-line option/arg processing
|
||||
if len(sys.argv) > 1:
|
||||
if sys.argv[1] in ('-d', '--use-defaults'):
|
||||
use_defaults = True
|
||||
if sys.argv[1] in ('-h', '--help'):
|
||||
print('%s [OPTION]...' % (sys.argv[0]))
|
||||
print('Options:')
|
||||
print(' -d --use-defaults run using default values')
|
||||
print(' -h --help show this help')
|
||||
sys.exit(0)
|
||||
|
||||
print(' +-------+ +-------+')
|
||||
print(' | |X--<-->--X|-+ |')
|
||||
print(' | Drone | | | DUT |')
|
||||
print(' | |Y--<-->--Y|-+ |')
|
||||
print(' +-------+ +-------+')
|
||||
print('')
|
||||
print('Drone has 2 ports connected to DUT. Packets sent on port X')
|
||||
print('are expected to be forwarded by the DUT and received back on')
|
||||
print('port Y and vice versa')
|
||||
print('')
|
||||
|
||||
if not use_defaults:
|
||||
s = raw_input('Drone\'s Hostname/IP [%s]: ' % (host_name))
|
||||
host_name = s or host_name
|
||||
s = raw_input('DUT\'s Hostname/IP [%s]: ' % (env.host_string))
|
||||
env.host_string = s or env.host_string
|
||||
# FIXME: get inputs for dut x/y ports
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def drone(request):
|
||||
"""Baseline Configuration for all testcases in this module"""
|
||||
|
||||
drn = DroneProxy(host_name)
|
||||
|
||||
log.info('connecting to drone(%s:%d)' % (drn.hostName(), drn.portNumber()))
|
||||
drn.connect()
|
||||
|
||||
def fin():
|
||||
drn.disconnect()
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
return drn
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def ports(request, drone):
|
||||
port_id_list = drone.getPortIdList()
|
||||
port_config_list = drone.getPortConfig(port_id_list)
|
||||
assert len(port_config_list.port) != 0
|
||||
|
||||
# print port list and find default X/Y ports
|
||||
ports.x_num = -1
|
||||
ports.y_num = -1
|
||||
print port_config_list
|
||||
print('Port List')
|
||||
print('---------')
|
||||
for port in port_config_list.port:
|
||||
print('%d.%s (%s)' % (port.port_id.id, port.name, port.description))
|
||||
# use a vhost port as default X/Y port
|
||||
if ('vhost' in port.name or 'oracle' in port.description.lower()):
|
||||
if ports.x_num < 0:
|
||||
ports.x_num = port.port_id.id
|
||||
elif ports.y_num < 0:
|
||||
ports.y_num = port.port_id.id
|
||||
if ('eth1' in port.name):
|
||||
ports.x_num = port.port_id.id
|
||||
if ('eth2' in port.name):
|
||||
ports.y_num = port.port_id.id
|
||||
|
||||
assert ports.x_num >= 0
|
||||
assert ports.y_num >= 0
|
||||
|
||||
print('Using port %d as port X' % ports.x_num)
|
||||
print('Using port %d as port Y' % ports.y_num)
|
||||
|
||||
ports.x = ost_pb.PortIdList()
|
||||
ports.x.port_id.add().id = ports.x_num;
|
||||
|
||||
ports.y = ost_pb.PortIdList()
|
||||
ports.y.port_id.add().id = ports.y_num;
|
||||
|
||||
# Enable stream stats on ports
|
||||
portConfig = ost_pb.PortConfigList()
|
||||
portConfig.port.add().port_id.id = ports.x_num;
|
||||
portConfig.port[0].is_tracking_stream_stats = True;
|
||||
portConfig.port.add().port_id.id = ports.y_num;
|
||||
portConfig.port[1].is_tracking_stream_stats = True;
|
||||
print('Enabling Stream Stats tracking on ports X and Y');
|
||||
drone.modifyPort(portConfig);
|
||||
|
||||
return ports
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def emul_ports(request, drone, ports):
|
||||
emul_ports = ost_pb.PortIdList()
|
||||
emul_ports.port_id.add().id = ports.x.port_id[0].id;
|
||||
emul_ports.port_id.add().id = ports.y.port_id[0].id;
|
||||
return emul_ports
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def dgid_list(request, drone, ports):
|
||||
# ----------------------------------------------------------------- #
|
||||
# create emulated device(s) on tx/rx ports - each test case will
|
||||
# use these same devices
|
||||
# ----------------------------------------------------------------- #
|
||||
|
||||
# delete existing devices, if any, on tx port
|
||||
dgid_list.x = drone.getDeviceGroupIdList(ports.x.port_id[0])
|
||||
drone.deleteDeviceGroup(dgid_list.x)
|
||||
|
||||
# add a emulated device group on port X
|
||||
dgid_list.x = ost_pb.DeviceGroupIdList()
|
||||
dgid_list.x.port_id.CopyFrom(ports.x.port_id[0])
|
||||
dgid_list.x.device_group_id.add().id = 1
|
||||
log.info('adding X device_group %d' % dgid_list.x.device_group_id[0].id)
|
||||
drone.addDeviceGroup(dgid_list.x)
|
||||
|
||||
# configure the X device(s)
|
||||
devgrp_cfg = ost_pb.DeviceGroupConfigList()
|
||||
devgrp_cfg.port_id.CopyFrom(ports.x.port_id[0])
|
||||
dg = devgrp_cfg.device_group.add()
|
||||
dg.device_group_id.id = dgid_list.x.device_group_id[0].id
|
||||
dg.core.name = "HostX"
|
||||
dg.device_count = 5
|
||||
|
||||
dg.Extensions[emul.mac].address = 0x000102030a01
|
||||
|
||||
dg.Extensions[emul.ip4].address = 0x0a0a0165
|
||||
dg.Extensions[emul.ip4].prefix_length = 24
|
||||
dg.Extensions[emul.ip4].default_gateway = 0x0a0a0101
|
||||
|
||||
ip6addr = ip6_address('1234:1::65/96')
|
||||
dg.Extensions[emul.ip6].address.CopyFrom(ip6addr.ip6)
|
||||
dg.Extensions[emul.ip6].prefix_length = ip6addr.prefixlen
|
||||
dg.Extensions[emul.ip6].default_gateway.CopyFrom(ip6addr.gateway)
|
||||
|
||||
drone.modifyDeviceGroup(devgrp_cfg)
|
||||
|
||||
# delete existing devices, if any, on Y port
|
||||
dgid_list.y = drone.getDeviceGroupIdList(ports.y.port_id[0])
|
||||
drone.deleteDeviceGroup(dgid_list.y)
|
||||
|
||||
# add a emulated device group on port Y
|
||||
dgid_list.y = ost_pb.DeviceGroupIdList()
|
||||
dgid_list.y.port_id.CopyFrom(ports.y.port_id[0])
|
||||
dgid_list.y.device_group_id.add().id = 1
|
||||
log.info('adding Y device_group %d' % dgid_list.y.device_group_id[0].id)
|
||||
drone.addDeviceGroup(dgid_list.y)
|
||||
|
||||
# configure the Y device(s)
|
||||
devgrp_cfg = ost_pb.DeviceGroupConfigList()
|
||||
devgrp_cfg.port_id.CopyFrom(ports.y.port_id[0])
|
||||
dg = devgrp_cfg.device_group.add()
|
||||
dg.device_group_id.id = dgid_list.y.device_group_id[0].id
|
||||
dg.core.name = "HostY"
|
||||
|
||||
dg.Extensions[emul.mac].address = 0x000102030b01
|
||||
|
||||
dg.Extensions[emul.ip4].address = 0x0a0a0265
|
||||
dg.Extensions[emul.ip4].prefix_length = 24
|
||||
dg.Extensions[emul.ip4].default_gateway = 0x0a0a0201
|
||||
|
||||
ip6addr = ip6_address('1234:2::65/96')
|
||||
dg.Extensions[emul.ip6].address.CopyFrom(ip6addr.ip6)
|
||||
dg.Extensions[emul.ip6].prefix_length = ip6addr.prefixlen
|
||||
dg.Extensions[emul.ip6].default_gateway.CopyFrom(ip6addr.gateway)
|
||||
|
||||
drone.modifyDeviceGroup(devgrp_cfg)
|
||||
|
||||
def fin():
|
||||
dgid_list = drone.getDeviceGroupIdList(ports.x.port_id[0])
|
||||
drone.deleteDeviceGroup(dgid_list)
|
||||
dgid_list = drone.getDeviceGroupIdList(ports.y.port_id[0])
|
||||
drone.deleteDeviceGroup(dgid_list)
|
||||
request.addfinalizer(fin)
|
||||
|
||||
return dgid_list
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def dut(request):
|
||||
# Enable IP forwarding on the DUT (aka make it a router)
|
||||
sudo('sysctl -w net.ipv4.ip_forward=1')
|
||||
sudo('sysctl -w net.ipv6.conf.all.forwarding=1')
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def dut_ports(request):
|
||||
dut_ports.x = 'eth1'
|
||||
dut_ports.y = 'eth2'
|
||||
|
||||
# delete all configuration on the DUT interfaces
|
||||
sudo('ip address flush dev ' + dut_ports.y)
|
||||
sudo('ip address flush dev ' + dut_ports.x)
|
||||
return dut_ports
|
||||
|
||||
@pytest.fixture
|
||||
def dut_ip(request, dut_ports):
|
||||
sudo('ip address add 10.10.1.1/24 dev ' + dut_ports.x)
|
||||
sudo('ip address add 10.10.2.1/24 dev ' + dut_ports.y)
|
||||
|
||||
sudo('ip -6 address add 1234:1::1/96 dev ' + dut_ports.x)
|
||||
sudo('ip -6 address add 1234:2::1/96 dev ' + dut_ports.y)
|
||||
|
||||
def fin():
|
||||
sudo('ip address delete 10.10.1.1/24 dev ' + dut_ports.x)
|
||||
sudo('ip address delete 10.10.2.1/24 dev ' + dut_ports.y)
|
||||
|
||||
sudo('ip -6 address delete 1234:1::1/96 dev ' + dut_ports.x)
|
||||
sudo('ip -6 address delete 1234:2::1/96 dev ' + dut_ports.y)
|
||||
request.addfinalizer(fin)
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def stream_clear(request, drone, ports):
|
||||
# delete existing streams, if any, on all ports
|
||||
sid_list = drone.getStreamIdList(ports.x.port_id[0])
|
||||
drone.deleteStream(sid_list)
|
||||
sid_list = drone.getStreamIdList(ports.y.port_id[0])
|
||||
drone.deleteStream(sid_list)
|
||||
|
||||
@pytest.fixture
|
||||
def stream(request, drone, ports, sign_stream_cfg):
|
||||
|
||||
sscfg = sign_stream_cfg
|
||||
num_streams = sscfg['num_streams']
|
||||
|
||||
# add stream(s)
|
||||
stream_id = ost_pb.StreamIdList()
|
||||
stream_id.port_id.CopyFrom(ports.x.port_id[0])
|
||||
for i in range(num_streams):
|
||||
stream_id.stream_id.add().id = 1+i
|
||||
log.info('adding X stream(s) ' + str(stream_id))
|
||||
drone.addStream(stream_id)
|
||||
|
||||
# configure the stream(s)
|
||||
stream_cfg = ost_pb.StreamConfigList()
|
||||
stream_cfg.port_id.CopyFrom(ports.x.port_id[0])
|
||||
for i in range(num_streams):
|
||||
s = stream_cfg.stream.add()
|
||||
s.stream_id.id = stream_id.stream_id[i].id
|
||||
s.core.is_enabled = True
|
||||
# On Win32, packets in a seq that take less than 1s to transmit
|
||||
# are transmitted in one shot using pcap_send_queue; this means that
|
||||
# stopTransmit() cannot stop them in the middle of the sequence and
|
||||
# stop will happen only at the pktSeq boundary
|
||||
# Since we want to test cases where stop is trigerred in the middle of
|
||||
# a sequence, we set pps < 1000
|
||||
# FIXME: for some reason values such as 100pps is also leading to
|
||||
# stop only at seq boundary - need to debug
|
||||
s.control.packets_per_sec = 523
|
||||
|
||||
# setup stream protocols
|
||||
p = s.protocol.add()
|
||||
s.control.num_packets = sscfg['num_pkts'][i]
|
||||
p.protocol_id.id = ost_pb.Protocol.kMacFieldNumber
|
||||
p.Extensions[mac].dst_mac_mode = Mac.e_mm_resolve
|
||||
p.Extensions[mac].src_mac_mode = Mac.e_mm_resolve
|
||||
|
||||
s.protocol.add().protocol_id.id = ost_pb.Protocol.kEth2FieldNumber
|
||||
|
||||
p = s.protocol.add()
|
||||
if i & 0x1: # odd numbered stream
|
||||
p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber
|
||||
ip = p.Extensions[ip4]
|
||||
ip.src_ip = 0x0a0a0165
|
||||
ip.dst_ip = 0x0a0a0265
|
||||
if sscfg['num_var_pkts'][i]:
|
||||
ip4.src_ip_mode = Ip4.e_im_inc_host
|
||||
ip4.src_ip_count = sscfg['num_var_pkts'][i]
|
||||
s.protocol.add().protocol_id.id = ost_pb.Protocol.kUdpFieldNumber
|
||||
else: # even numbered stream
|
||||
s.core.frame_len = 96 # IPv6 needs a larger frame
|
||||
p.protocol_id.id = ost_pb.Protocol.kIp6FieldNumber
|
||||
ip = p.Extensions[ip6]
|
||||
ip6addr = ip6_address('1234:1::65/96')
|
||||
ip.src_addr_hi = ip6addr.ip6.hi
|
||||
ip.src_addr_lo = ip6addr.ip6.lo
|
||||
ip6addr = ip6_address('1234:2::65/96')
|
||||
ip.dst_addr_hi = ip6addr.ip6.hi
|
||||
ip.dst_addr_lo = ip6addr.ip6.lo
|
||||
if sscfg['num_var_pkts'][i]:
|
||||
ip.dst_addr_mode = Ip6.kIncHost
|
||||
ip.dst_addr_count = sscfg['num_var_pkts'][i]
|
||||
ip.dst_addr_prefix = ip6addr.prefixlen
|
||||
s.protocol.add().protocol_id.id = ost_pb.Protocol.kTcpFieldNumber
|
||||
|
||||
s.protocol.add().protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber
|
||||
if sscfg['guid'][i] > 0:
|
||||
p = s.protocol.add()
|
||||
p.protocol_id.id = ost_pb.Protocol.kSignFieldNumber
|
||||
p.Extensions[sign].stream_guid = sscfg['guid'][i]
|
||||
|
||||
if sscfg['loop']:
|
||||
stream_cfg.stream[-1].control.next = ost_pb.StreamControl.e_nw_goto_id
|
||||
|
||||
drone.modifyStream(stream_cfg)
|
||||
|
||||
def fin():
|
||||
# delete streams
|
||||
log.info('deleting tx_stream ' + str(stream_id))
|
||||
drone.deleteStream(stream_id)
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
return stream_cfg
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def stream_guids(request, drone, ports):
|
||||
stream_guids = ost_pb.StreamGuidList()
|
||||
stream_guids.port_id_list.port_id.add().id = ports.x.port_id[0].id;
|
||||
stream_guids.port_id_list.port_id.add().id = ports.y.port_id[0].id;
|
||||
return stream_guids
|
||||
|
||||
# ================================================================= #
|
||||
# ----------------------------------------------------------------- #
|
||||
# TEST CASES
|
||||
# ----------------------------------------------------------------- #
|
||||
# ================================================================= #
|
||||
|
||||
@pytest.mark.parametrize('sign_stream_cfg', [
|
||||
# Min Seq Size = 256 on Win32 and 16 on other platforms, keep this in
|
||||
# mind while configuring the below num_pkts
|
||||
# Verify tx pkts = multiple complete repeats of packetList + no partials
|
||||
{'num_streams': 3, 'num_pkts': [10, 270, 512], 'num_var_pkts': [0, 0, 0],
|
||||
'guid': [-1, 101, 102], 'loop': False},
|
||||
# Verify tx pkts = multiple repeats of packetList + partial repeat
|
||||
{'num_streams': 3, 'num_pkts': [10, 276, 510], 'num_var_pkts': [0, 0, 0],
|
||||
'guid': [-1, 101, 102], 'loop': True},
|
||||
# Verify tx pkts = first seq partial
|
||||
{'num_streams': 1, 'num_pkts': [12], 'num_var_pkts': [0],
|
||||
'guid': [101], 'loop': True},
|
||||
])
|
||||
def test_unidir(drone, ports, dut, dut_ports, dut_ip, emul_ports, dgid_list,
|
||||
sign_stream_cfg, stream_clear, stream, stream_guids):
|
||||
""" TESTCASE: Verify that uni-directional stream stats are correct for a
|
||||
single signed stream X --> Y
|
||||
DUT
|
||||
/.1 \.1
|
||||
/ \
|
||||
10.10.1/24 10.10.2/24
|
||||
1234::1/96 1234::2/96
|
||||
/ \
|
||||
/.101-105 \.101-105
|
||||
HostX HostY
|
||||
"""
|
||||
|
||||
# calculate tx pkts
|
||||
total_tx_pkts = 0
|
||||
for pkts in sign_stream_cfg['num_pkts']:
|
||||
total_tx_pkts += pkts
|
||||
|
||||
num_sign_streams = sum(1 for i in sign_stream_cfg['guid'] if i > 0)
|
||||
|
||||
# clear port X/Y stats
|
||||
log.info('clearing stats')
|
||||
drone.clearStats(ports.x)
|
||||
drone.clearStats(ports.y)
|
||||
|
||||
# clear and verify stream strats are indeed cleared
|
||||
drone.clearStreamStats(stream_guids)
|
||||
ssd = drone.getStreamStatsDict(stream_guids)
|
||||
assert len(ssd.port) == 0
|
||||
|
||||
# resolve ARP/NDP on ports X/Y
|
||||
log.info('resolving Neighbors on (X, Y) ports ...')
|
||||
# wait for interface to do DAD? Otherwise we don't get replies for NS
|
||||
# FIXME: find alternative to sleep
|
||||
time.sleep(5)
|
||||
drone.resolveDeviceNeighbors(emul_ports)
|
||||
time.sleep(3)
|
||||
|
||||
# clear ARP/NDP cache before ping from DUT to devices
|
||||
# - this helps set up ARP/NDP on DUT so that it doesn't age out
|
||||
# while traffic is flowing
|
||||
sudo('ip neigh flush all')
|
||||
out = run('ping -c3 10.10.1.101', warn_only=True)
|
||||
assert '100% packet loss' not in out
|
||||
out = run('ping -c3 10.10.2.101', warn_only=True)
|
||||
assert '100% packet loss' not in out
|
||||
out = run('ping -6 -c3 1234:1::65', warn_only=True)
|
||||
assert '100% packet loss' not in out
|
||||
out = run('ping -6 -c3 1234:2::65', warn_only=True)
|
||||
assert '100% packet loss' not in out
|
||||
|
||||
# FIXME: dump ARP/NDP table on devices and DUT
|
||||
|
||||
try:
|
||||
#run('ip -6 neigh show')
|
||||
|
||||
drone.startCapture(ports.y)
|
||||
drone.startCapture(ports.x)
|
||||
time.sleep(1)
|
||||
drone.startTransmit(ports.x)
|
||||
log.info('waiting for transmit to finish ...')
|
||||
time.sleep(random.randint(3, 10))
|
||||
drone.stopTransmit(ports.x)
|
||||
time.sleep(1)
|
||||
drone.stopCapture(ports.x)
|
||||
drone.stopCapture(ports.y)
|
||||
|
||||
#run('ip -6 neigh show')
|
||||
|
||||
# verify port stats
|
||||
x_stats = drone.getStats(ports.x)
|
||||
log.info('--> (x_stats)' + x_stats.__str__())
|
||||
if not sign_stream_cfg['loop']:
|
||||
assert(x_stats.port_stats[0].tx_pkts >= total_tx_pkts)
|
||||
|
||||
y_stats = drone.getStats(ports.y)
|
||||
log.info('--> (y_stats)' + y_stats.__str__())
|
||||
if not sign_stream_cfg['loop']:
|
||||
assert(y_stats.port_stats[0].rx_pkts >= total_tx_pkts)
|
||||
|
||||
ssd = drone.getStreamStatsDict(stream_guids)
|
||||
log.info('--> (stream stats)\n' + str(ssd))
|
||||
assert len(ssd.port) == 2
|
||||
assert len(ssd.port[ports.x_num].sguid) == num_sign_streams
|
||||
assert len(ssd.port[ports.y_num].sguid) == num_sign_streams
|
||||
assert len(ssd.sguid) == num_sign_streams
|
||||
|
||||
# dump X capture buffer
|
||||
log.info('getting X capture buffer')
|
||||
buff = drone.getCaptureBuffer(ports.x.port_id[0])
|
||||
drone.saveCaptureBuffer(buff, 'capX.pcap')
|
||||
#log.info('dumping X capture buffer')
|
||||
#cap_pkts = subprocess.check_output([tshark, '-n', '-r', 'capX.pcap'])
|
||||
#print(cap_pkts)
|
||||
|
||||
# get and verify each sign stream Tx stats from capture
|
||||
for i in range(sign_stream_cfg['num_streams']):
|
||||
guid = sign_stream_cfg['guid'][i]
|
||||
if guid < 0:
|
||||
continue
|
||||
filter='frame[-9:9]==00.' \
|
||||
+ re.sub('..', '\g<0>.', format(guid, '06x')) \
|
||||
+ '61.1d.10.c0.da && !icmp && !icmpv6'
|
||||
print(filter)
|
||||
cap_pkts = subprocess.check_output([tshark, '-n', '-r', 'capX.pcap',
|
||||
'-Y', filter])
|
||||
#log.info('dumping X capture buffer (filtered)')
|
||||
#print(cap_pkts)
|
||||
sign_stream_cnt = cap_pkts.count('\n')
|
||||
|
||||
log.info('guid %d tx cap count: %d' % (guid, sign_stream_cnt))
|
||||
#log.info('--> (stream stats)\n' + str(ssd))
|
||||
|
||||
# verify tx stream stats from drone is same as that from capture
|
||||
assert ssd.port[ports.x_num].sguid[guid].tx_pkts \
|
||||
== sign_stream_cnt
|
||||
assert ssd.port[ports.x_num].sguid[guid].tx_bytes \
|
||||
== (sign_stream_cnt
|
||||
* (stream.stream[i].core.frame_len - 4))
|
||||
|
||||
# verify tx stream stats from drone is same as configured
|
||||
if not sign_stream_cfg['loop']:
|
||||
assert ssd.port[ports.x_num].sguid[guid].tx_pkts \
|
||||
== sign_stream_cfg['num_pkts'][i]
|
||||
assert ssd.port[ports.x_num].sguid[guid].tx_bytes \
|
||||
== (sign_stream_cfg['num_pkts'][i]
|
||||
* (stream.stream[i].core.frame_len - 4))
|
||||
os.remove('capX.pcap')
|
||||
|
||||
# dump Y capture buffer
|
||||
log.info('getting Y capture buffer')
|
||||
buff = drone.getCaptureBuffer(ports.y.port_id[0])
|
||||
drone.saveCaptureBuffer(buff, 'capY.pcap')
|
||||
#log.info('dumping Y capture buffer')
|
||||
#cap_pkts = subprocess.check_output([tshark, '-n', '-r', 'capY.pcap'])
|
||||
#print(cap_pkts)
|
||||
|
||||
# get and verify each sign stream Rx stats from capture
|
||||
for i in range(sign_stream_cfg['num_streams']):
|
||||
guid = sign_stream_cfg['guid'][i]
|
||||
if guid < 0:
|
||||
continue
|
||||
filter='frame[-9:9]==00.' \
|
||||
+ re.sub('..', '\g<0>.', format(guid, '06x')) \
|
||||
+ '61.1d.10.c0.da && !icmp && !icmpv6'
|
||||
print(filter)
|
||||
cap_pkts = subprocess.check_output([tshark, '-n', '-r', 'capY.pcap',
|
||||
'-Y', filter])
|
||||
#log.info('dumping X capture buffer (filtered)')
|
||||
#print(cap_pkts)
|
||||
sign_stream_cnt = cap_pkts.count('\n')
|
||||
|
||||
log.info('guid %d rx cap count: %d' % (guid, sign_stream_cnt))
|
||||
#log.info('--> (stream stats)\n' + str(ssd))
|
||||
|
||||
# verify rx stream stats from drone is same as that from capture
|
||||
assert ssd.port[ports.y_num].sguid[guid].rx_pkts \
|
||||
== sign_stream_cnt
|
||||
assert ssd.port[ports.y_num].sguid[guid].rx_bytes \
|
||||
== (sign_stream_cnt
|
||||
* (stream.stream[i].core.frame_len - 4))
|
||||
|
||||
# verify rx stream stats from drone is same as configured
|
||||
if not sign_stream_cfg['loop']:
|
||||
assert ssd.port[ports.y_num].sguid[guid].rx_pkts \
|
||||
== sign_stream_cfg['num_pkts'][i]
|
||||
assert ssd.port[ports.y_num].sguid[guid].rx_bytes \
|
||||
== (sign_stream_cfg['num_pkts'][i]
|
||||
* (stream.stream[i].core.frame_len - 4))
|
||||
os.remove('capY.pcap')
|
||||
|
||||
# verify tx == rx
|
||||
for i in range(sign_stream_cfg['num_streams']):
|
||||
guid = sign_stream_cfg['guid'][i]
|
||||
if guid < 0:
|
||||
continue
|
||||
assert ssd.port[ports.x_num].sguid[guid].tx_pkts \
|
||||
== ssd.port[ports.y_num].sguid[guid].rx_pkts
|
||||
assert ssd.port[ports.x_num].sguid[guid].tx_bytes \
|
||||
== ssd.port[ports.y_num].sguid[guid].rx_bytes
|
||||
|
||||
assert ssd.sguid[guid].total.tx_pkts \
|
||||
== ssd.sguid[guid].total.rx_pkts
|
||||
assert ssd.sguid[guid].total.pkt_loss == 0
|
||||
|
||||
# for unidir verify rx on tx port is 0 and vice versa
|
||||
assert ssd.port[ports.x_num].sguid[guid].rx_pkts == 0
|
||||
assert ssd.port[ports.x_num].sguid[guid].rx_bytes == 0
|
||||
assert ssd.port[ports.y_num].sguid[guid].tx_pkts == 0
|
||||
assert ssd.port[ports.y_num].sguid[guid].tx_bytes == 0
|
||||
|
||||
except RpcError as e:
|
||||
raise
|
||||
finally:
|
||||
drone.stopTransmit(ports.x)
|
||||
|
||||
#
|
||||
# TODO
|
||||
# * Verify that bi-directional stream stats are correct for multiple streams
|
||||
# * Verify transmit modes
|
||||
#
|
Loading…
Reference in New Issue
Block a user