/* 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 */ #include "portstatsmodel.h" #include "portgrouplist.h" #include #include #include 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())); 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())); 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(" Link %2") .arg(linkIcon) .arg(LinkStateName.at( stats.state().link_state())); if (stats.state().is_transmit_on()) tooltip.prepend("" " Transmit On
"); if (stats.state().is_capture_on()) tooltip.append("
" " 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); portName = QString("Port %1-%2") .arg(pgl->mPortGroups.at(portGroupIdx)->id()) .arg(pgl->mPortGroups.at(portGroupIdx)->mPorts.at(portIdx)->id()); if (portGroupIdx < (uint) pgl->mPortGroups.size() && portIdx < (uint) pgl->mPortGroups.at(portGroupIdx)->mPorts.size()) { if (!pgl->mPortGroups.at(portGroupIdx)->mPorts[portIdx]->notes() .isEmpty()) portName += " *"; } 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 &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(); } // 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; }