forked from osmocom/wireshark
1023 lines
34 KiB
C++
1023 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.
|
|
if (channel_item != NULL) {
|
|
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_TR_NOOP("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(<e_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:
|
|
*/
|