Qt: Implement total columns for traffic tables

Implement a column type, which will show total values
for traffic columns.

Implements #15071
This commit is contained in:
Roland Knall 2022-06-07 00:22:31 +02:00
parent 0640b711ea
commit 51d5cb42b2
6 changed files with 176 additions and 39 deletions

View File

@ -638,6 +638,10 @@ add_conversation_table_data_with_conv_id(
new_conv_item.tx_frames = 0;
new_conv_item.rx_bytes = 0;
new_conv_item.tx_bytes = 0;
new_conv_item.rx_frames_total = 0;
new_conv_item.tx_frames_total = 0;
new_conv_item.rx_bytes_total = 0;
new_conv_item.tx_bytes_total = 0;
if (ts) {
memcpy(&new_conv_item.start_time, ts, sizeof(new_conv_item.start_time));
@ -782,6 +786,10 @@ add_hostlist_table_data(conv_hash_t *ch, const address *addr, guint32 port, gboo
host.tx_frames=0;
host.rx_bytes=0;
host.tx_bytes=0;
host.rx_frames_total=0;
host.tx_frames_total=0;
host.rx_bytes_total=0;
host.tx_bytes_total=0;
host.modified = TRUE;
host.filtered = TRUE;

View File

@ -111,10 +111,10 @@ typedef struct _conversation_item_t {
guint64 rx_bytes; /**< number of received bytes */
guint64 tx_bytes; /**< number of transmitted bytes */
guint64 rx_frames_total; /**< number of received packets */
guint64 tx_frames_total; /**< number of transmitted packets */
guint64 rx_bytes_total; /**< number of received bytes */
guint64 tx_bytes_total; /**< number of transmitted bytes */
guint64 rx_frames_total; /**< number of received packets total */
guint64 tx_frames_total; /**< number of transmitted packets total */
guint64 rx_bytes_total; /**< number of received bytes total */
guint64 tx_bytes_total; /**< number of transmitted bytes total */
nstime_t start_time; /**< relative start time for the conversation */
nstime_t stop_time; /**< relative stop time for the conversation */
@ -135,10 +135,10 @@ typedef struct _hostlist_talker_t {
guint64 rx_bytes; /**< number of received bytes */
guint64 tx_bytes; /**< number of transmitted bytes */
guint64 rx_frames_total; /**< number of received packets */
guint64 tx_frames_total; /**< number of transmitted packets */
guint64 rx_bytes_total; /**< number of received bytes */
guint64 tx_bytes_total; /**< number of transmitted bytes */
guint64 rx_frames_total; /**< number of received packets total */
guint64 tx_frames_total; /**< number of transmitted packets total */
guint64 rx_bytes_total; /**< number of received bytes total */
guint64 tx_bytes_total; /**< number of transmitted bytes total */
gboolean modified; /**< new to redraw the row */
gboolean filtered; /**< the entry contains only filtered data */

View File

@ -30,10 +30,13 @@
#include <QWidget>
#include <QDateTime>
static QString formatString(qlonglong value)
{
return gchar_free_to_qstring(format_size(value, FORMAT_SIZE_UNIT_NONE, FORMAT_SIZE_PREFIX_SI));
}
ATapDataModel::ATapDataModel(dataModelType type, int protoId, QString filter, QObject *parent):
QAbstractListModel(parent),
_protoId(protoId),
_filter(filter)
QAbstractListModel(parent)
{
hash_.conv_array = nullptr;
hash_.hashtable = nullptr;
@ -44,6 +47,9 @@ ATapDataModel::ATapDataModel(dataModelType type, int protoId, QString filter, QO
_absoluteTime = false;
_nanoseconds = false;
_protoId = protoId;
_filter = filter;
_minRelStartTime = 0;
_maxRelStopTime = 0;
@ -102,8 +108,8 @@ bool ATapDataModel::enableTap()
/* The errorString is ignored. If this is not working, there is nothing really the user may do about
* it, so the error is only interesting to the developer.*/
GString * errorString = register_tap_listener(tap().toUtf8().constData(), hash(), _filter.toUtf8().constData(), 0,
&ATapDataModel::tapReset, conversationPacketHandler(), &ATapDataModel::tapDraw, nullptr);
GString * errorString = register_tap_listener(tap().toUtf8().constData(), hash(), _filter.toUtf8().constData(),
TL_IGNORE_DISPLAY_FILTER, &ATapDataModel::tapReset, conversationPacketHandler(), &ATapDataModel::tapDraw, nullptr);
if (errorString && errorString->len > 0) {
_disableTap = true;
emit tapListenerChanged(false);
@ -209,7 +215,7 @@ void ATapDataModel::updateData(GArray * newData)
endResetModel();
if (_type == ATapDataModel::DATAMODEL_CONVERSATION)
((ConversationDataModel *)(this))->updateData();
((ConversationDataModel *)(this))->doDataUpdate();
}
bool ATapDataModel::resolveNames() const
@ -273,7 +279,7 @@ void ATapDataModel::setFilter(QString filter)
return;
_filter = filter;
GString * errorString = set_tap_dfilter(&hash_, !filter.isEmpty() ? filter.toUtf8().constData() : nullptr);
GString * errorString = set_tap_dfilter(&hash_, !_filter.isEmpty() ? _filter.toUtf8().constData() : nullptr);
if (errorString && errorString->len > 0) {
/* If this fails, chances are that the main system failed as well. Silently exiting as the
* user cannot react to it */
@ -284,6 +290,11 @@ void ATapDataModel::setFilter(QString filter)
g_string_free(errorString, TRUE);
}
QString ATapDataModel::filter() const
{
return _filter;
}
ATapDataModel::dataModelType ATapDataModel::modelType() const
{
return _type;
@ -294,6 +305,12 @@ bool ATapDataModel::portsAreHidden() const
return (get_conversation_hide_ports(registerTable()));
}
bool ATapDataModel::showTotalColumn() const
{
/* Implemented to ensure future changes may be done more easily */
return _filter.length() > 0;
}
EndpointDataModel::EndpointDataModel(int protoId, QString filter, QObject *parent) :
ATapDataModel(ATapDataModel::DATAMODEL_ENDPOINT, protoId, filter, parent)
{}
@ -303,6 +320,8 @@ int EndpointDataModel::columnCount(const QModelIndex &) const
int columnMax = ENDP_NUM_COLUMNS;
if (portsAreHidden())
columnMax--;
if (showTotalColumn())
columnMax += 2;
return columnMax;
}
@ -315,6 +334,14 @@ QVariant EndpointDataModel::headerData(int section, Qt::Orientation orientation,
if (portsAreHidden() && section >= ENDP_COLUMN_PORT)
column += 1;
if (showTotalColumn())
{
if (column == ENDP_COLUMN_PKT_AB || column == ENDP_COLUMN_BYTES_AB)
column += 8;
else if (column > ENDP_COLUMN_BYTES_AB)
column -= 2;
}
if (role == Qt::DisplayRole) {
switch (column) {
case 0:
@ -341,6 +368,10 @@ QVariant EndpointDataModel::headerData(int section, Qt::Orientation orientation,
return tr("AS Number"); break;
case 11:
return tr("AS Organization"); break;
case 12:
return tr("Total Packets"); break;
case 13:
return tr("Percent filtered"); break;
}
} else if (role == Qt::TextAlignmentRole) {
if (section == ENDP_COLUMN_ADDR)
@ -351,11 +382,6 @@ QVariant EndpointDataModel::headerData(int section, Qt::Orientation orientation,
return QVariant();
}
static QString formatString(qlonglong value)
{
return gchar_free_to_qstring(format_size(value, FORMAT_SIZE_UNIT_NONE, FORMAT_SIZE_PREFIX_SI));
}
QVariant EndpointDataModel::data(const QModelIndex &idx, int role) const
{
if (! idx.isValid())
@ -378,12 +404,18 @@ QVariant EndpointDataModel::data(const QModelIndex &idx, int role) const
QString ipAddress(addr);
#endif
int column = idx.column();
if (portsAreHidden() && idx.column() >= ENDP_COLUMN_PORT)
column += 1;
if (showTotalColumn()) {
if (column == ENDP_COLUMN_PKT_AB || column == ENDP_COLUMN_BYTES_AB)
column += 8;
else if (column > ENDP_COLUMN_BYTES_AB)
column -= 2;
}
if (role == Qt::DisplayRole || role == ATapDataModel::UNFORMATTED_DISPLAYDATA) {
int column = idx.column();
if (portsAreHidden() && idx.column() >= ENDP_COLUMN_PORT)
column += 1;
switch (column) {
case ENDP_COLUMN_ADDR: {
char* addr_str = get_conversation_address(NULL, &item->myaddress, _resolveNames);
@ -401,7 +433,10 @@ QVariant EndpointDataModel::data(const QModelIndex &idx, int role) const
return quint32(item->port);
}
case ENDP_COLUMN_PACKETS:
return (qlonglong)(item->tx_frames + item->rx_frames);
{
qlonglong packets = (qlonglong)(item->tx_frames + item->rx_frames);
return packets;
}
case ENDP_COLUMN_BYTES:
return role == Qt::DisplayRole ? formatString((qlonglong)(item->tx_bytes + item->rx_bytes)) :
QVariant((qlonglong)(item->tx_bytes + item->rx_bytes));
@ -433,6 +468,22 @@ QVariant EndpointDataModel::data(const QModelIndex &idx, int role) const
return QVariant(mmdb_lookup->as_org);
}
return QVariant();
case 12:
{
if (showTotalColumn())
return (qlonglong)(item->tx_frames_total + item->rx_frames_total);
return QVariant();
}
case 13:
{
if (showTotalColumn()) {
qlonglong totalPackets = (qlonglong)(item->tx_frames_total + item->rx_frames_total);
qlonglong packets = (qlonglong)(item->tx_frames + item->rx_frames);
double percent = totalPackets == 0 ? 0 : packets * 100 / totalPackets;
return QString::number(percent, 'f', 2) + "%";
}
return QVariant();
}
default:
return QVariant();
}
@ -442,6 +493,8 @@ QVariant EndpointDataModel::data(const QModelIndex &idx, int role) const
return Qt::AlignRight;
} else if (role == ATapDataModel::DISPLAY_FILTER) {
return QString(get_hostlist_filter(item));
} else if (role == ATapDataModel::ROW_IS_FILTERED) {
return (bool)item->filtered && showTotalColumn();
}
#ifdef HAVE_MAXMINDDB
else if (role == ATapDataModel::GEODATA_AVAILABLE) {
@ -460,7 +513,7 @@ ConversationDataModel::ConversationDataModel(int protoId, QString filter, QObjec
ATapDataModel(ATapDataModel::DATAMODEL_CONVERSATION, protoId, filter, parent)
{}
void ConversationDataModel::updateData()
void ConversationDataModel::doDataUpdate()
{
_minRelStartTime = 0;
_maxRelStopTime = 0;
@ -490,6 +543,8 @@ int ConversationDataModel::columnCount(const QModelIndex &) const
int columnMax = CONV_NUM_COLUMNS;
if (portsAreHidden())
columnMax -= 2;
if (showTotalColumn())
columnMax += 2;
return columnMax;
}
@ -506,6 +561,13 @@ QVariant ConversationDataModel::headerData(int section, Qt::Orientation orientat
if (column > CONV_COLUMN_DST_ADDR)
column++;
}
if (showTotalColumn())
{
if (column == CONV_COLUMN_PKT_AB || column == CONV_COLUMN_BYTES_AB)
column += 8;
else if (column > CONV_COLUMN_BYTES_AB)
column -= 2;
}
if (role == Qt::DisplayRole) {
switch (column) {
@ -537,6 +599,10 @@ QVariant ConversationDataModel::headerData(int section, Qt::Orientation orientat
return tr("Bits/s A " UTF8_RIGHTWARDS_ARROW " B"); break;
case 13:
return tr("Bits/s B " UTF8_RIGHTWARDS_ARROW " A"); break;
case 14:
return tr("Total Packets"); break;
case 15:
return tr("Percent filtered"); break;
}
} else if (role == Qt::TextAlignmentRole) {
if (column == CONV_COLUMN_SRC_ADDR || column == CONV_COLUMN_DST_ADDR)
@ -580,6 +646,12 @@ QVariant ConversationDataModel::data(const QModelIndex &idx, int role) const
bpsCalculated = true;
}
if (showTotalColumn()) {
if (column == CONV_COLUMN_PKT_AB || column == CONV_COLUMN_BYTES_AB)
column += 8;
else if (column > CONV_COLUMN_BYTES_AB)
column -= 2;
}
if (role == Qt::DisplayRole || role == ATapDataModel::UNFORMATTED_DISPLAYDATA) {
switch(column) {
case CONV_COLUMN_SRC_ADDR:
@ -651,6 +723,22 @@ QVariant ConversationDataModel::data(const QModelIndex &idx, int role) const
return bpsCalculated ? (role == Qt::DisplayRole ? formatString(bps_ab) : QVariant((qlonglong)bps_ab)): QVariant();
case CONV_COLUMN_BPS_BA:
return bpsCalculated ? (role == Qt::DisplayRole ? formatString(bps_ba) : QVariant((qlonglong)bps_ba)): QVariant();
case 14:
{
if (showTotalColumn())
return (qlonglong)(conv_item->tx_frames_total + conv_item->rx_frames_total);
return (qlonglong) 0;
}
case 15:
{
if (showTotalColumn()) {
qlonglong totalPackets = (qlonglong)(conv_item->tx_frames_total + conv_item->rx_frames_total);
qlonglong packets = (qlonglong)(conv_item->tx_frames + conv_item->rx_frames);
double percent = totalPackets == 0 ? 0 : packets * 100 / totalPackets;
return QString::number(percent, 'f', 2) + "%";
}
return (qlonglong) 0;
}
}
} else if (role == Qt::ToolTipRole) {
if (column == CONV_COLUMN_START || column == CONV_COLUMN_DURATION)
@ -675,6 +763,8 @@ QVariant ConversationDataModel::data(const QModelIndex &idx, int role) const
return (int)(conv_item->etype);
} else if (role == ATapDataModel::CONVERSATION_ID) {
return (int)(conv_item->conv_id);
} else if (role == ATapDataModel::ROW_IS_FILTERED) {
return (bool)conv_item->filtered && showTotalColumn();
}
return QVariant();

View File

@ -46,7 +46,8 @@ public:
#endif
TIMELINE_DATA,
ENDPOINT_DATATYPE,
CONVERSATION_ID
CONVERSATION_ID,
ROW_IS_FILTERED
};
typedef enum {
@ -106,6 +107,13 @@ public:
*/
void setFilter(QString filter);
/**
* @brief Return a filter set for the model
*
* @return QString the filter string for the model
*/
QString filter() const;
/**
* @brief Is the model set to resolve names in address and ports columns
*
@ -203,8 +211,8 @@ protected:
void updateData(GArray * data);
dataModelType _type;
GArray * storage_;
QString _filter;
bool _absoluteTime;
bool _nanoseconds;
@ -216,9 +224,10 @@ protected:
register_ct_t* registerTable() const;
bool showTotalColumn() const;
private:
int _protoId;
QString _filter;
QMap<QString, QVariant> _lookUp;
@ -286,7 +295,7 @@ public:
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const;
void updateData();
void doDataUpdate();
conv_item_t * itemForRow(int row);

View File

@ -66,6 +66,24 @@ int TabData::protoId() const
return _protoId;
}
TrafficDataFilterProxy::TrafficDataFilterProxy(QObject *parent) :
QSortFilterProxyModel(parent)
{}
bool TrafficDataFilterProxy::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
ATapDataModel * dataModel = qobject_cast<ATapDataModel *>(sourceModel());
if (dataModel) {
bool isFiltered = dataModel->data(dataModel->index(source_row, 0), ATapDataModel::ROW_IS_FILTERED).toBool();
if (dataModel->filter().length() > 0)
return ! isFiltered;
}
return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
}
static gboolean iterateProtocols(const void *key, void *value, void *userdata)
{
QMap<int, QString> *protocols = (QMap<int, QString> *)userdata;
@ -211,7 +229,7 @@ QTreeView * TrafficTab::createTree(int protoId)
}
}
QSortFilterProxyModel * proxyModel = new QSortFilterProxyModel();
TrafficDataFilterProxy * proxyModel = new TrafficDataFilterProxy();
proxyModel->setSourceModel(model);
tree->setModel(proxyModel);
@ -221,13 +239,13 @@ QTreeView * TrafficTab::createTree(int protoId)
tree->sortByColumn(0, Qt::AscendingOrder);
connect(proxyModel, &QSortFilterProxyModel::modelReset, this, [tree]() {
connect(proxyModel, &TrafficDataFilterProxy::modelReset, this, [tree]() {
if (tree->model()->rowCount() > 0) {
for (int col = 0; col < tree->model()->columnCount(); col++)
tree->resizeColumnToContents(col);
}
});
connect(proxyModel, &QSortFilterProxyModel::modelReset, this, &TrafficTab::modelReset);
connect(proxyModel, &TrafficDataFilterProxy::modelReset, this, &TrafficTab::modelReset);
}
return tree;
@ -339,7 +357,7 @@ void TrafficTab::doCurrentIndexChange(const QModelIndex & cur, const QModelIndex
if (! cur.isValid())
return;
const QSortFilterProxyModel * proxy = qobject_cast<const QSortFilterProxyModel *>(cur.model());
const TrafficDataFilterProxy * proxy = qobject_cast<const TrafficDataFilterProxy *>(cur.model());
if (! proxy)
return;
@ -371,10 +389,10 @@ QVariant TrafficTab::currentItemData(int role)
void TrafficTab::modelReset()
{
if (! qobject_cast<QSortFilterProxyModel *>(sender()))
if (! qobject_cast<TrafficDataFilterProxy *>(sender()))
return;
QSortFilterProxyModel * qsfpm = qobject_cast<QSortFilterProxyModel *>(sender());
TrafficDataFilterProxy * qsfpm = qobject_cast<TrafficDataFilterProxy *>(sender());
if (! qobject_cast<ATapDataModel *>(qsfpm->sourceModel()))
return;
@ -408,8 +426,8 @@ ATapDataModel * TrafficTab::modelForWidget(QWidget * searchWidget)
{
if (qobject_cast<QTreeView *>(searchWidget)) {
QTreeView * tree = qobject_cast<QTreeView *>(searchWidget);
if (qobject_cast<QSortFilterProxyModel *>(tree->model())) {
QSortFilterProxyModel * qsfpm = qobject_cast<QSortFilterProxyModel *>(tree->model());
if (qobject_cast<TrafficDataFilterProxy *>(tree->model())) {
TrafficDataFilterProxy * qsfpm = qobject_cast<TrafficDataFilterProxy *>(tree->model());
if (qobject_cast<ATapDataModel *>(qsfpm->sourceModel())) {
return qobject_cast<ATapDataModel *>(qsfpm->sourceModel());
}

View File

@ -23,6 +23,7 @@
#include <QFile>
#include <QUrl>
#include <QAbstractItemDelegate>
#include <QSortFilterProxyModel>
/**
* @brief Callback for creating an ATapDataModel
@ -62,6 +63,17 @@ private:
Q_DECLARE_METATYPE(TabData)
class TrafficDataFilterProxy : public QSortFilterProxyModel
{
Q_OBJECT
public:
TrafficDataFilterProxy(QObject *parent = nullptr);
protected:
virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
};
/**
* @brief A QTabWidget class, providing tap information
*