Qt: minor updates to TCP stream dialog and throughput moving average

The time-based moving average code should use a constant window size
  as the denominator when calculating throughput.
Added QDoubleSpinbox widget to choose the time-based moving average size.
Also added (optional) ability to select ACK (reverse) packets as well as data
  packets when clicking on the base graph.
  (useful, for example, when examining SACK or DSACK packets)
Added CheckBox widget (with tooltip) for user to choose ACK selection

Change-Id: Ib3ba4f9be9e30aa8f3088b0b2d48b4ca214f7cc3
Reviewed-on: https://code.wireshark.org/review/19108
Petri-Dish: Peter Wu <peter@lekensteyn.nl>
Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org>
Reviewed-by: Peter Wu <peter@lekensteyn.nl>
This commit is contained in:
Kevin Hogan 2016-12-05 23:50:45 -08:00 committed by Peter Wu
parent 05b3c067ce
commit 5f6732d74e
3 changed files with 175 additions and 18 deletions

View File

@ -98,9 +98,11 @@ TCPStreamDialog::TCPStreamDialog(QWidget *parent, capture_file *cf, tcp_graph_ty
packet_num_(0),
mouse_drags_(true),
rubber_band_(NULL),
graph_update_timer_(NULL),
num_dsegs_(-1),
num_acks_(-1),
num_sack_ranges_(-1)
num_sack_ranges_(-1),
ma_window_size_(1.0)
{
struct segment current;
int graph_idx = -1;
@ -184,11 +186,21 @@ TCPStreamDialog::TCPStreamDialog(QWidget *parent, capture_file *cf, tcp_graph_ty
graph_.stream = header->th_stream;
findStream();
showWidgetsForGraphType();
ui->streamNumberSpinBox->blockSignals(true);
ui->streamNumberSpinBox->setMaximum(get_tcp_stream_count() - 1);
ui->streamNumberSpinBox->setValue(graph_.stream);
ui->streamNumberSpinBox->blockSignals(false);
#ifdef MA_1_SECOND
ui->maWindowSizeSpinBox->blockSignals(true);
ui->maWindowSizeSpinBox->setDecimals(6);
ui->maWindowSizeSpinBox->setMinimum(0.000001);
ui->maWindowSizeSpinBox->setValue(ma_window_size_);
ui->maWindowSizeSpinBox->blockSignals(false);
#endif
QCustomPlot *sp = ui->streamPlot;
QCPPlotTitle *file_title = new QCPPlotTitle(sp, cf_get_display_name(cap_file_));
file_title->setFont(sp->xAxis->labelFont());
@ -202,7 +214,7 @@ TCPStreamDialog::TCPStreamDialog(QWidget *parent, capture_file *cf, tcp_graph_ty
base_graph_->setPen(QPen(QBrush(graph_color_1), 0.25));
tput_graph_ = sp->addGraph(sp->xAxis, sp->yAxis2); // Throughput: Moving average
tput_graph_->setPen(QPen(QBrush(graph_color_2), 0.5));
tput_graph_->setLineStyle(QCPGraph::lsLine);
tput_graph_->setLineStyle(QCPGraph::lsStepLeft);
seg_graph_ = sp->addGraph(); // tcptrace: fwd segments
seg_graph_->setErrorType(QCPGraph::etValue);
seg_graph_->setLineStyle(QCPGraph::lsNone);
@ -264,6 +276,17 @@ void TCPStreamDialog::keyPressEvent(QKeyEvent *event)
{
int pan_pixels = event->modifiers() & Qt::ShiftModifier ? 1 : 10;
if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) &&
ui->maWindowSizeSpinBox->hasFocus()) {
// If user was editing maWindowSize, then just use the new value
// clear the focus and accept the event
// (don't propagate to default close button)
ui->maWindowSizeSpinBox->clearFocus();
event->accept();
return;
}
// XXX - This differs from the main window but matches other applications (e.g. Mozilla and Safari)
switch(event->key()) {
case Qt::Key_Minus:
@ -379,7 +402,7 @@ void TCPStreamDialog::findStream()
connect(sp, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMoved(QMouseEvent*)));
}
void TCPStreamDialog::fillGraph()
void TCPStreamDialog::fillGraph(bool reset_axes, bool set_focus)
{
QCustomPlot *sp = ui->streamPlot;
@ -424,17 +447,25 @@ void TCPStreamDialog::fillGraph()
time_stamp_map_.clear();
for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
// NOTE - adding both forward and reverse packets to time_stamp_map_
// so that both data and acks are selectable
// (this is important especially in selecting particular SACK pkts)
if (!compareHeaders(seg)) {
bytes_rev += seg->th_seglen;
pkts_rev++;
continue;
} else {
bytes_fwd += seg->th_seglen;
pkts_fwd++;
}
bytes_fwd += seg->th_seglen;
pkts_fwd++;
double ts = seg->rel_secs + seg->rel_usecs / 1000000.0;
if (first) {
if (ts_origin_conn_) ts_offset_ = ts;
if (seq_origin_zero_) seq_offset_ = seg->th_seq;
if (seq_origin_zero_) {
if (compareHeaders(seg))
seq_offset_ = seg->th_seq;
else
seq_offset_ = seg->th_ack;
}
first = false;
}
time_stamp_map_.insertMulti(ts - ts_offset_, seg);
@ -469,11 +500,37 @@ void TCPStreamDialog::fillGraph()
.arg(gchar_free_to_qstring(format_size(pkts_rev, format_size_unit_none|format_size_prefix_si)))
.arg(gchar_free_to_qstring(format_size(bytes_rev, format_size_unit_bytes|format_size_prefix_si)));
mouseMoved(NULL);
resetAxes();
if (reset_axes)
resetAxes();
else
sp->replot();
tracer_->setGraph(base_graph_);
// XXX QCustomPlot doesn't seem to draw any sort of focus indicator.
sp->setFocus();
if (set_focus)
sp->setFocus();
}
void TCPStreamDialog::showWidgetsForGraphType()
{
#ifdef MA_1_SECOND
if (graph_.type == GRAPH_THROUGHPUT) {
ui->maWindowSizeLabel->setVisible(true);
ui->maWindowSizeSpinBox->setVisible(true);
} else {
ui->maWindowSizeLabel->setVisible(false);
ui->maWindowSizeSpinBox->setVisible(false);
}
#else
ui->maWindowSizeLabel->setVisible(false);
ui->maWindowSizeSpinBox->setVisible(false);
#endif
if (graph_.type == GRAPH_TSEQ_TCPTRACE) {
ui->selectAcksCheckBox->setVisible(true);
} else {
ui->selectAcksCheckBox->setVisible(false);
}
}
void TCPStreamDialog::zoomAxes(bool in)
@ -597,6 +654,8 @@ void TCPStreamDialog::fillTcptrace()
setWindowTitle(dlg_title);
title_->setText(dlg_title);
bool allow_ack_select = ui->selectAcksCheckBox->isChecked();
QCustomPlot *sp = ui->streamPlot;
sp->yAxis->setLabel(sequence_number_label_);
@ -606,13 +665,14 @@ void TCPStreamDialog::fillTcptrace()
ack_graph_->setVisible(true);
rwin_graph_->setVisible(true);
QVector<double> seq_time, seq, sb_time, sb_center, sb_span, ackrwin_time, ack, rwin;
QVector<double> pkt_time, pkt_seqnums, sb_time, sb_center, sb_span, ackrwin_time, ack, rwin;
for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
double ts = (seg->rel_secs + seg->rel_usecs / 1000000.0) - ts_offset_;
if (compareHeaders(seg)) {
// Add forward direction to base_graph_ (to select data packets)
// Forward direction: seq + data
seq_time.append(ts);
seq.append(seg->th_seq - seq_offset_);
pkt_time.append(ts);
pkt_seqnums.append(seg->th_seq - seq_offset_);
// QCP doesn't have a segment graph type. For now, fake
// it with error bars.
@ -629,12 +689,18 @@ void TCPStreamDialog::fillTcptrace()
continue;
}
double ackno = seg->th_ack - seq_offset_;
if (allow_ack_select) {
// Add reverse direction to base_graph_ (to select ACK packets)
pkt_time.append(ts);
pkt_seqnums.append(ackno);
}
// Also add reverse packets to the ack_graph_
ackrwin_time.append(ts);
ack.append(ackno);
rwin.append(ackno + seg->th_win);
}
}
base_graph_->setData(seq_time, seq);
base_graph_->setData(pkt_time, pkt_seqnums);
seg_graph_->setDataValueError(sb_time, sb_center, sb_span);
ack_graph_->setData(ackrwin_time, ack);
rwin_graph_->setData(ackrwin_time, rwin);
@ -644,7 +710,7 @@ void TCPStreamDialog::fillThroughput()
{
QString dlg_title = QString(tr("Throughput")) + streamDescription();
#ifdef MA_1_SECOND
dlg_title.append(tr(" (1s MA)"));
dlg_title.append(tr(" (MA)"));
#else
dlg_title.append(QString(tr(" (%1 Segment MA)")).arg(moving_avg_period_));
#endif
@ -660,7 +726,11 @@ void TCPStreamDialog::fillThroughput()
tput_graph_->setVisible(true);
#ifdef MA_1_SECOND
if (!graph_.segments) {
#else
if (!graph_.segments || !graph_.segments->next) {
#endif
dlg_title.append(tr(" [not enough data]"));
return;
}
@ -669,10 +739,20 @@ void TCPStreamDialog::fillThroughput()
int oldest = 0;
guint64 sum = 0;
// Financial charts don't show MA data until a full period has elapsed.
// [ NOTE - this is because they assume that there's old data that they
// don't have access to - but in our case we know that there's NO
// data prior to the first packet in the stream - so it's fine to
// spit out the MA immediately... ]
// The Rosetta Code MA examples start spitting out values immediately.
// For now use not-really-correct initial values just to keep our vector
// lengths the same.
#ifdef MA_1_SECOND
// NOTE that for the time-based MA case, you certainly can start with the
// first segment!
for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
#else
for (struct segment *seg = graph_.segments->next; seg != NULL; seg = seg->next) {
#endif
if (!compareHeaders(seg)) {
continue;
}
@ -683,8 +763,12 @@ void TCPStreamDialog::fillThroughput()
seg_len.append(seg->th_seglen);
#ifdef MA_1_SECOND
while (ts - rel_time[oldest] > 1.0 && oldest < rel_time.size()) {
while (ts - rel_time[oldest] > ma_window_size_ && oldest < rel_time.size()) {
sum -= seg_len[oldest];
// append points where a packet LEAVES the MA window
// (as well as, below, where they ENTER the MA window)
tput.append(sum * 8.0 / ma_window_size_);
tput_time.append(rel_time[oldest] + ma_window_size_);
oldest++;
}
#else
@ -694,14 +778,19 @@ void TCPStreamDialog::fillThroughput()
}
#endif
double dtime = ts - rel_time[oldest];
double av_tput;
sum += seg->th_seglen;
#ifdef MA_1_SECOND
// for time-based MA, delta_t is constant
av_tput = sum * 8.0 / ma_window_size_;
#else
double dtime = ts - rel_time[oldest];
if (dtime > 0.0) {
av_tput = sum * 8.0 / dtime;
} else {
av_tput = 0.0;
}
#endif
// Add a data point only if our time window has advanced. Otherwise
// update the most recent point. (We might want to show a warning
@ -1087,6 +1176,7 @@ void TCPStreamDialog::on_graphTypeComboBox_currentIndexChanged(int index)
{
if (index < 0) return;
graph_.type = static_cast<tcp_graph_type>(ui->graphTypeComboBox->itemData(index).toInt());
showWidgetsForGraphType();
fillGraph();
}
@ -1102,6 +1192,21 @@ void TCPStreamDialog::setCaptureFile(capture_file *cf)
}
}
void TCPStreamDialog::updateGraph()
{
// graph_update_timer_ is created only when there's a pending
// parameter change, and deleted when an update consumes that change.
// Therefore lack of an update timer means nothing to update.
if (graph_update_timer_) {
if (graph_update_timer_->isActive())
graph_update_timer_->stop();
delete graph_update_timer_;
graph_update_timer_ = NULL;
fillGraph(/*reset_axes=*/false, /*set_focus=*/false);
}
}
void TCPStreamDialog::on_streamNumberSpinBox_valueChanged(int new_stream)
{
if (new_stream >= 0 && new_stream < int(get_tcp_stream_count())) {
@ -1109,10 +1214,33 @@ void TCPStreamDialog::on_streamNumberSpinBox_valueChanged(int new_stream)
clear_address(&graph_.src_address);
clear_address(&graph_.dst_address);
findStream();
fillGraph();
fillGraph(/*reset_axes=*/true, /*set_focus=*/false);
}
}
void TCPStreamDialog::on_maWindowSizeSpinBox_valueChanged(double new_ma_size)
{
if (new_ma_size > 0.0) {
ma_window_size_ = new_ma_size;
if (!graph_update_timer_) {
graph_update_timer_ = new QTimer(this);
graph_update_timer_->setSingleShot(true);
connect(graph_update_timer_, SIGNAL(timeout()), this, SLOT(updateGraph()));
}
graph_update_timer_->start(1000);
}
}
void TCPStreamDialog::on_maWindowSizeSpinBox_editingFinished()
{
updateGraph();
}
void TCPStreamDialog::on_selectAcksCheckBox_stateChanged(int /* state */)
{
fillGraph(/*reset_axes=*/false, /*set_focus=*/true);
}
void TCPStreamDialog::on_otherDirectionButton_clicked()
{
on_actionSwitchDirection_triggered();

View File

@ -36,6 +36,7 @@
#include <QDialog>
#include <QMenu>
#include <QRubberBand>
#include <QTimer>
namespace Ui {
class TCPStreamDialog;
@ -54,6 +55,7 @@ signals:
public slots:
void setCaptureFile(capture_file *cf);
void updateGraph();
protected:
void showEvent(QShowEvent *event);
@ -85,13 +87,17 @@ private:
QRubberBand *rubber_band_;
QPoint rb_origin_;
QMenu ctx_menu_;
QTimer *graph_update_timer_;
int num_dsegs_;
int num_acks_;
int num_sack_ranges_;
double ma_window_size_;
void findStream();
void fillGraph();
void fillGraph(bool reset_axes = true, bool set_focus = true);
void showWidgetsForGraphType();
void zoomAxes(bool in);
void zoomXAxis(bool in);
void zoomYAxis(bool in);
@ -117,6 +123,9 @@ private slots:
void on_graphTypeComboBox_currentIndexChanged(int index);
void on_resetButton_clicked();
void on_streamNumberSpinBox_valueChanged(int new_stream);
void on_maWindowSizeSpinBox_valueChanged(double new_ma_size);
void on_maWindowSizeSpinBox_editingFinished();
void on_selectAcksCheckBox_stateChanged(int state);
void on_otherDirectionButton_clicked();
void on_dragRadioButton_toggled(bool checked);
void on_zoomRadioButton_toggled(bool checked);

View File

@ -91,6 +91,26 @@
<item>
<widget class="QComboBox" name="graphTypeComboBox"/>
</item>
<item>
<widget class="QLabel" name="maWindowSizeLabel">
<property name="text">
<string>MA Window (s)</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="maWindowSizeSpinBox" />
</item>
<item>
<widget class="QCheckBox" name="selectAcksCheckBox">
<property name="toolTip">
<string>Allow ACKs as well as data packets to be selected by clicking on the graph</string>
</property>
<property name="text">
<string>select ACKs</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">