wireshark/ui/qt/lte_rlc_statistics_dialog.cpp

1021 lines
34 KiB
C++

/* lte_rlc_statistics_dialog.cpp
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "lte_rlc_statistics_dialog.h"
#include <epan/packet.h>
#include <epan/strutil.h>
#include <epan/tap.h>
#include <epan/dissectors/packet-rlc-lte.h>
#include <QFormLayout>
#include <QTreeWidgetItem>
#include <QPushButton>
#include "wireshark_application.h"
#include "ui/recent.h"
// TODO: have never tested in a live capture.
enum {
col_ueid_,
col_mode_, // channel only
col_priority_, // channel only
col_ul_frames_,
col_ul_bytes_,
col_ul_mb_s_,
col_ul_acks_,
col_ul_nacks_,
col_ul_missing_,
col_dl_frames_,
col_dl_bytes_,
col_dl_mb_s_,
col_dl_acks_,
col_dl_nacks_,
col_dl_missing_
};
enum {
rlc_ue_row_type_ = 1000,
rlc_channel_row_type_
};
/* Calculate and return a bandwidth figure, in Mbs */
static double calculate_bw(const nstime_t *start_time, const nstime_t *stop_time, guint32 bytes)
{
/* Can only calculate bandwidth if have time delta */
if (memcmp(start_time, stop_time, sizeof(nstime_t)) != 0) {
double elapsed_ms = (((double)stop_time->secs - start_time->secs) * 1000) +
(((double)stop_time->nsecs - start_time->nsecs) / 1000000);
/* Only really meaningful if have a few frames spread over time...
For now at least avoid dividing by something very close to 0.0 */
if (elapsed_ms < 2.0) {
return 0.0f;
}
// N.B. very small values will display as scientific notation, but rather that than show 0
// when there is some traffic..
return ((bytes * 8) / elapsed_ms) / 1000;
}
else {
return 0.0f;
}
}
// Stats kept for one channel.
typedef struct rlc_channel_stats {
guint8 rlcMode;
guint8 priority;
guint16 channelType;
guint16 channelId;
guint32 UL_frames;
guint32 UL_bytes;
nstime_t UL_time_start;
nstime_t UL_time_stop;
gboolean UL_has_data; // i.e. not just ACKs for DL.
guint32 DL_frames;
guint32 DL_bytes;
nstime_t DL_time_start;
nstime_t DL_time_stop;
gboolean DL_has_data; // i.e. not just ACKs for UL.
guint32 UL_acks;
guint32 UL_nacks;
guint32 DL_acks;
guint32 DL_nacks;
guint32 UL_missing;
guint32 DL_missing;
} rlc_channel_stats;
//-------------------------------------------------------------------
// Channel item.
//-------------------------------------------------------------------
class RlcChannelTreeWidgetItem : public QTreeWidgetItem
{
public:
RlcChannelTreeWidgetItem(QTreeWidgetItem *parent,
unsigned ueid,
unsigned mode,
unsigned channelType, unsigned channelId) :
QTreeWidgetItem(parent, rlc_channel_row_type_),
ueid_(ueid),
channelType_(channelType),
channelId_(channelId),
mode_(mode),
priority_(0)
{
QString mode_str;
switch (mode_) {
case RLC_TM_MODE:
mode_str = QObject::tr("TM");
break;
case RLC_UM_MODE:
mode_str = QObject::tr("UM");
break;
case RLC_AM_MODE:
mode_str = QObject::tr("AM");
break;
case RLC_PREDEF:
mode_str = QObject::tr("Predef");
break;
default:
mode_str = QObject::tr("Unknown (%1)").arg(mode_);
break;
}
// Set name of channel.
switch (channelType) {
case CHANNEL_TYPE_CCCH:
setText(col_ueid_, QObject::tr("CCCH"));
break;
case CHANNEL_TYPE_SRB:
setText(col_ueid_, QObject::tr("SRB-%1").arg(channelId));
break;
case CHANNEL_TYPE_DRB:
setText(col_ueid_, QObject::tr("DRB-%1").arg(channelId));
break;
default:
setText(col_ueid_, QObject::tr("Unknown"));
break;
}
// Zero out stats.
memset(&stats_, 0, sizeof(stats_));
// TODO: could change, but should only reset string if changes.
setText(col_mode_, mode_str);
}
// Update UE/channels from tap info.
void update(const rlc_lte_tap_info *tap_info) {
// Copy these fields into UE stats.
if (tap_info->rlcMode != stats_.rlcMode) {
stats_.rlcMode = tap_info->rlcMode;
// TODO: update the column string!
}
// TODO: these 2 really shouldn't change!!
stats_.channelType = tap_info->channelType;
stats_.channelId = tap_info->channelId;
if (tap_info->priority != 0) {
priority_ = tap_info->priority;
// TODO: update the column string!
}
if (tap_info->direction == DIRECTION_UPLINK) {
// Update time range.
if (stats_.UL_frames == 0) {
stats_.UL_time_start = tap_info->rlc_lte_time;
}
stats_.UL_time_stop = tap_info->rlc_lte_time;
stats_.UL_frames++;
stats_.UL_bytes += tap_info->pduLength;
stats_.UL_nacks += tap_info->noOfNACKs;
stats_.UL_missing += tap_info->missingSNs;
if (tap_info->isControlPDU) {
stats_.UL_acks++;
}
else {
stats_.UL_has_data = TRUE;
}
}
else {
// Update time range.
if (stats_.DL_frames == 0) {
stats_.DL_time_start = tap_info->rlc_lte_time;
}
stats_.DL_time_stop = tap_info->rlc_lte_time;
stats_.DL_frames++;
stats_.DL_bytes += tap_info->pduLength;
stats_.DL_nacks += tap_info->noOfNACKs;
stats_.DL_missing += tap_info->missingSNs;
if (tap_info->isControlPDU) {
stats_.DL_acks++;
}
else {
stats_.DL_has_data = TRUE;
}
}
}
// Draw channel entry.
void draw() {
// Calculate bandwidths.
double UL_bw = calculate_bw(&stats_.UL_time_start,
&stats_.UL_time_stop,
stats_.UL_bytes);
double DL_bw = calculate_bw(&stats_.DL_time_start,
&stats_.DL_time_stop,
stats_.DL_bytes);
// Priority
setText(col_priority_, QString::number(priority_));
// Uplink.
setText(col_ul_frames_, QString::number(stats_.UL_frames));
setText(col_ul_bytes_, QString::number(stats_.UL_bytes));
setText(col_ul_mb_s_, QString::number(UL_bw));
setText(col_ul_acks_, QString::number(stats_.UL_acks));
setText(col_ul_nacks_, QString::number(stats_.UL_nacks));
setText(col_ul_missing_, QString::number(stats_.UL_missing));
// Downlink.
setText(col_dl_frames_, QString::number(stats_.DL_frames));
setText(col_dl_bytes_, QString::number(stats_.DL_bytes));
setText(col_dl_mb_s_, QString::number(DL_bw));
setText(col_dl_acks_, QString::number(stats_.DL_acks));
setText(col_dl_nacks_, QString::number(stats_.DL_nacks));
setText(col_dl_missing_, QString::number(stats_.DL_missing));
}
bool operator< (const QTreeWidgetItem &other) const
{
if (other.type() != rlc_channel_row_type_) return QTreeWidgetItem::operator< (other);
const RlcChannelTreeWidgetItem *other_row = static_cast<const RlcChannelTreeWidgetItem *>(&other);
// Switch by selected column.
switch (treeWidget()->sortColumn()) {
case col_ueid_:
// This is channel name. Rank CCCH before SRB before DRB, then channel ID.
return channelRank() < other_row->channelRank();
case col_mode_:
return mode_ < other_row->mode_;
case col_priority_:
return priority_ < other_row->priority_;
default:
break;
}
return QTreeWidgetItem::operator< (other);
}
// Filter expression for channel.
const QString filterExpression(bool showSR, bool showRACH) {
// Create an expression to match with all traffic for this UE.
QString filter_expr;
// Are we taking RLC PDUs from MAC, or not?
if (!recent.gui_rlc_use_pdus_from_mac) {
filter_expr += QString("not mac-lte and ");
}
else {
filter_expr += QString("mac-lte and ");
}
if (showSR) {
filter_expr += QString("(mac-lte.sr-req and mac-lte.ueid == %1) or (").arg(ueid_);
}
if (showRACH) {
filter_expr += QString("(mac-lte.rar or (mac-lte.preamble-sent and mac-lte.ueid == %1)) or (").arg(ueid_);
}
// Main part of expression.
filter_expr += QString("rlc-lte.ueid==%1 and rlc-lte.channel-type == %2").
arg(ueid_).arg(channelType_);
if ((channelType_ == CHANNEL_TYPE_SRB) || (channelType_ == CHANNEL_TYPE_DRB)) {
filter_expr += QString(" and rlc-lte.channel-id == %1").arg(channelId_);
}
// Close () if open because of SR
if (showSR) {
filter_expr += QString(")");
}
// Close () if open because of RACH
if (showRACH) {
filter_expr += QString(")");
}
return filter_expr;
}
// Accessors (queried for launching graph)
unsigned get_ueid() const { return ueid_; }
unsigned get_channelType() const { return channelType_; }
unsigned get_channelId() const { return channelId_; }
unsigned get_mode() const { return mode_; }
bool hasULData() const { return stats_.UL_has_data != 0; }
bool hasDLData() const { return stats_.DL_has_data != 0; }
QList<QVariant> rowData() const
{
// Don't output anything for channel entries when exporting to text.
return QList<QVariant>();
}
private:
unsigned ueid_;
unsigned channelType_;
unsigned channelId_;
unsigned mode_;
unsigned priority_;
unsigned channelRank() const
{
switch (channelType_) {
case CHANNEL_TYPE_CCCH:
return 0;
case CHANNEL_TYPE_SRB:
return channelId_;
case CHANNEL_TYPE_DRB:
return 3 + channelId_;
default:
// Shouldn't really get here..
return 0;
}
}
rlc_channel_stats stats_;
};
// Stats for one UE. TODO: private to class?
typedef struct rlc_ue_stats {
guint32 UL_frames;
guint32 UL_total_bytes;
nstime_t UL_time_start;
nstime_t UL_time_stop;
guint32 UL_total_acks;
guint32 UL_total_nacks;
guint32 UL_total_missing;
guint32 DL_frames;
guint32 DL_total_bytes;
nstime_t DL_time_start;
nstime_t DL_time_stop;
guint32 DL_total_acks;
guint32 DL_total_nacks;
guint32 DL_total_missing;
} rlc_ue_stats;
//-------------------------------------------------------------------
// UE item.
//-------------------------------------------------------------------
class RlcUeTreeWidgetItem : public QTreeWidgetItem
{
public:
RlcUeTreeWidgetItem(QTreeWidget *parent, const rlc_lte_tap_info *rlt_info) :
QTreeWidgetItem (parent, rlc_ue_row_type_),
ueid_(0)
{
ueid_ = rlt_info->ueid;
setText(col_ueid_, QString::number(ueid_));
// We create RlcChannelTreeWidgetItems when first data on new channel is seen.
// Of course, there will be a channel associated with the PDU
// that causes this UE item to be created...
memset(&stats_, 0, sizeof(stats_));
CCCH_stats_ = NULL;
for (int srb=0; srb < 2; srb++) {
srb_stats_[srb] = NULL;
}
for (int drb=0; drb < 32; drb++) {
drb_stats_[drb] = NULL;
}
}
// Does UE match?
bool isMatch(const rlc_lte_tap_info *rlt_info) {
return ueid_ == rlt_info->ueid;
}
// Update UE/channels from tap info.
void update(const rlc_lte_tap_info *tap_info) {
// Are we ignoring RLC frames that were found in MAC frames, or only those
// that were logged separately?
if ((!recent.gui_rlc_use_pdus_from_mac && tap_info->loggedInMACFrame) ||
(recent.gui_rlc_use_pdus_from_mac && !tap_info->loggedInMACFrame)) {
return;
}
// TODO: update title with number of UEs and frames like MAC does?
// N.B. not really expecting to see common stats - ignoring them.
switch (tap_info->channelType) {
case CHANNEL_TYPE_BCCH_BCH:
case CHANNEL_TYPE_BCCH_DL_SCH:
case CHANNEL_TYPE_PCCH:
return;
default:
// Drop through for UE-specific.
break;
}
// UE-level traffic stats.
if (tap_info->direction == DIRECTION_UPLINK) {
// Update time range.
if (stats_.UL_frames == 0) {
stats_.UL_time_start = tap_info->rlc_lte_time;
}
stats_.UL_time_stop = tap_info->rlc_lte_time;
stats_.UL_frames++;
stats_.UL_total_bytes += tap_info->pduLength;
// Status PDU counters.
if (tap_info->isControlPDU) {
stats_.UL_total_acks++;
stats_.UL_total_nacks += tap_info->noOfNACKs;
}
stats_.UL_total_missing += tap_info->missingSNs;
}
else {
// Update time range.
if (stats_.DL_frames == 0) {
stats_.DL_time_start = tap_info->rlc_lte_time;
}
stats_.DL_time_stop = tap_info->rlc_lte_time;
stats_.DL_frames++;
stats_.DL_total_bytes += tap_info->pduLength;
// Status PDU counters.
if (tap_info->isControlPDU) {
stats_.DL_total_acks++;
stats_.DL_total_nacks += tap_info->noOfNACKs;
}
stats_.DL_total_missing += tap_info->missingSNs;
}
RlcChannelTreeWidgetItem *channel_item;
// Find or create tree item for this channel.
switch (tap_info->channelType) {
case CHANNEL_TYPE_CCCH:
channel_item = CCCH_stats_;
if (channel_item == NULL) {
channel_item = CCCH_stats_ =
new RlcChannelTreeWidgetItem(this, tap_info->ueid, RLC_TM_MODE,
tap_info->channelType, tap_info->channelId);
}
break;
case CHANNEL_TYPE_SRB:
channel_item = srb_stats_[tap_info->channelId-1];
if (channel_item == NULL) {
channel_item = srb_stats_[tap_info->channelId-1] =
new RlcChannelTreeWidgetItem(this, tap_info->ueid, RLC_AM_MODE,
tap_info->channelType, tap_info->channelId);
}
break;
case CHANNEL_TYPE_DRB:
channel_item = drb_stats_[tap_info->channelId-1];
if (channel_item == NULL) {
channel_item = drb_stats_[tap_info->channelId-1] =
new RlcChannelTreeWidgetItem(this, tap_info->ueid, tap_info->rlcMode,
tap_info->channelType, tap_info->channelId);
}
break;
default:
// Shouldn't get here...
return;
}
// Update channel with tap_info.
channel_item->update(tap_info);
}
// Draw UE entry
void draw() {
// Fixed fields only drawn once from constructor so don't redraw here.
/* Calculate bandwidths. */
double UL_bw = calculate_bw(&stats_.UL_time_start,
&stats_.UL_time_stop,
stats_.UL_total_bytes);
double DL_bw = calculate_bw(&stats_.DL_time_start,
&stats_.DL_time_stop,
stats_.DL_total_bytes);
// Uplink.
setText(col_ul_frames_, QString::number(stats_.UL_frames));
setText(col_ul_bytes_, QString::number(stats_.UL_total_bytes));
setText(col_ul_mb_s_, QString::number(UL_bw));
setText(col_ul_acks_, QString::number(stats_.UL_total_acks));
setText(col_ul_nacks_, QString::number(stats_.UL_total_nacks));
setText(col_ul_missing_, QString::number(stats_.UL_total_missing));
// Downlink.
setText(col_dl_frames_, QString::number(stats_.DL_frames));
setText(col_dl_bytes_, QString::number(stats_.DL_total_bytes));
setText(col_dl_mb_s_, QString::number(DL_bw));
setText(col_dl_acks_, QString::number(stats_.DL_total_acks));
setText(col_dl_nacks_, QString::number(stats_.DL_total_nacks));
setText(col_dl_missing_, QString::number(stats_.DL_total_missing));
// Call draw() for each channel for this UE.
if (CCCH_stats_ != NULL) {
CCCH_stats_->draw();
}
for (int srb=0; srb < 2; srb++) {
if (srb_stats_[srb] != NULL) {
srb_stats_[srb]->draw();
}
}
for (int drb=0; drb < 32; drb++) {
if (drb_stats_[drb] != NULL) {
drb_stats_[drb]->draw();
}
}
}
bool operator< (const QTreeWidgetItem &other) const
{
if (other.type() != rlc_ue_row_type_) return QTreeWidgetItem::operator< (other);
const RlcUeTreeWidgetItem *other_row = static_cast<const RlcUeTreeWidgetItem *>(&other);
switch (treeWidget()->sortColumn()) {
case col_ueid_:
return ueid_ < other_row->ueid_;
default:
break;
}
return QTreeWidgetItem::operator< (other);
}
// Filter expression for UE.
const QString filterExpression(bool showSR, bool showRACH) {
// Create an expression to match with all traffic for this UE.
QString filter_expr;
// Are we taking RLC PDUs from MAC, or not?
if (!recent.gui_rlc_use_pdus_from_mac) {
filter_expr += QString("not mac-lte and ");
}
else {
filter_expr += QString("mac-lte and ");
}
if (showSR) {
filter_expr += QString("(mac-lte.sr-req and mac-lte.ueid == %1) or (").arg(ueid_);
}
if (showRACH) {
filter_expr += QString("(mac-lte.rar or (mac-lte.preamble-sent and mac-lte.ueid == %1)) or (").arg(ueid_);
}
filter_expr += QString("rlc-lte.ueid==%1").arg(ueid_);
// Close () if open because of SR
if (showSR) {
filter_expr += QString(")");
}
// Close () if open because of RACH
if (showRACH) {
filter_expr += QString(")");
}
return filter_expr;
}
QList<QVariant> rowData() const
{
QList<QVariant> row_data;
// Key fields.
// After the UEId field, there are 2 unused columns for UE entries.
row_data << ueid_ << QString("") << QString("");
// UL
row_data << stats_.UL_frames << stats_.UL_total_bytes
<< calculate_bw(&stats_.UL_time_start,
&stats_.UL_time_stop,
stats_.UL_total_bytes)
<< stats_.UL_total_acks << stats_.UL_total_nacks << stats_.UL_total_missing;
// DL
row_data << stats_.DL_frames << stats_.DL_total_bytes
<< calculate_bw(&stats_.DL_time_start,
&stats_.DL_time_stop,
stats_.DL_total_bytes)
<< stats_.DL_total_acks << stats_.DL_total_nacks << stats_.DL_total_missing;
return row_data;
}
private:
unsigned ueid_;
rlc_ue_stats stats_;
// Channel counters stored in channel sub-items.
RlcChannelTreeWidgetItem* CCCH_stats_;
RlcChannelTreeWidgetItem* srb_stats_[2];
RlcChannelTreeWidgetItem* drb_stats_[32];
};
// Only the first 3 columns headings differ between UE and channel rows.
static const QString ue_col_0_title_ = QObject::tr("UE Id");
static const QString ue_col_1_title_ = QObject::tr("");
static const QString ue_col_2_title_ = QObject::tr("");
static const QString channel_col_0_title_ = QObject::tr("Name");
static const QString channel_col_1_title_ = QObject::tr("Mode");
static const QString channel_col_2_title_ = QObject::tr("Priority");
//------------------------------------------------------------------------------------------
// Dialog
// Constructor.
LteRlcStatisticsDialog::LteRlcStatisticsDialog(QWidget &parent, CaptureFile &cf, const char *filter) :
TapParameterDialog(parent, cf, HELP_STATS_LTE_MAC_TRAFFIC_DIALOG),
cf_(cf),
packet_count_(0)
{
setWindowSubtitle(tr("LTE RLC Statistics"));
loadGeometry((parent.width() * 5) / 5, (parent.height() * 3) / 4, "LTERLCStatisticsDialog");
// Create a grid for filtering-related widgetsto also appear in layout.
int filter_controls_layout_idx = verticalLayout()->indexOf(filterLayout()->widget());
QGridLayout *filter_controls_grid = new QGridLayout();
// Insert into the vertical layout
verticalLayout()->insertLayout(filter_controls_layout_idx, filter_controls_grid);
int one_em = fontMetrics().height();
filter_controls_grid->setColumnMinimumWidth(2, one_em * 2);
filter_controls_grid->setColumnStretch(2, 1);
filter_controls_grid->setColumnMinimumWidth(5, one_em * 2);
filter_controls_grid->setColumnStretch(5, 1);
// Add individual controls into the grid
launchULGraph_ = new QPushButton(QString("Launch UL Graph"));
launchULGraph_->setEnabled(false);
filter_controls_grid->addWidget(launchULGraph_);
connect(launchULGraph_, SIGNAL(clicked()), this, SLOT(launchULGraphButtonClicked()));
launchDLGraph_ = new QPushButton(QString("Launch DL Graph"));
launchDLGraph_->setEnabled(false);
filter_controls_grid->addWidget(launchDLGraph_);
connect(launchDLGraph_, SIGNAL(clicked()), this, SLOT(launchDLGraphButtonClicked()));
showSRFilterCheckBox_ = new QCheckBox(tr("Include SR frames in filter"));
filter_controls_grid->addWidget(showSRFilterCheckBox_);
showRACHFilterCheckBox_ = new QCheckBox(tr("Include RACH frames in filter"));
filter_controls_grid->addWidget(showRACHFilterCheckBox_);
useRLCFramesFromMacCheckBox_ = new QCheckBox(tr("Use RLC frames only from MAC frames"));
useRLCFramesFromMacCheckBox_->setCheckState(recent.gui_rlc_use_pdus_from_mac ?
Qt::Checked :
Qt::Unchecked);
connect(useRLCFramesFromMacCheckBox_, SIGNAL(clicked(bool)), this,
SLOT(useRLCFramesFromMacCheckBoxToggled(bool)));
filter_controls_grid->addWidget(useRLCFramesFromMacCheckBox_);
QStringList header_labels = QStringList()
<< "" << "" << ""
<< tr("UL Frames") << tr("UL Bytes") << tr("UL MB/s")
<< tr("UL ACKs") << tr("UL NACKs") << tr("UL Missing")
<< tr("DL Frames") << tr("DL Bytes") << tr("DL MB/s")
<< tr("DL ACKs") << tr("DL NACKs") << tr("DL Missing");
statsTreeWidget()->setHeaderLabels(header_labels);
updateHeaderLabels();
statsTreeWidget()->sortByColumn(col_ueid_, Qt::AscendingOrder);
// resizeColumnToContents doesn't work well here, so set sizes manually.
for (int col = 0; col < statsTreeWidget()->columnCount() - 1; col++) {
switch (col) {
case col_ueid_:
statsTreeWidget()->setColumnWidth(col, one_em * 7);
break;
case col_ul_frames_:
case col_dl_frames_:
statsTreeWidget()->setColumnWidth(col, one_em * 5);
break;
case col_ul_acks_:
case col_dl_acks_:
statsTreeWidget()->setColumnWidth(col, one_em * 5);
break;
case col_ul_nacks_:
case col_dl_nacks_:
statsTreeWidget()->setColumnWidth(col, one_em * 6);
break;
case col_ul_missing_:
case col_dl_missing_:
statsTreeWidget()->setColumnWidth(col, one_em * 7);
break;
case col_ul_mb_s_:
case col_dl_mb_s_:
statsTreeWidget()->setColumnWidth(col, one_em * 6);
break;
default:
// The rest are numeric.
statsTreeWidget()->setColumnWidth(col, one_em * 4);
break;
}
}
addFilterActions();
if (filter) {
setDisplayFilter(filter);
}
// Set handler for when the tree item changes to set the appropriate labels.
connect(statsTreeWidget(), SIGNAL(itemSelectionChanged()),
this, SLOT(updateItemSelectionChanged()));
// Set handler for when display filter string is changed.
connect(this, SIGNAL(updateFilter(QString)),
this, SLOT(filterUpdated(QString)));
}
// Destructor.
LteRlcStatisticsDialog::~LteRlcStatisticsDialog()
{
}
void LteRlcStatisticsDialog::tapReset(void *ws_dlg_ptr)
{
LteRlcStatisticsDialog *ws_dlg = static_cast<LteRlcStatisticsDialog *>(ws_dlg_ptr);
if (!ws_dlg) {
return;
}
// Clears/deletes all UEs.
ws_dlg->statsTreeWidget()->clear();
ws_dlg->packet_count_ = 0;
}
// Process the tap info from a dissected RLC PDU.
// Returns TAP_PACKET_REDRAW if a redraw is needed, TAP_PACKET_DONT_REDRAW otherwise.
tap_packet_status LteRlcStatisticsDialog::tapPacket(void *ws_dlg_ptr, struct _packet_info *, epan_dissect *, const void *rlc_lte_tap_info_ptr)
{
// Look up dialog.
LteRlcStatisticsDialog *ws_dlg = static_cast<LteRlcStatisticsDialog *>(ws_dlg_ptr);
const rlc_lte_tap_info *rlt_info = (const rlc_lte_tap_info *) rlc_lte_tap_info_ptr;
if (!ws_dlg || !rlt_info) {
return TAP_PACKET_DONT_REDRAW;
}
ws_dlg->incFrameCount();
// Look for this UE (TODO: avoid linear search if have lots of UEs in capture...)
RlcUeTreeWidgetItem *ue_ti = NULL;
for (int i = 0; i < ws_dlg->statsTreeWidget()->topLevelItemCount(); i++) {
QTreeWidgetItem *ti = ws_dlg->statsTreeWidget()->topLevelItem(i);
if (ti->type() != rlc_ue_row_type_) continue;
RlcUeTreeWidgetItem *cur_ru_ti = static_cast<RlcUeTreeWidgetItem*>(ti);
if (cur_ru_ti->isMatch(rlt_info)) {
ue_ti = cur_ru_ti;
break;
}
}
if (!ue_ti) {
// Existing UE wasn't found so create a new one.
ue_ti = new RlcUeTreeWidgetItem(ws_dlg->statsTreeWidget(), rlt_info);
for (int col = 0; col < ws_dlg->statsTreeWidget()->columnCount(); col++) {
ue_ti->setTextAlignment(col, ws_dlg->statsTreeWidget()->headerItem()->textAlignment(col));
}
}
// Update the UE from the information in the tap structure.
ue_ti->update(rlt_info);
return TAP_PACKET_REDRAW;
}
void LteRlcStatisticsDialog::tapDraw(void *ws_dlg_ptr)
{
// Look up UE.
LteRlcStatisticsDialog *ws_dlg = static_cast<LteRlcStatisticsDialog *>(ws_dlg_ptr);
if (!ws_dlg) {
return;
}
// Draw each UE.
for (int i = 0; i < ws_dlg->statsTreeWidget()->topLevelItemCount(); i++) {
QTreeWidgetItem *ti = ws_dlg->statsTreeWidget()->topLevelItem(i);
if (ti->type() != rlc_ue_row_type_) continue;
RlcUeTreeWidgetItem *ru_ti = static_cast<RlcUeTreeWidgetItem*>(ti);
ru_ti->draw();
}
// Update title
ws_dlg->setWindowSubtitle(QString("LTE RLC Statistics (%1 UEs, %2 frames)").
arg(ws_dlg->statsTreeWidget()->topLevelItemCount()).arg(ws_dlg->getFrameCount()));
}
void LteRlcStatisticsDialog::useRLCFramesFromMacCheckBoxToggled(bool state)
{
// Update state to be stored in recent preferences
recent.gui_rlc_use_pdus_from_mac = state;
// Retap to get updated list of PDUs
fillTree();
}
const QString LteRlcStatisticsDialog::filterExpression()
{
QString filter_expr;
if (statsTreeWidget()->selectedItems().count() > 0) {
QTreeWidgetItem *ti = statsTreeWidget()->selectedItems()[0];
// Generate expression according to what type of item is selected.
if (ti->type() == rlc_ue_row_type_) {
RlcUeTreeWidgetItem *ru_ti = static_cast<RlcUeTreeWidgetItem*>(ti);
filter_expr = ru_ti->filterExpression(showSRFilterCheckBox_->checkState() > Qt::Unchecked,
showRACHFilterCheckBox_->checkState() > Qt::Unchecked);
} else if (ti->type() == rlc_channel_row_type_) {
RlcChannelTreeWidgetItem *rc_ti = static_cast<RlcChannelTreeWidgetItem*>(ti);
filter_expr = rc_ti->filterExpression(showSRFilterCheckBox_->checkState() > Qt::Unchecked,
showRACHFilterCheckBox_->checkState() > Qt::Unchecked);
}
}
return filter_expr;
}
void LteRlcStatisticsDialog::fillTree()
{
if (!registerTapListener("rlc-lte",
this,
displayFilter_.toLatin1().data(),
TL_REQUIRES_NOTHING,
tapReset,
tapPacket,
tapDraw)) {
reject();
return;
}
cap_file_.retapPackets();
tapDraw(this);
removeTapListeners();
}
void LteRlcStatisticsDialog::updateItemSelectionChanged()
{
updateHeaderLabels();
bool enableULGraphButton = false, enableDLGraphButton = false;
if (statsTreeWidget()->selectedItems().count() > 0 && statsTreeWidget()->selectedItems()[0]->type() == rlc_channel_row_type_) {
QTreeWidgetItem *ti = statsTreeWidget()->selectedItems()[0];
RlcChannelTreeWidgetItem *rc_ti = static_cast<RlcChannelTreeWidgetItem*>(ti);
enableULGraphButton = rc_ti->hasULData();
enableDLGraphButton = rc_ti->hasDLData();
}
// Only enabling graph buttons for channel entries.
launchULGraph_->setEnabled(enableULGraphButton);
launchDLGraph_->setEnabled(enableDLGraphButton);
}
void LteRlcStatisticsDialog::updateHeaderLabels()
{
if (statsTreeWidget()->selectedItems().count() > 0 &&
statsTreeWidget()->selectedItems()[0]->type() == rlc_channel_row_type_) {
// UE column headings.
statsTreeWidget()->headerItem()->setText(col_ueid_, channel_col_0_title_);
statsTreeWidget()->headerItem()->setText(col_mode_, channel_col_1_title_);
statsTreeWidget()->headerItem()->setText(col_priority_, channel_col_2_title_);
} else {
// Channel column headings.
statsTreeWidget()->headerItem()->setText(col_ueid_, ue_col_0_title_);
statsTreeWidget()->headerItem()->setText(col_mode_, ue_col_1_title_);
statsTreeWidget()->headerItem()->setText(col_priority_, ue_col_2_title_);
}
}
void LteRlcStatisticsDialog::captureFileClosing()
{
remove_tap_listener(this);
updateWidgets();
WiresharkDialog::captureFileClosing();
}
// Launch a UL graph for the currently-selected channel.
void LteRlcStatisticsDialog::launchULGraphButtonClicked()
{
if (statsTreeWidget()->selectedItems().count() > 0 && statsTreeWidget()->selectedItems()[0]->type() == rlc_channel_row_type_) {
// Get the channel item.
QTreeWidgetItem *ti = statsTreeWidget()->selectedItems()[0];
RlcChannelTreeWidgetItem *rc_ti = static_cast<RlcChannelTreeWidgetItem*>(ti);
emit launchRLCGraph(true,
rc_ti->get_ueid(),
rc_ti->get_mode(),
rc_ti->get_channelType(),
rc_ti->get_channelId(),
DIRECTION_UPLINK);
}
}
// Launch a DL graph for the currently-selected channel.
void LteRlcStatisticsDialog::launchDLGraphButtonClicked()
{
if (statsTreeWidget()->selectedItems().count() > 0 && statsTreeWidget()->selectedItems()[0]->type() == rlc_channel_row_type_) {
// Get the channel item.
QTreeWidgetItem *ti = statsTreeWidget()->selectedItems()[0];
RlcChannelTreeWidgetItem *rc_ti = static_cast<RlcChannelTreeWidgetItem*>(ti);
emit launchRLCGraph(true,
rc_ti->get_ueid(),
rc_ti->get_mode(),
rc_ti->get_channelType(),
rc_ti->get_channelId(),
DIRECTION_DOWNLINK);
}
}
// Store filter from signal.
void LteRlcStatisticsDialog::filterUpdated(QString filter)
{
displayFilter_ = filter;
}
// Get the item for the row, depending upon the type of tree item.
QList<QVariant> LteRlcStatisticsDialog::treeItemData(QTreeWidgetItem *item) const
{
// Cast up to our type.
RlcChannelTreeWidgetItem *channel_item = dynamic_cast<RlcChannelTreeWidgetItem*>(item);
if (channel_item) {
return channel_item->rowData();
}
RlcUeTreeWidgetItem *ue_item = dynamic_cast<RlcUeTreeWidgetItem*>(item);
if (ue_item) {
return ue_item->rowData();
}
// Need to return something..
return QList<QVariant>();
}
// Stat command + args
static void
lte_rlc_statistics_init(const char *args, void*) {
QStringList args_l = QString(args).split(',');
QByteArray filter;
if (args_l.length() > 2) {
filter = QStringList(args_l.mid(2)).join(",").toUtf8();
}
wsApp->emitStatCommandSignal("LteRlcStatistics", filter.constData(), NULL);
}
static stat_tap_ui lte_rlc_statistics_ui = {
REGISTER_STAT_GROUP_TELEPHONY_LTE,
QT_TRANSLATE_NOOP("LteRlcStatisticsDialog", "RLC Statistics"),
"rlc-lte,stat",
lte_rlc_statistics_init,
0,
NULL
};
extern "C" {
void
register_tap_listener_qt_lte_rlc_statistics(void)
{
register_stat_tap_ui(&lte_rlc_statistics_ui, NULL);
}
}
/*
* Editor modelines
*
* Local Variables:
* c-basic-offset: 4
* tab-width: 8
* indent-tabs-mode: nil
* End:
*
* ex: set shiftwidth=4 tabstop=8 expandtab:
* :indentSize=4:tabSize=8:noTabs=true:
*/