8dbd01622c
Also remove sent/recv byte rates as we have bit rates and that should be sufficient
459 lines
13 KiB
C++
459 lines
13 KiB
C++
/*
|
|
Copyright (C) 2010 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 "portstatsmodel.h"
|
|
#include "portgrouplist.h"
|
|
|
|
#include <QPainter>
|
|
#include <QPixmapCache>
|
|
#include <QTimer>
|
|
|
|
enum {
|
|
// XXX: The byte stats don't include FCS so include it in the overhead
|
|
kPerPacketByteOverhead = 24 // 1(SFD)+7(Preamble)+12(IPG)+4(FCS)
|
|
};
|
|
|
|
PortStatsModel::PortStatsModel(PortGroupList *p, QObject *parent)
|
|
: QAbstractTableModel(parent)
|
|
{
|
|
pgl = p;
|
|
|
|
timer = new QTimer();
|
|
connect(timer, SIGNAL(timeout()), this, SLOT(updateStats()));
|
|
timer->start(1000);
|
|
}
|
|
|
|
PortStatsModel::~PortStatsModel()
|
|
{
|
|
timer->stop();
|
|
delete timer;
|
|
}
|
|
|
|
int PortStatsModel::rowCount(const QModelIndex &parent) const
|
|
{
|
|
if (parent.isValid())
|
|
return 0;
|
|
|
|
if (numPorts.isEmpty())
|
|
return 0;
|
|
|
|
if (numPorts.last() == 0)
|
|
return 0;
|
|
|
|
return (int) e_STAT_MAX;
|
|
}
|
|
|
|
int PortStatsModel::columnCount(const QModelIndex &parent ) const
|
|
{
|
|
if (parent.isValid())
|
|
return 0;
|
|
else
|
|
if (numPorts.isEmpty())
|
|
return 0;
|
|
else
|
|
return numPorts.last();
|
|
}
|
|
|
|
void PortStatsModel::getDomainIndexes(const QModelIndex &index,
|
|
uint &portGroupIdx, uint &portIdx) const
|
|
{
|
|
int portNum;
|
|
|
|
// TODO(LOW): Optimize using binary search: see qLowerBound()
|
|
portNum = index.column() + 1;
|
|
for (portGroupIdx = 0; portGroupIdx < (uint) numPorts.size(); portGroupIdx++)
|
|
if (portNum <= numPorts.at(portGroupIdx))
|
|
break;
|
|
|
|
if (portGroupIdx)
|
|
{
|
|
if (numPorts.at(portGroupIdx -1))
|
|
portIdx = (portNum - 1) - numPorts.at(portGroupIdx - 1);
|
|
else
|
|
portIdx = portNum - 1;
|
|
}
|
|
else
|
|
portIdx = portNum - 1;
|
|
|
|
//qDebug("PSM: %d - %d, %d", index.column(), portGroupIdx, portIdx);
|
|
}
|
|
|
|
QVariant PortStatsModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
// Check for a valid index
|
|
if (!index.isValid())
|
|
return QVariant();
|
|
|
|
// Check for row/column limits
|
|
int row = index.row();
|
|
if (row >= e_STAT_MAX)
|
|
return QVariant();
|
|
|
|
if (numPorts.isEmpty())
|
|
return QVariant();
|
|
|
|
if (index.column() >= (numPorts.last()))
|
|
return QVariant();
|
|
|
|
if (role == Qt::TextAlignmentRole)
|
|
{
|
|
if (row >= e_STATISTICS_START && row <= e_STATISTICS_END)
|
|
return Qt::AlignRight; // right-align numbers
|
|
else
|
|
return Qt::AlignHCenter; // center-align everything else
|
|
}
|
|
|
|
uint pgidx, pidx;
|
|
getDomainIndexes(index, pgidx, pidx);
|
|
|
|
OstProto::PortStats stats = pgl->mPortGroups.at(pgidx)
|
|
->mPorts[pidx]->getStats();
|
|
// Check role
|
|
if (role == Qt::DisplayRole)
|
|
{
|
|
switch(row)
|
|
{
|
|
// Info
|
|
case e_INFO_USER:
|
|
return pgl->mPortGroups.at(pgidx)->mPorts[pidx]->userName();
|
|
|
|
// States
|
|
case e_COMBO_STATE:
|
|
return QVariant();
|
|
|
|
// Statistics
|
|
case e_STAT_FRAMES_RCVD:
|
|
return QString("%L1").arg(quint64(stats.rx_pkts()));
|
|
|
|
case e_STAT_FRAMES_SENT:
|
|
return QString("%L1").arg(quint64(stats.tx_pkts()));
|
|
|
|
case e_STAT_FRAME_SEND_RATE:
|
|
return QString("%L1").arg(quint64(stats.tx_pps()));
|
|
|
|
case e_STAT_FRAME_RECV_RATE:
|
|
return QString("%L1").arg(quint64(stats.rx_pps()));
|
|
|
|
case e_STAT_BYTES_RCVD:
|
|
return QString("%L1").arg(quint64(stats.rx_bytes()));
|
|
|
|
case e_STAT_BYTES_SENT:
|
|
return QString("%L1").arg(quint64(stats.tx_bytes()));
|
|
|
|
#if 0
|
|
case e_STAT_BYTE_SEND_RATE:
|
|
return QString("%L1").arg(quint64(stats.tx_bps()));
|
|
|
|
case e_STAT_BYTE_RECV_RATE:
|
|
return QString("%L1").arg(quint64(stats.rx_bps()));
|
|
#endif
|
|
|
|
case e_STAT_BIT_SEND_RATE:
|
|
return QString("%L1").arg(quint64(
|
|
stats.tx_bps()
|
|
+ stats.tx_pps()*kPerPacketByteOverhead)*8);
|
|
|
|
case e_STAT_BIT_RECV_RATE:
|
|
return QString("%L1").arg(quint64(
|
|
stats.rx_bps()
|
|
+ stats.rx_pps()*kPerPacketByteOverhead)*8);
|
|
|
|
#if 0
|
|
case e_STAT_FRAMES_RCVD_NIC:
|
|
return stats.rx_pkts_nic();
|
|
|
|
case e_STAT_FRAMES_SENT_NIC:
|
|
return stats.tx_pkts_nic();
|
|
|
|
case e_STAT_BYTES_RCVD_NIC:
|
|
return stats.rx_bytes_nic();
|
|
|
|
case e_STAT_BYTES_SENT_NIC:
|
|
return stats.tx_bytes_nic();
|
|
#endif
|
|
|
|
case e_STAT_RX_DROPS:
|
|
return QString("%L1").arg(quint64(stats.rx_drops()));
|
|
case e_STAT_RX_ERRORS:
|
|
return QString("%L1").arg(quint64(stats.rx_errors()));
|
|
case e_STAT_RX_FIFO_ERRORS:
|
|
return QString("%L1").arg(quint64(stats.rx_fifo_errors()));
|
|
case e_STAT_RX_FRAME_ERRORS:
|
|
return QString("%L1").arg(quint64(stats.rx_frame_errors()));
|
|
|
|
default:
|
|
qWarning("%s: Unhandled stats id %d\n", __FUNCTION__,
|
|
index.row());
|
|
return 0;
|
|
}
|
|
}
|
|
else if (role == Qt::DecorationRole)
|
|
{
|
|
if (row == e_COMBO_STATE)
|
|
return statusIcons(
|
|
stats.state().link_state(),
|
|
stats.state().is_transmit_on(),
|
|
stats.state().is_capture_on());
|
|
else
|
|
return QVariant();
|
|
}
|
|
else if (role == Qt::ToolTipRole)
|
|
{
|
|
if (row == e_COMBO_STATE) {
|
|
QString linkIcon;
|
|
switch (stats.state().link_state()) {
|
|
case OstProto::LinkStateUp:
|
|
linkIcon = ":/icons/bullet_green.png";
|
|
break;
|
|
case OstProto::LinkStateDown:
|
|
linkIcon = ":/icons/bullet_red.png";
|
|
break;
|
|
case OstProto::LinkStateUnknown:
|
|
linkIcon = ":/icons/bullet_white.png";
|
|
break;
|
|
}
|
|
// FIXME: Ideally, the text should be vertically centered wrt icon
|
|
// but style='vertical-align:middle for the img tag doesn't work
|
|
QString tooltip = QString("<img src='%1'/> Link %2")
|
|
.arg(linkIcon)
|
|
.arg(LinkStateName.at(
|
|
stats.state().link_state()));
|
|
if (stats.state().is_transmit_on())
|
|
tooltip.prepend("<img src=':/icons/transmit_on.png'/>"
|
|
" Transmit On<br/>");
|
|
if (stats.state().is_capture_on())
|
|
tooltip.append("<br/><img src=':/icons/sound_none.png'/>"
|
|
" Capture On");
|
|
return tooltip;
|
|
}
|
|
else
|
|
return QVariant();
|
|
}
|
|
else
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
QVariant PortStatsModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
{
|
|
if (role == Qt::ToolTipRole)
|
|
{
|
|
if (orientation == Qt::Horizontal)
|
|
{
|
|
QString notes;
|
|
uint portGroupIdx, portIdx;
|
|
|
|
if (numPorts.isEmpty() || section >= numPorts.last())
|
|
return QVariant();
|
|
getDomainIndexes(index(0, section), portGroupIdx, portIdx);
|
|
notes = pgl->mPortGroups.at(portGroupIdx)->mPorts[portIdx]->notes();
|
|
if (!notes.isEmpty())
|
|
return notes;
|
|
else
|
|
return QVariant();
|
|
}
|
|
else
|
|
return QVariant();
|
|
}
|
|
|
|
if (role != Qt::DisplayRole)
|
|
return QVariant();
|
|
|
|
if (orientation == Qt::Horizontal)
|
|
{
|
|
uint portGroupIdx, portIdx;
|
|
QString portName;
|
|
|
|
if (numPorts.isEmpty() || section >= numPorts.last())
|
|
return QVariant();
|
|
|
|
getDomainIndexes(index(0, section), portGroupIdx, portIdx);
|
|
|
|
PortGroup *portGroup = pgl->mPortGroups.at(portGroupIdx);
|
|
Port *port = portGroup->mPorts.at(portIdx);
|
|
|
|
portName = QString("Port %1-%2")
|
|
.arg(portGroup->id())
|
|
.arg(port->id());
|
|
if (portGroupIdx < (uint) pgl->mPortGroups.size()
|
|
&& portIdx < (uint) portGroup->mPorts.size())
|
|
{
|
|
if (!port->notes().isEmpty())
|
|
portName += " *";
|
|
portName += "\n";
|
|
portName += port->userDescription().isEmpty() ?
|
|
port->userAlias() : port->userDescription();
|
|
}
|
|
return portName;
|
|
}
|
|
else
|
|
return PortStatName.at(section);
|
|
}
|
|
|
|
Qt::DropActions PortStatsModel::supportedDropActions() const
|
|
{
|
|
return Qt::IgnoreAction; // read-only model, doesn't accept any data
|
|
}
|
|
|
|
void PortStatsModel::portListFromIndex(QModelIndexList indices,
|
|
QList<PortGroupAndPortList> &portList)
|
|
{
|
|
int i, j;
|
|
QModelIndexList selectedCols(indices);
|
|
|
|
portList.clear();
|
|
|
|
//selectedCols = indices.selectedColumns();
|
|
for (i = 0; i < selectedCols.size(); i++)
|
|
{
|
|
uint portGroupIdx, portIdx;
|
|
|
|
getDomainIndexes(selectedCols.at(i), portGroupIdx, portIdx);
|
|
for (j = 0; j < portList.size(); j++)
|
|
{
|
|
if (portList[j].portGroupId == portGroupIdx)
|
|
break;
|
|
}
|
|
|
|
if (j >= portList.size())
|
|
{
|
|
// PortGroup Not found
|
|
PortGroupAndPortList p;
|
|
|
|
p.portGroupId = portGroupIdx;
|
|
p.portList.append(portIdx);
|
|
|
|
portList.append(p);
|
|
}
|
|
else
|
|
{
|
|
// PortGroup found
|
|
|
|
portList[j].portList.append(portIdx);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Slots
|
|
//
|
|
void PortStatsModel::when_portListChanged()
|
|
{
|
|
int i, count = 0;
|
|
|
|
beginResetModel();
|
|
|
|
// recalc numPorts
|
|
while (numPorts.size())
|
|
numPorts.removeFirst();
|
|
|
|
for (i = 0; i < pgl->mPortGroups.size(); i++)
|
|
{
|
|
count += pgl->mPortGroups.at(i)->numPorts();
|
|
numPorts.append(count);
|
|
}
|
|
|
|
endResetModel();
|
|
}
|
|
|
|
void PortStatsModel::when_portGroupDataChanged(int /*portGroupId*/, int /*portId*/)
|
|
{
|
|
if (!columnCount())
|
|
return;
|
|
|
|
// Port (user) description may have changed - update column headers
|
|
// TODO: update only the changed ports, not all
|
|
emit headerDataChanged(Qt::Horizontal, 0, columnCount()-1);
|
|
}
|
|
|
|
// FIXME: unused? if used, the index calculation row/column needs to be swapped
|
|
#if 0
|
|
void PortStatsModel::on_portStatsUpdate(int port, void* /*stats*/)
|
|
{
|
|
QModelIndex topLeft = index(port, 0, QModelIndex());
|
|
QModelIndex bottomRight = index(port, e_STAT_MAX, QModelIndex());
|
|
|
|
emit dataChanged(topLeft, bottomRight);
|
|
}
|
|
#endif
|
|
|
|
void PortStatsModel::updateStats()
|
|
{
|
|
// Request each portgroup to fetch updated stats - the port group
|
|
// raises a signal once updated stats are available
|
|
for (int i = 0; i < pgl->mPortGroups.size(); i++)
|
|
pgl->mPortGroups[i]->getPortStats();
|
|
}
|
|
|
|
void PortStatsModel::when_portGroup_stats_update(quint32 /*portGroupId*/)
|
|
{
|
|
// FIXME(MED): update only the changed ports, not all
|
|
|
|
QModelIndex topLeft = index(0, 0, QModelIndex());
|
|
QModelIndex bottomRight = index(rowCount()-1, columnCount()-1, QModelIndex());
|
|
|
|
emit dataChanged(topLeft, bottomRight);
|
|
}
|
|
|
|
QPixmap PortStatsModel::statusIcons(
|
|
int linkState, bool transmit, bool capture) const
|
|
{
|
|
QPixmap pixmap;
|
|
QString key = QString("$ost:statusList:%1:%2:%3")
|
|
.arg(linkState).arg(transmit).arg(capture);
|
|
|
|
if (QPixmapCache::find(key, pixmap))
|
|
return pixmap;
|
|
|
|
static int sz = QPixmap(":/icons/transmit_on.png").width();
|
|
|
|
// Assume all icons are of same size and are square
|
|
QPixmap blank(sz, sz);
|
|
blank.fill(Qt::transparent);
|
|
|
|
pixmap = QPixmap(sz*3, sz);
|
|
pixmap.fill(Qt::transparent);
|
|
|
|
QPainter painter(&pixmap);
|
|
|
|
painter.drawPixmap(0, 0,
|
|
transmit ? QPixmap(":/icons/transmit_on.png") : blank);
|
|
|
|
switch (linkState) {
|
|
case OstProto::LinkStateUp:
|
|
painter.drawPixmap(sz, 0, QPixmap(":/icons/bullet_green.png"));
|
|
break;
|
|
case OstProto::LinkStateDown:
|
|
painter.drawPixmap(sz, 0, QPixmap(":/icons/bullet_red.png"));
|
|
break;
|
|
case OstProto::LinkStateUnknown:
|
|
painter.drawPixmap(sz, 0, QPixmap(":/icons/bullet_white.png"));
|
|
break;
|
|
default:
|
|
painter.drawPixmap(sz, 0, blank);
|
|
}
|
|
|
|
painter.drawPixmap(sz*2, 0,
|
|
capture ? QPixmap(":/icons/sound_none.png") : blank);
|
|
|
|
|
|
QPixmapCache::insert(key, pixmap);
|
|
return pixmap;
|
|
}
|