forked from osmocom/wireshark
929 lines
32 KiB
C++
929 lines
32 KiB
C++
/* lte_mac_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_mac_statistics_dialog.h"
|
|
|
|
#include <epan/packet.h>
|
|
#include <epan/strutil.h>
|
|
#include <epan/tap.h>
|
|
|
|
#include <epan/dissectors/packet-mac-lte.h>
|
|
|
|
#include <QFormLayout>
|
|
#include <QTreeWidgetItem>
|
|
|
|
#include <ui/qt/models/percent_bar_delegate.h>
|
|
#include "wireshark_application.h"
|
|
|
|
// TODO: have never tested in a live capture.
|
|
|
|
// Whole-UE headings.
|
|
enum {
|
|
col_rnti_,
|
|
col_type_,
|
|
col_ueid_,
|
|
// UL-specific
|
|
col_ul_frames_,
|
|
col_ul_bytes_,
|
|
col_ul_mb_s_,
|
|
col_ul_padding_percent_,
|
|
/* col_ul_crc_failed_, */
|
|
col_ul_retx_,
|
|
// DL-specific
|
|
col_dl_frames_,
|
|
col_dl_bytes_,
|
|
col_dl_mb_s_,
|
|
col_dl_padding_percent_,
|
|
col_dl_crc_failed_,
|
|
col_dl_retx_
|
|
};
|
|
|
|
|
|
// Type of tree item, so can set column headings properly.
|
|
enum {
|
|
mac_whole_ue_row_type_ = 1000,
|
|
mac_ulsch_packet_count_row_type,
|
|
mac_ulsch_byte_count_row_type,
|
|
mac_dlsch_packet_count_row_type,
|
|
mac_dlsch_byte_count_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;
|
|
}
|
|
}
|
|
|
|
|
|
// Channels (by LCID) data node. Used for UL/DL frames/bytes.
|
|
class MacULDLTreeWidgetItem : public QTreeWidgetItem
|
|
{
|
|
public:
|
|
MacULDLTreeWidgetItem(QTreeWidgetItem *parent, unsigned ueid, unsigned rnti, int row_type) :
|
|
QTreeWidgetItem (parent, row_type),
|
|
ueid_(ueid),
|
|
rnti_(rnti)
|
|
{
|
|
// Init values held for all lcids to 0.
|
|
for (int n=0; n < MAC_LTE_DATA_LCID_COUNT_MAX; n++) {
|
|
lcids[n] = 0;
|
|
}
|
|
|
|
// Set first column to show what counts in this row mean.
|
|
switch (row_type) {
|
|
case mac_ulsch_packet_count_row_type:
|
|
setText(col_rnti_, "UL Packets");
|
|
break;
|
|
case mac_ulsch_byte_count_row_type:
|
|
setText(col_rnti_, "UL Bytes");
|
|
break;
|
|
case mac_dlsch_packet_count_row_type:
|
|
setText(col_rnti_, "DL Packets");
|
|
break;
|
|
case mac_dlsch_byte_count_row_type:
|
|
setText(col_rnti_, "DL Bytes");
|
|
break;
|
|
default:
|
|
// Should never get here...
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool operator< (const QTreeWidgetItem &other) const
|
|
{
|
|
// We want rows with a UE to appear in the order they appear in the row_type enum.
|
|
return type() < other.type();
|
|
}
|
|
|
|
void draw()
|
|
{
|
|
// Show current value of counter for each LCID.
|
|
// N.B. fields that are set as % using percent_bar_delegate.h
|
|
// for UE headings don't display here...
|
|
for (int n=0; n < MAC_LTE_DATA_LCID_COUNT_MAX; n++) {
|
|
setText(col_type_+n, QString::number((uint)lcids[n]));
|
|
}
|
|
}
|
|
|
|
// Increase value held for lcid by given value.
|
|
void updateLCID(guint8 lcid, guint value)
|
|
{
|
|
lcids[lcid] += value;
|
|
}
|
|
|
|
// Generate expression for this UE and direction, also filter for SRs and RACH if indicated.
|
|
const QString filterExpression(bool showSR, bool showRACH) {
|
|
int direction = (type() == mac_dlsch_packet_count_row_type) ||
|
|
(type() == mac_dlsch_byte_count_row_type);
|
|
|
|
QString filter_expr;
|
|
|
|
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 expression matching this UE and direction
|
|
filter_expr += QString("mac-lte.ueid==%1 && mac-lte.rnti==%2 && mac-lte.direction==%3").
|
|
arg(ueid_).arg(rnti_).arg(direction);
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Not showing anything for individual channels. Headings are different than from UEs, and
|
|
// trying to show both would be too confusing.
|
|
QList<QVariant> rowData() const
|
|
{
|
|
return QList<QVariant>();
|
|
}
|
|
|
|
private:
|
|
unsigned ueid_;
|
|
unsigned rnti_;
|
|
int lcids[MAC_LTE_DATA_LCID_COUNT_MAX]; /* 0 to 10 and 32 to 38 */
|
|
};
|
|
|
|
|
|
|
|
// Whole UE tree item
|
|
class MacUETreeWidgetItem : public QTreeWidgetItem
|
|
{
|
|
public:
|
|
MacUETreeWidgetItem(QTreeWidget *parent, const mac_lte_tap_info *mlt_info) :
|
|
QTreeWidgetItem (parent, mac_whole_ue_row_type_),
|
|
rnti_(0),
|
|
type_(0),
|
|
ueid_(0),
|
|
ul_frames_(0),
|
|
ul_bytes_(0),
|
|
ul_raw_bytes_(0),
|
|
ul_padding_bytes_(0),
|
|
ul_retx_(0),
|
|
dl_frames_(0),
|
|
dl_bytes_(0),
|
|
dl_raw_bytes_(0),
|
|
dl_padding_bytes_(0),
|
|
dl_crc_failed_(0),
|
|
dl_retx_(0)
|
|
{
|
|
// Set fixed fields.
|
|
rnti_ = mlt_info->rnti;
|
|
type_ = mlt_info->rntiType;
|
|
ueid_ = mlt_info->ueid;
|
|
setText(col_rnti_, QString::number(rnti_));
|
|
setText(col_type_, type_ == C_RNTI ? QObject::tr("C-RNTI") : QObject::tr("SPS-RNTI"));
|
|
setText(col_ueid_, QString::number(ueid_));
|
|
|
|
// Add UL/DL packet/byte count subitems.
|
|
addDetails();
|
|
}
|
|
|
|
// Does this tap-info match this existing UE item?
|
|
bool isMatch(const mac_lte_tap_info *mlt_info) {
|
|
return ((rnti_ == mlt_info->rnti) &&
|
|
(type_ == mlt_info->rntiType) &&
|
|
(ueid_ == mlt_info->ueid));
|
|
}
|
|
|
|
// Update this UE according to the tap info
|
|
void update(const mac_lte_tap_info *mlt_info) {
|
|
|
|
// Uplink.
|
|
if (mlt_info->direction == DIRECTION_UPLINK) {
|
|
if (mlt_info->isPHYRetx) {
|
|
ul_retx_++;
|
|
return;
|
|
}
|
|
|
|
if (mlt_info->crcStatusValid && (mlt_info->crcStatus != crc_success)) {
|
|
// TODO: there is not a column for this...
|
|
//ul_crc_errors_++;
|
|
return;
|
|
}
|
|
|
|
// Update time range
|
|
if (ul_frames_ == 0) {
|
|
ul_time_start_ = mlt_info->mac_lte_time;
|
|
}
|
|
ul_time_stop_ = mlt_info->mac_lte_time;
|
|
|
|
ul_frames_++;
|
|
|
|
// These values needed for padding % calculation.
|
|
ul_raw_bytes_ += mlt_info->raw_length;
|
|
ul_padding_bytes_ += mlt_info->padding_bytes;
|
|
|
|
// N.B. Not going to support predefined data in Qt version..
|
|
if (!mlt_info->isPredefinedData) {
|
|
for (int n=0; n < MAC_LTE_DATA_LCID_COUNT_MAX; n++) {
|
|
// Update UL child items
|
|
ul_frames_item_->updateLCID(n, mlt_info->sdus_for_lcid[n]);
|
|
ul_bytes_item_->updateLCID(n, mlt_info->bytes_for_lcid[n]);
|
|
|
|
ul_bytes_ += mlt_info->bytes_for_lcid[n];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Downlink
|
|
else {
|
|
if (mlt_info->isPHYRetx) {
|
|
dl_retx_++;
|
|
return;
|
|
}
|
|
|
|
if (mlt_info->crcStatusValid && (mlt_info->crcStatus != crc_success)) {
|
|
switch (mlt_info->crcStatus) {
|
|
case crc_fail:
|
|
dl_crc_failed_++;
|
|
break;
|
|
|
|
default:
|
|
// Not a reason we currently care about.
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Update time range
|
|
if (dl_frames_ == 0) {
|
|
dl_time_start_ = mlt_info->mac_lte_time;
|
|
}
|
|
dl_time_stop_ = mlt_info->mac_lte_time;
|
|
|
|
dl_frames_++;
|
|
|
|
// These values needed for padding % calculation.
|
|
dl_raw_bytes_ += mlt_info->raw_length;
|
|
dl_padding_bytes_ += mlt_info->padding_bytes;
|
|
|
|
// N.B. Not going to support predefined data in Qt version..
|
|
if (!mlt_info->isPredefinedData) {
|
|
for (int n=0; n < MAC_LTE_DATA_LCID_COUNT_MAX; n++) {
|
|
// Update DL child items
|
|
dl_frames_item_->updateLCID(n, mlt_info->sdus_for_lcid[n]);
|
|
dl_bytes_item_->updateLCID(n, mlt_info->bytes_for_lcid[n]);
|
|
|
|
dl_bytes_ += mlt_info->bytes_for_lcid[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void addDetails() {
|
|
// Add UL/DL packet and byte counts.
|
|
ul_frames_item_ = new MacULDLTreeWidgetItem(this, ueid_, rnti_, mac_ulsch_packet_count_row_type);
|
|
ul_bytes_item_ = new MacULDLTreeWidgetItem(this, ueid_, rnti_, mac_ulsch_byte_count_row_type);
|
|
dl_frames_item_ = new MacULDLTreeWidgetItem(this, ueid_, rnti_, mac_dlsch_packet_count_row_type);
|
|
dl_bytes_item_ = new MacULDLTreeWidgetItem(this, ueid_, rnti_, mac_dlsch_byte_count_row_type);
|
|
|
|
setExpanded(false);
|
|
}
|
|
|
|
// Draw this UE.
|
|
void draw() {
|
|
// Fixed fields (rnti, type, ueid) won't change during lifetime of UE entry.
|
|
|
|
// Calculate bw now.
|
|
double UL_bw = calculate_bw(&ul_time_start_,
|
|
&ul_time_stop_,
|
|
ul_bytes_);
|
|
double DL_bw = calculate_bw(&dl_time_start_,
|
|
&dl_time_stop_,
|
|
dl_bytes_);
|
|
|
|
// Set columns with current values.
|
|
setText(col_ul_frames_, QString::number(ul_frames_));
|
|
setText(col_ul_bytes_, QString::number(ul_bytes_));
|
|
setText(col_ul_mb_s_, QString::number(UL_bw));
|
|
setData(col_ul_padding_percent_, Qt::UserRole,
|
|
QVariant::fromValue<double>(ul_raw_bytes_ ?
|
|
(((double)ul_padding_bytes_ / (double)ul_raw_bytes_) * 100.0) :
|
|
0.0));
|
|
setText(col_ul_retx_, QString::number(ul_retx_));
|
|
|
|
setText(col_dl_frames_, QString::number(dl_frames_));
|
|
setText(col_dl_bytes_, QString::number(dl_bytes_));
|
|
setText(col_dl_mb_s_, QString::number(DL_bw));
|
|
|
|
setData(col_dl_padding_percent_, Qt::UserRole,
|
|
QVariant::fromValue<double>(dl_raw_bytes_ ?
|
|
(((double)dl_padding_bytes_ / (double)dl_raw_bytes_) * 100.0) :
|
|
0.0));
|
|
setText(col_dl_crc_failed_, QString::number(dl_crc_failed_));
|
|
setText(col_dl_retx_, QString::number(dl_retx_));
|
|
|
|
// Draw child items with per-channel counts.
|
|
ul_frames_item_->draw();
|
|
ul_bytes_item_->draw();
|
|
dl_frames_item_->draw();
|
|
dl_bytes_item_->draw();
|
|
}
|
|
|
|
// < operator. Compare this item with another item, using the column we are currently sorting on.
|
|
bool operator< (const QTreeWidgetItem &other) const
|
|
{
|
|
if (other.type() != mac_whole_ue_row_type_) return QTreeWidgetItem::operator< (other);
|
|
const MacUETreeWidgetItem *other_row = static_cast<const MacUETreeWidgetItem *>(&other);
|
|
|
|
switch (treeWidget()->sortColumn()) {
|
|
case col_rnti_:
|
|
return rnti_ < other_row->rnti_;
|
|
case col_type_:
|
|
return type_ < other_row->type_;
|
|
case col_ueid_:
|
|
return ueid_ < other_row->ueid_;
|
|
// TODO: other fields?
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return QTreeWidgetItem::operator< (other);
|
|
}
|
|
|
|
// Generate expression for this UE, also filter for SRs and RACH if indicated.
|
|
const QString filterExpression(bool showSR, bool showRACH) {
|
|
QString filter_expr;
|
|
|
|
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 expression matching this UE
|
|
filter_expr += QString("mac-lte.ueid==%1 && mac-lte.rnti==%2").arg(ueid_).arg(rnti_);
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Return the UE-specific fields.
|
|
QList<QVariant> rowData() const
|
|
{
|
|
QList<QVariant> row_data;
|
|
|
|
// Key fields
|
|
row_data << rnti_ << (type_ == C_RNTI ? QObject::tr("C-RNTI") : QObject::tr("SPS-RNTI")) << ueid_;
|
|
|
|
// UL
|
|
row_data << ul_frames_ << ul_bytes_
|
|
<< calculate_bw(&ul_time_start_, &ul_time_stop_, ul_bytes_)
|
|
<< QVariant::fromValue<double>(ul_raw_bytes_ ?
|
|
(((double)ul_padding_bytes_ / (double)ul_raw_bytes_) * 100.0) :
|
|
0.0)
|
|
<< ul_retx_;
|
|
|
|
// DL
|
|
row_data << dl_frames_ << dl_bytes_
|
|
<< calculate_bw(&dl_time_start_, &dl_time_stop_, dl_bytes_)
|
|
<< QVariant::fromValue<double>(dl_raw_bytes_ ?
|
|
(((double)dl_padding_bytes_ / (double)dl_raw_bytes_) * 100.0) :
|
|
0.0)
|
|
<< dl_crc_failed_ << dl_retx_;
|
|
return row_data;
|
|
}
|
|
|
|
private:
|
|
// Unchanging (key) fields.
|
|
unsigned rnti_;
|
|
unsigned type_;
|
|
unsigned ueid_;
|
|
|
|
// UL-specific.
|
|
unsigned ul_frames_;
|
|
unsigned ul_bytes_;
|
|
unsigned ul_raw_bytes_;
|
|
unsigned ul_padding_bytes_;
|
|
nstime_t ul_time_start_;
|
|
nstime_t ul_time_stop_;
|
|
unsigned ul_retx_;
|
|
|
|
// DL-specific.
|
|
unsigned dl_frames_;
|
|
unsigned dl_bytes_;
|
|
unsigned dl_raw_bytes_;
|
|
unsigned dl_padding_bytes_;
|
|
nstime_t dl_time_start_;
|
|
nstime_t dl_time_stop_;
|
|
unsigned dl_crc_failed_;
|
|
unsigned dl_retx_;
|
|
|
|
// Child nodes storing per-lcid counts.
|
|
MacULDLTreeWidgetItem *ul_frames_item_;
|
|
MacULDLTreeWidgetItem *ul_bytes_item_;
|
|
MacULDLTreeWidgetItem *dl_frames_item_;
|
|
MacULDLTreeWidgetItem *dl_bytes_item_;
|
|
};
|
|
|
|
|
|
|
|
|
|
// Label headings. Show according to which type of tree item is currently selected.
|
|
static const QStringList mac_whole_ue_row_labels = QStringList()
|
|
<< QObject::tr("RNTI") << QObject::tr("Type") << QObject::tr("UEId")
|
|
<< QObject::tr("UL Frames") << QObject::tr("UL Bytes") << QObject::tr("UL MB/s")
|
|
<< QObject::tr("UL Padding %") << QObject::tr("UL Re TX")
|
|
<< QObject::tr("DL Frames") << QObject::tr("DL Bytes") << QObject::tr("DL MB/s")
|
|
<< QObject::tr("DL Padding %") << QObject::tr("DL CRC Failed")
|
|
<< QObject::tr("DL ReTX")
|
|
// 'Blank out' Channel-level fields
|
|
<< QObject::tr("") << QObject::tr("") << QObject::tr("") << QObject::tr("") << QObject::tr("");
|
|
|
|
static const QStringList mac_channel_counts_labels = QStringList()
|
|
<< QObject::tr("") << QObject::tr("CCCH")
|
|
<< QObject::tr("LCID 1") << QObject::tr("LCID 2") << QObject::tr("LCID 3")
|
|
<< QObject::tr("LCID 4") << QObject::tr("LCID 5") << QObject::tr("LCID 6")
|
|
<< QObject::tr("LCID 7") << QObject::tr("LCID 8") << QObject::tr("LCID 9")
|
|
<< QObject::tr("LCID 10") << QObject::tr("LCID 32") << QObject::tr("LCID 33")
|
|
<< QObject::tr("LCID 34") << QObject::tr("LCID 35") << QObject::tr("LCID 36")
|
|
<< QObject::tr("LCID 37") << QObject::tr("LCID 38");
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
|
// Dialog
|
|
|
|
// Constructor.
|
|
LteMacStatisticsDialog::LteMacStatisticsDialog(QWidget &parent, CaptureFile &cf, const char *filter) :
|
|
TapParameterDialog(parent, cf, HELP_STATS_LTE_MAC_TRAFFIC_DIALOG),
|
|
commonStatsCurrent_(false)
|
|
{
|
|
setWindowSubtitle(tr("LTE Mac Statistics"));
|
|
loadGeometry(parent.width() * 1, parent.height() * 3 / 4, "LTEMacStatisticsDialog");
|
|
|
|
clearCommonStats();
|
|
|
|
// Create common_stats_grid to appear just above the filter area.
|
|
int statstree_layout_idx = verticalLayout()->indexOf(filterLayout()->widget());
|
|
QGridLayout *common_stats_grid = new QGridLayout();
|
|
// Insert into the vertical layout
|
|
verticalLayout()->insertLayout(statstree_layout_idx, common_stats_grid);
|
|
int one_em = fontMetrics().height();
|
|
common_stats_grid->setColumnMinimumWidth(2, one_em * 2);
|
|
common_stats_grid->setColumnStretch(2, 1);
|
|
common_stats_grid->setColumnMinimumWidth(5, one_em * 2);
|
|
common_stats_grid->setColumnStretch(5, 1);
|
|
|
|
// Create statistics label.
|
|
commonStatsLabel_ = new QLabel(this);
|
|
commonStatsLabel_->setObjectName("statisticsLabel");
|
|
commonStatsLabel_->setTextFormat(Qt::RichText);
|
|
commonStatsLabel_->setTextInteractionFlags(Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse);
|
|
common_stats_grid->addWidget(commonStatsLabel_);
|
|
|
|
|
|
// 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);
|
|
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
|
|
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_);
|
|
|
|
// Will set whole-UE headings originally.
|
|
updateHeaderLabels();
|
|
|
|
ul_delegate_ = new PercentBarDelegate();
|
|
statsTreeWidget()->setItemDelegateForColumn(col_ul_padding_percent_, ul_delegate_);
|
|
dl_delegate_ = new PercentBarDelegate();
|
|
statsTreeWidget()->setItemDelegateForColumn(col_dl_padding_percent_, dl_delegate_);
|
|
|
|
statsTreeWidget()->sortByColumn(col_rnti_, Qt::AscendingOrder);
|
|
|
|
// Set up column widths.
|
|
// resizeColumnToContents doesn't work well here, so set sizes manually.
|
|
for (int col = 0; col < statsTreeWidget()->columnCount() - 1; col++) {
|
|
switch (col) {
|
|
case col_rnti_:
|
|
statsTreeWidget()->setColumnWidth(col, one_em * 8);
|
|
break;
|
|
case col_ul_frames_:
|
|
statsTreeWidget()->setColumnWidth(col, one_em * 5);
|
|
break;
|
|
case col_ul_bytes_:
|
|
statsTreeWidget()->setColumnWidth(col, one_em * 5);
|
|
break;
|
|
case col_ul_mb_s_:
|
|
statsTreeWidget()->setColumnWidth(col, one_em * 4);
|
|
break;
|
|
case col_ul_padding_percent_:
|
|
statsTreeWidget()->setColumnWidth(col, one_em * 6);
|
|
break;
|
|
case col_ul_retx_:
|
|
statsTreeWidget()->setColumnWidth(col, one_em * 6);
|
|
break;
|
|
case col_dl_frames_:
|
|
statsTreeWidget()->setColumnWidth(col, one_em * 5);
|
|
break;
|
|
case col_dl_bytes_:
|
|
statsTreeWidget()->setColumnWidth(col, one_em * 5);
|
|
break;
|
|
case col_dl_mb_s_:
|
|
statsTreeWidget()->setColumnWidth(col, one_em * 4);
|
|
break;
|
|
case col_dl_padding_percent_:
|
|
statsTreeWidget()->setColumnWidth(col, one_em * 6);
|
|
break;
|
|
case col_dl_crc_failed_:
|
|
statsTreeWidget()->setColumnWidth(col, one_em * 6);
|
|
break;
|
|
case col_dl_retx_:
|
|
statsTreeWidget()->setColumnWidth(col, one_em * 6);
|
|
break;
|
|
|
|
default:
|
|
// The rest are numeric
|
|
statsTreeWidget()->setColumnWidth(col, one_em * 4);
|
|
statsTreeWidget()->headerItem()->setTextAlignment(col, Qt::AlignRight);
|
|
break;
|
|
}
|
|
}
|
|
|
|
addFilterActions();
|
|
|
|
if (filter) {
|
|
setDisplayFilter(filter);
|
|
}
|
|
|
|
// Set handler for when the tree item changes to set the appropriate labels.
|
|
connect(statsTreeWidget(), &QTreeWidget::itemSelectionChanged,
|
|
this, &LteMacStatisticsDialog::updateHeaderLabels);
|
|
|
|
// Set handler for when display filter string is changed.
|
|
connect(this, &LteMacStatisticsDialog::updateFilter,
|
|
this, &LteMacStatisticsDialog::filterUpdated);
|
|
}
|
|
|
|
// Destructor.
|
|
LteMacStatisticsDialog::~LteMacStatisticsDialog()
|
|
{
|
|
delete ul_delegate_;
|
|
delete dl_delegate_;
|
|
}
|
|
|
|
// Update system/common counters, and redraw if changed.
|
|
void LteMacStatisticsDialog::updateCommonStats(const mac_lte_tap_info *tap_info)
|
|
{
|
|
commonStats_.all_frames++;
|
|
|
|
// For common channels, just update global counters
|
|
switch (tap_info->rntiType) {
|
|
case P_RNTI:
|
|
commonStats_.pch_frames++;
|
|
commonStats_.pch_bytes += tap_info->single_number_of_bytes;
|
|
commonStats_.pch_paging_ids += tap_info->number_of_paging_ids;
|
|
commonStatsCurrent_ = false;
|
|
break;
|
|
case SI_RNTI:
|
|
commonStats_.sib_frames++;
|
|
commonStats_.sib_bytes += tap_info->single_number_of_bytes;
|
|
commonStatsCurrent_ = false;
|
|
break;
|
|
case NO_RNTI:
|
|
commonStats_.mib_frames++;
|
|
commonStatsCurrent_ = false;
|
|
break;
|
|
case RA_RNTI:
|
|
commonStats_.rar_frames++;
|
|
commonStats_.rar_entries += tap_info->number_of_rars;
|
|
commonStatsCurrent_ = false;
|
|
break;
|
|
case C_RNTI:
|
|
case SPS_RNTI:
|
|
// UE-specific.
|
|
break;
|
|
|
|
default:
|
|
// Error...
|
|
return;
|
|
}
|
|
|
|
// Check max UEs/tti counter
|
|
switch (tap_info->direction) {
|
|
case DIRECTION_UPLINK:
|
|
if (tap_info->ueInTTI > commonStats_.max_ul_ues_in_tti) {
|
|
commonStats_.max_ul_ues_in_tti = tap_info->ueInTTI;
|
|
commonStatsCurrent_ = false;
|
|
}
|
|
break;
|
|
case DIRECTION_DOWNLINK:
|
|
if (tap_info->ueInTTI > commonStats_.max_dl_ues_in_tti) {
|
|
commonStats_.max_dl_ues_in_tti = tap_info->ueInTTI;
|
|
commonStatsCurrent_ = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Draw current common statistics by regenerating label with current values.
|
|
void LteMacStatisticsDialog::drawCommonStats()
|
|
{
|
|
if (!commonStatsCurrent_) {
|
|
QString stats_tables = "<html><head></head><body>\n";
|
|
stats_tables += QString("<table>\n");
|
|
stats_tables += QString("<tr><th align=\"left\">System</th> <td align=\"left\"> Max UL UEs/TTI=%1</td>").arg(commonStats_.max_ul_ues_in_tti);
|
|
stats_tables += QString("<td align=\"left\">Max DL UEs/TTI=%1</td></tr>\n").arg(commonStats_.max_dl_ues_in_tti);
|
|
|
|
stats_tables += QString("<tr><th align=\"left\">System broadcast</th><td align=\"left\">MIBs=%1</td>").arg(commonStats_.mib_frames);
|
|
stats_tables += QString("<td align=\"left\">SIBs=%1 (%2 bytes)</td></tr>\n").arg(commonStats_.sib_frames).arg(commonStats_.sib_bytes);
|
|
|
|
stats_tables += QString("<tr><th align=\"left\">RACH</th><td align=\"left\">RARs=%1 frames (%2 RARs)</td></tr>\n").
|
|
arg(commonStats_.rar_frames).
|
|
arg(commonStats_.rar_entries);
|
|
|
|
stats_tables += QString("<tr><th align=\"left\">Paging</th><td align=\"left\">PCH=%1 (%2 bytes, %3 IDs)</td></tr>\n").
|
|
arg(commonStats_.pch_frames).
|
|
arg(commonStats_.pch_bytes).
|
|
arg(commonStats_.pch_paging_ids);
|
|
|
|
stats_tables += QString("</table>\n");
|
|
stats_tables += "</body>\n";
|
|
|
|
commonStatsLabel_->setText(stats_tables);
|
|
|
|
commonStatsCurrent_ = true;
|
|
}
|
|
}
|
|
|
|
void LteMacStatisticsDialog::clearCommonStats()
|
|
{
|
|
memset(&commonStats_, 0, sizeof(commonStats_));
|
|
}
|
|
|
|
void LteMacStatisticsDialog::tapReset(void *ws_dlg_ptr)
|
|
{
|
|
LteMacStatisticsDialog *ws_dlg = static_cast<LteMacStatisticsDialog *>(ws_dlg_ptr);
|
|
if (!ws_dlg) {
|
|
return;
|
|
}
|
|
|
|
ws_dlg->statsTreeWidget()->clear();
|
|
ws_dlg->clearCommonStats();
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------
|
|
// Process tap info from a new packet.
|
|
// Returns TAP_PACKET_REDRAW if a redraw is needed, TAP_PACKET_DONT_REDRAW otherwise.
|
|
tap_packet_status LteMacStatisticsDialog::tapPacket(void *ws_dlg_ptr, struct _packet_info *, epan_dissect *, const void *mac_lte_tap_info_ptr)
|
|
{
|
|
// Look up dialog and tap info.
|
|
LteMacStatisticsDialog *ws_dlg = static_cast<LteMacStatisticsDialog *>(ws_dlg_ptr);
|
|
const mac_lte_tap_info *mlt_info = (const mac_lte_tap_info *) mac_lte_tap_info_ptr;
|
|
if (!ws_dlg || !mlt_info) {
|
|
return TAP_PACKET_DONT_REDRAW;
|
|
}
|
|
|
|
// Update common stats.
|
|
ws_dlg->updateCommonStats(mlt_info);
|
|
|
|
// Nothing more to do if tap entry isn't for a UE.
|
|
if ((mlt_info->rntiType != C_RNTI) && (mlt_info->rntiType != SPS_RNTI)) {
|
|
return TAP_PACKET_DONT_REDRAW;
|
|
}
|
|
|
|
// Look for an existing UE to match this tap info.
|
|
MacUETreeWidgetItem *mac_ue_ti = NULL;
|
|
for (int i = 0; i < ws_dlg->statsTreeWidget()->topLevelItemCount(); i++) {
|
|
QTreeWidgetItem *ti = ws_dlg->statsTreeWidget()->topLevelItem(i);
|
|
// Make sure we're looking at a UE entry
|
|
if (ti->type() != mac_whole_ue_row_type_) {
|
|
continue;
|
|
}
|
|
|
|
// See if current item matches tap.
|
|
MacUETreeWidgetItem *cur_muds_ti = static_cast<MacUETreeWidgetItem*>(ti);
|
|
if (cur_muds_ti->isMatch(mlt_info)) {
|
|
mac_ue_ti = cur_muds_ti;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If don't find matching UE, create a new one.
|
|
if (!mac_ue_ti) {
|
|
mac_ue_ti = new MacUETreeWidgetItem(ws_dlg->statsTreeWidget(), mlt_info);
|
|
for (int col = 0; col < ws_dlg->statsTreeWidget()->columnCount(); col++) {
|
|
mac_ue_ti->setTextAlignment(col, ws_dlg->statsTreeWidget()->headerItem()->textAlignment(col));
|
|
}
|
|
}
|
|
|
|
// Update the UE item with info from tap!
|
|
mac_ue_ti->update(mlt_info);
|
|
return TAP_PACKET_REDRAW;
|
|
}
|
|
|
|
// Return total number of frames tapped.
|
|
unsigned LteMacStatisticsDialog::getFrameCount()
|
|
{
|
|
return commonStats_.all_frames;
|
|
}
|
|
|
|
void LteMacStatisticsDialog::tapDraw(void *ws_dlg_ptr)
|
|
{
|
|
// Look up dialog.
|
|
LteMacStatisticsDialog *ws_dlg = static_cast<LteMacStatisticsDialog *>(ws_dlg_ptr);
|
|
if (!ws_dlg) {
|
|
return;
|
|
}
|
|
|
|
// Go over all of the top-level items.
|
|
for (int i = 0; i < ws_dlg->statsTreeWidget()->topLevelItemCount(); i++) {
|
|
// Get item, make sure its of the whole-UE type.
|
|
QTreeWidgetItem *ti = ws_dlg->statsTreeWidget()->topLevelItem(i);
|
|
if (ti->type() != mac_whole_ue_row_type_) {
|
|
continue;
|
|
}
|
|
|
|
// Tell the UE item to draw itself.
|
|
MacUETreeWidgetItem *mac_ue_ti = static_cast<MacUETreeWidgetItem*>(ti);
|
|
mac_ue_ti->draw();
|
|
}
|
|
|
|
ws_dlg->drawCommonStats();
|
|
|
|
// Update title
|
|
ws_dlg->setWindowSubtitle(QString("LTE Mac Statistics (%1 UEs, %2 frames)").
|
|
arg(ws_dlg->statsTreeWidget()->topLevelItemCount()).arg(ws_dlg->getFrameCount()));
|
|
}
|
|
|
|
const QString LteMacStatisticsDialog::filterExpression()
|
|
{
|
|
QString filter_expr;
|
|
|
|
if (statsTreeWidget()->selectedItems().count() > 0) {
|
|
QTreeWidgetItem *ti = statsTreeWidget()->selectedItems()[0];
|
|
|
|
if (ti->type() == mac_whole_ue_row_type_) {
|
|
MacUETreeWidgetItem *mac_ue_ti = static_cast<MacUETreeWidgetItem*>(ti);
|
|
filter_expr = mac_ue_ti->filterExpression(showSRFilterCheckBox_->checkState() > Qt::Unchecked,
|
|
showRACHFilterCheckBox_->checkState() > Qt::Unchecked);
|
|
} else {
|
|
MacULDLTreeWidgetItem *mac_channels_ti = static_cast<MacULDLTreeWidgetItem*>(ti);
|
|
filter_expr = mac_channels_ti->filterExpression(showSRFilterCheckBox_->checkState() > Qt::Unchecked,
|
|
showRACHFilterCheckBox_->checkState() > Qt::Unchecked);
|
|
}
|
|
}
|
|
return filter_expr;
|
|
}
|
|
|
|
void LteMacStatisticsDialog::fillTree()
|
|
{
|
|
if (!registerTapListener("mac-lte",
|
|
this,
|
|
displayFilter_.toLatin1().data(),
|
|
TL_REQUIRES_NOTHING,
|
|
tapReset,
|
|
tapPacket,
|
|
tapDraw)) {
|
|
reject();
|
|
return;
|
|
}
|
|
|
|
cap_file_.retapPackets();
|
|
tapDraw(this);
|
|
removeTapListeners();
|
|
}
|
|
|
|
void LteMacStatisticsDialog::updateHeaderLabels()
|
|
{
|
|
if (statsTreeWidget()->selectedItems().count() > 0 && statsTreeWidget()->selectedItems()[0]->type() == mac_whole_ue_row_type_) {
|
|
// Whole-UE labels
|
|
statsTreeWidget()->setHeaderLabels(mac_whole_ue_row_labels);
|
|
} else if (statsTreeWidget()->selectedItems().count() > 0) {
|
|
// ULDL labels
|
|
switch (statsTreeWidget()->selectedItems()[0]->type()) {
|
|
case mac_ulsch_packet_count_row_type:
|
|
case mac_ulsch_byte_count_row_type:
|
|
case mac_dlsch_packet_count_row_type:
|
|
case mac_dlsch_byte_count_row_type:
|
|
statsTreeWidget()->setHeaderLabels(mac_channel_counts_labels);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
// Nothing selected yet, but set whole-UE labels.
|
|
statsTreeWidget()->setHeaderLabels(mac_whole_ue_row_labels);
|
|
}
|
|
}
|
|
|
|
void LteMacStatisticsDialog::captureFileClosing()
|
|
{
|
|
remove_tap_listener(this);
|
|
|
|
WiresharkDialog::captureFileClosing();
|
|
}
|
|
|
|
// Store filter from signal.
|
|
void LteMacStatisticsDialog::filterUpdated(QString filter)
|
|
{
|
|
displayFilter_ = filter;
|
|
}
|
|
|
|
// Get the item for the row, depending upon the type of tree item.
|
|
QList<QVariant> LteMacStatisticsDialog::treeItemData(QTreeWidgetItem *item) const
|
|
{
|
|
// Cast up to our type.
|
|
MacULDLTreeWidgetItem *channel_item = dynamic_cast<MacULDLTreeWidgetItem*>(item);
|
|
if (channel_item) {
|
|
return channel_item->rowData();
|
|
}
|
|
MacUETreeWidgetItem *ue_item = dynamic_cast<MacUETreeWidgetItem*>(item);
|
|
if (ue_item) {
|
|
return ue_item->rowData();
|
|
}
|
|
|
|
// Need to return something..
|
|
return QList<QVariant>();
|
|
}
|
|
|
|
|
|
// Stat command + args
|
|
|
|
static void
|
|
lte_mac_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("LteMacStatistics", filter.constData(), NULL);
|
|
}
|
|
|
|
static stat_tap_ui lte_mac_statistics_ui = {
|
|
REGISTER_STAT_GROUP_TELEPHONY_LTE,
|
|
QT_TRANSLATE_NOOP("LteMacStatisticsDialog", "MAC Statistics"),
|
|
"mac-lte,stat",
|
|
lte_mac_statistics_init,
|
|
0,
|
|
NULL
|
|
};
|
|
|
|
extern "C" {
|
|
|
|
void register_tap_listener_qt_lte_mac_statistics(void);
|
|
|
|
void
|
|
register_tap_listener_qt_lte_mac_statistics(void)
|
|
{
|
|
register_stat_tap_ui(<e_mac_statistics_ui, NULL);
|
|
}
|
|
|
|
}
|