diff options
author | Kevin Hogan <kwabena@google.com> | 2017-01-20 20:57:09 -0800 |
---|---|---|
committer | Alexis La Goutte <alexis.lagoutte@gmail.com> | 2017-01-22 14:41:49 +0000 |
commit | 799827b503a61d1b7c0306f2c987e42e7280b0a3 (patch) | |
tree | ec107f1f6a3855b3f3fd71c5aace3a3de08744c2 | |
parent | 21a3b8cc71ac127e21375c62e0a738db8f3ea286 (diff) | |
download | wireshark-799827b503a61d1b7c0306f2c987e42e7280b0a3.tar.gz |
Qt: Add Goodput graph (ACK rate), and minor bug fixes
Add Goodput graph:
- measures rate of ACKed bytes (including SACKed bytes)
- useful to compare to throughput during slow-start to estimate
bottleneck rate
Add graph selection checkboxes to multi-plot graphs:
- most important for Throughput, since there are good cases
for showing a subset of graphs at once
- also added for Window Scale, since the addition is similar
to that for Throughput
Minor bug fixes:
- allow zoom rect to work when growing in any direction
(not just right and up)
- keep stray mouse clicks from re-doing a previous zoom
- hide rubber band if active when keypress changes mouse mode
to drag
- allow mouse clicks on open space or grpah to return to default focus
(i.e. focus on graph)
Change-Id: Id29356ceec810ebdbed9c3c0d8415416401fe643
Reviewed-on: https://code.wireshark.org/review/19718
Petri-Dish: Anders Broman <a.broman58@gmail.com>
Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org>
Reviewed-by: Alexis La Goutte <alexis.lagoutte@gmail.com>
-rw-r--r-- | ui/qt/tcp_stream_dialog.cpp | 542 | ||||
-rw-r--r-- | ui/qt/tcp_stream_dialog.h | 7 | ||||
-rw-r--r-- | ui/qt/tcp_stream_dialog.ui | 95 | ||||
-rw-r--r-- | ui/tap-tcp-stream.h | 6 |
4 files changed, 592 insertions, 58 deletions
diff --git a/ui/qt/tcp_stream_dialog.cpp b/ui/qt/tcp_stream_dialog.cpp index 8d785274ba..a103e06211 100644 --- a/ui/qt/tcp_stream_dialog.cpp +++ b/ui/qt/tcp_stream_dialog.cpp @@ -22,6 +22,10 @@ #include "tcp_stream_dialog.h" #include <ui_tcp_stream_dialog.h> +#include <algorithm> // for std::sort +#include <utility> // for std::pair +#include <vector> + #include "epan/to_str.h" #include "wsutil/str_util.h" @@ -74,7 +78,7 @@ const double pkt_point_size_ = 3.0; // in zoom mode. const int min_zoom_pixels_ = 20; -const QString average_throughput_label_ = QObject::tr("Average Througput (bits/s)"); +const QString average_throughput_label_ = QObject::tr("Average Throughput (bits/s)"); const QString round_trip_time_ms_label_ = QObject::tr("Round Trip Time (ms)"); const QString segment_length_label_ = QObject::tr("Segment Length (B)"); const QString sequence_number_label_ = QObject::tr("Sequence Number (B)"); @@ -92,6 +96,7 @@ TCPStreamDialog::TCPStreamDialog(QWidget *parent, capture_file *cf, tcp_graph_ty title_(NULL), base_graph_(NULL), tput_graph_(NULL), + goodput_graph_(NULL), seg_graph_(NULL), ack_graph_(NULL), rwin_graph_(NULL), @@ -202,6 +207,24 @@ TCPStreamDialog::TCPStreamDialog(QWidget *parent, capture_file *cf, tcp_graph_ty ui->maWindowSizeSpinBox->blockSignals(false); #endif + // set which Throughput graphs are displayed by default + ui->showSegLengthCheckBox->blockSignals(true); + ui->showSegLengthCheckBox->setChecked(true); + ui->showSegLengthCheckBox->blockSignals(false); + + ui->showThroughputCheckBox->blockSignals(true); + ui->showThroughputCheckBox->setChecked(true); + ui->showThroughputCheckBox->blockSignals(false); + + // set which WScale graphs are displayed by default + ui->showRcvWinCheckBox->blockSignals(true); + ui->showRcvWinCheckBox->setChecked(true); + ui->showRcvWinCheckBox->blockSignals(false); + + ui->showBytesOutCheckBox->blockSignals(true); + ui->showBytesOutCheckBox->setChecked(true); + ui->showBytesOutCheckBox->blockSignals(false); + QCustomPlot *sp = ui->streamPlot; QCPPlotTitle *file_title = new QCPPlotTitle(sp, cf_get_display_name(cap_file_)); file_title->setFont(sp->xAxis->labelFont()); @@ -214,10 +237,14 @@ TCPStreamDialog::TCPStreamDialog(QWidget *parent, capture_file *cf, tcp_graph_ty // Base Graph - enables selecting segments (both data and SACKs) base_graph_ = sp->addGraph(); base_graph_->setPen(QPen(QBrush(graph_color_1), 0.25)); - // Throughput Graph + // Throughput Graph - rate of sent bytes tput_graph_ = sp->addGraph(sp->xAxis, sp->yAxis2); tput_graph_->setPen(QPen(QBrush(graph_color_2), 0.5)); tput_graph_->setLineStyle(QCPGraph::lsStepLeft); + // Goodput Graph - rate of ACKed bytes + goodput_graph_ = sp->addGraph(sp->xAxis, sp->yAxis2); + goodput_graph_->setPen(QPen(QBrush(graph_color_3), 0.5)); + goodput_graph_->setLineStyle(QCPGraph::lsStepLeft); // Seg Graph - displays forward data segments on tcptrace graph seg_graph_ = sp->addGraph(); seg_graph_->setErrorType(QCPGraph::etValue); @@ -416,6 +443,14 @@ void TCPStreamDialog::keyPressEvent(QKeyEvent *event) QDialog::keyPressEvent(event); } +void TCPStreamDialog::mousePressEvent(QMouseEvent *event) +{ + // if no-one else wants the event, then this is a click on blank space. + // Use this opportunity to set focus back to default, and accept event. + ui->streamPlot->setFocus(); + event->accept(); +} + void TCPStreamDialog::mouseReleaseEvent(QMouseEvent *event) { mouseReleased(event); @@ -460,7 +495,9 @@ void TCPStreamDialog::fillGraph(bool reset_axes, bool set_focus) sp->xAxis->setLabel(time_s_label_); sp->xAxis->setNumberFormat("gb"); - sp->xAxis->setNumberPrecision(6); + // Use enough precision to mark microseconds + // when zooming in on a <100s capture + sp->xAxis->setNumberPrecision(8); sp->yAxis->setNumberFormat("f"); sp->yAxis->setNumberPrecision(0); sp->yAxis2->setVisible(false); @@ -548,7 +585,9 @@ void TCPStreamDialog::fillGraph(bool reset_axes, bool set_focus) resetAxes(); else sp->replot(); - tracer_->setGraph(base_graph_); + // Throughput and Window Scale graphs can hide base_graph_ + if (base_graph_ && base_graph_->visible()) + tracer_->setGraph(base_graph_); // XXX QCustomPlot doesn't seem to draw any sort of focus indicator. if (set_focus) @@ -557,24 +596,38 @@ void TCPStreamDialog::fillGraph(bool reset_axes, bool set_focus) void TCPStreamDialog::showWidgetsForGraphType() { -#ifdef MA_1_SECOND if (graph_.type == GRAPH_THROUGHPUT) { +#ifdef MA_1_SECOND ui->maWindowSizeLabel->setVisible(true); ui->maWindowSizeSpinBox->setVisible(true); +#else + ui->maWindowSizeLabel->setVisible(false); + ui->maWindowSizeSpinBox->setVisible(false); +#endif + ui->showSegLengthCheckBox->setVisible(true); + ui->showThroughputCheckBox->setVisible(true); + ui->showGoodputCheckBox->setVisible(true); } else { ui->maWindowSizeLabel->setVisible(false); ui->maWindowSizeSpinBox->setVisible(false); + ui->showSegLengthCheckBox->setVisible(false); + ui->showThroughputCheckBox->setVisible(false); + ui->showGoodputCheckBox->setVisible(false); } -#else - ui->maWindowSizeLabel->setVisible(false); - ui->maWindowSizeSpinBox->setVisible(false); -#endif if (graph_.type == GRAPH_TSEQ_TCPTRACE) { ui->selectSACKsCheckBox->setVisible(true); } else { ui->selectSACKsCheckBox->setVisible(false); } + + if (graph_.type == GRAPH_WSCALE) { + ui->showRcvWinCheckBox->setVisible(true); + ui->showBytesOutCheckBox->setVisible(true); + } else { + ui->showRcvWinCheckBox->setVisible(false); + ui->showBytesOutCheckBox->setVisible(false); + } } void TCPStreamDialog::zoomAxes(bool in) @@ -646,7 +699,7 @@ void TCPStreamDialog::resetAxes() double pixel_pad = 10.0; // per side sp->rescaleAxes(true); - tput_graph_->rescaleValueAxis(false, true); +// tput_graph_->rescaleValueAxis(false, true); // base_graph_->rescaleAxes(false, true); // for (int i = 0; i < sp->graphCount(); i++) { // sp->graph(i)->rescaleValueAxis(false, true); @@ -775,6 +828,261 @@ void TCPStreamDialog::fillTcptrace() rwin_graph_->setData(ackrwin_time, rwin); } +// If the current implementation of incorporating SACKs in goodput calc +// is slow, comment out the following line to ignore SACKs in goodput calc. +#define USE_SACKS_IN_GOODPUT_CALC + +#ifdef USE_SACKS_IN_GOODPUT_CALC +// to incorporate SACKED segments into goodput calculation, +// need to keep track of all the SACK blocks we haven't yet +// fully ACKed. +// I expect this to be _relatively_ small, so using vector to store +// them. If this performs badly, it can be refactored with std::list +// or std::map. +typedef std::pair<guint32, guint32> sack_t; +typedef std::vector<sack_t> sack_list_t; +static inline bool compare_sack(const sack_t& s1, const sack_t& s2) { + return tcp_seq_before(s1.first, s2.first); +} + +// Helper function to adjust an acked seglen for goodput: +// - removes previously sacked ranges from seglen (and from old_sacks), +// - adds newly sacked ranges to seglen (and to old_sacks) +static void +goodput_adjust_for_sacks(guint32 *seglen, guint32 last_ack, + sack_list_t& new_sacks, guint8 num_sack_ranges, + sack_list_t& old_sacks) { + + // Step 1 - For any old_sacks acked by last_ack, + // delete their acked length from seglen, + // and remove the sack block (or portion) + // from (sorted) old_sacks. + sack_list_t::iterator unacked = old_sacks.begin(); + while (unacked != old_sacks.end()) { + // break on first sack not fully acked + if (tcp_seq_before(last_ack, unacked->second)) { + if (tcp_seq_after(last_ack, unacked->first)) { + // partially acked - modify to remove acked part + *seglen -= (last_ack - unacked->first); + unacked->first = last_ack; + } + break; + } + // remove fully acked sacks from seglen and move on + // (we'll actually remove from the list when loop is done) + *seglen -= (unacked->second - unacked->first); + ++unacked; + } + // actually remove all fully acked sacks from old_sacks list + if (unacked != old_sacks.begin()) + old_sacks.erase(old_sacks.begin(), unacked); + + // Step 2 - for any new_sacks that precede last_ack, + // ignore them. (These would generally be SACKed dup-acks of + // a retransmitted seg). + // [ in the unlikely case that any new SACK straddles last_ack, + // the sack block will be modified to remove the acked portion ] + int next_new_idx = 0; + while (next_new_idx < num_sack_ranges) { + if (tcp_seq_before(last_ack, new_sacks[next_new_idx].second)) { + // if a new SACK block is unacked by its own packet, then it's + // likely fully unacked, but let's check for partial ack anyway, + // and truncate the SACK so that it's fully unacked: + if (tcp_seq_before(new_sacks[next_new_idx].first, last_ack)) + new_sacks[next_new_idx].first = last_ack; + break; + } + ++next_new_idx; + } + + // Step 3 - for any byte ranges in remaining new_sacks + // that don't already exist in old_sacks, add + // their length to seglen + // and add that range (by extension, if possible) to + // the list of old_sacks. + + sack_list_t::iterator next_old = old_sacks.begin(); + + while (next_new_idx < num_sack_ranges && + next_old != old_sacks.end()) { + sack_t* next_new = &new_sacks[next_new_idx]; + + // Assumptions / Invariants: + // - new and old lists are sorted + // - span of leftmost to rightmost endpt. is less than half uint32 range + // [ensures transitivity - e.g. before(a,b) and before(b,c) ==> before(a,c)] + // - all SACKs are non-empty (sack.left before sack.right) + // - adjacent SACKs in list always have a gap between them + // (sack.right before next_sack.left) + + // Given these assumptions, and noting that there are only three + // possible comparisons for a pair of points (before/equal/after), + // there are only a few possible relative configurations + // of next_old and next_new: + // next_new: + // [-------------) + // next_old: + // 1. [---) + // 2. [-------) + // 3. [----------------) + // 4. [---------------------) + // 5. [----------------------------) + // 6. [--------) + // 7. [-------------) + // 8. [--------------------) + // 9. [---) + // 10. [--------) + // 11. [---------------) + // 12. [------) + // 13. [--) + + // Case 1: end of next_old is before beginning of next_new + // next_new: + // [-------------) ... <end> + // next_old: + // 1. [---) ... <end> + if (tcp_seq_before(next_old->second, next_new->first)) { + // Actions: + // advance to the next sack in old_sacks + ++next_old; + // retry from the top + continue; + } + + // Case 13: end of next_new is before beginning of next_old + // next_new: + // [-------------) ... <end> + // next_old: + // 13. [--) ... <end> + if (tcp_seq_before(next_new->second, next_old->first)) { + // Actions: + // add then entire length of next_new into seglen + *seglen += (next_new->second - next_new->first); + // insert next_new before next_old in old_sacks + // (be sure to save and restore next_old iterator around insert!) + int next_old_idx = next_old - old_sacks.begin(); + old_sacks.insert(next_old, *next_new); + next_old = old_sacks.begin() + next_old_idx + 1; + // advance to the next remaining sack in new_sacks + ++next_new_idx; + // retry from the top + continue; + } + + // Remaining possible configurations: + // next_new: + // [-------------) + // next_old: + // 2. [-------) + // 3. [----------------) + // 4. [---------------------) + // 5. [----------------------------) + // 6. [--------) + // 7. [-------------) + // 8. [--------------------) + // 9. [---) + // 10. [--------) + // 11. [---------------) + // 12. [------) + + // Cases 2,3,6,9: end of next_old is before end of next_new + // next_new: + // [-------------) + // next_old: + // 2. [-------) + // 3. [----------------) + // 6. [--------) + // 9. [---) + // Actions: + // until end of next_old is equal or after end of next_new, + // repeatedly extend next_old, coalescing with next_next_old + // if necessary. (and add extended bytes to seglen) + while (tcp_seq_before(next_old->second, next_new->second)) { + // if end of next_new doesn't collide with start of next_next_old, + if (((next_old+1) == old_sacks.end()) || + tcp_seq_before(next_new->second, (next_old + 1)->first)) { + // extend end of next_old up to end of next_new, + // adding extended bytes to seglen + *seglen += (next_new->second - next_old->second); + next_old->second = next_new->second; + } + // otherwise, coalesce next_old with next_next_old + else { + // add bytes to close gap between sacks to seglen + *seglen += ((next_old + 1)->first - next_old->second); + // coalesce next_next_old into next_old + next_old->second = (next_old + 1)->second; + old_sacks.erase(next_old + 1); + } + } + // This operation turns: + // Cases 2 and 3 into Case 4 or 5 + // Case 6 into Case 7 + // Case 9 into Case 10 + // Leaving: + + // Remaining possible configurations: + // next_new: + // [-------------) + // next_old: + // 4. [---------------------) + // 5. [----------------------------) + // 7. [-------------) + // 8. [--------------------) + // 10. [--------) + // 11. [---------------) + // 12. [------) + + // Cases 10,11,12: start of next_new is before start of next_old + // next_new: + // [-------------) + // next_old: + // 10. [--------) + // 11. [---------------) + // 12. [------) + if (tcp_seq_before(next_new->first, next_old->first)) { + // Actions: + // add the unaccounted bytes in next_new to seglen + *seglen += (next_old->first - next_new->first); + // then pull the start of next_old back to the start of next_new + next_old->first = next_new->first; + } + // This operation turns: + // Case 10 into Case 7 + // Cases 11 and 12 into Case 8 + // Leaving: + + // Remaining possible configurations: + // next_new: + // [-------------) + // next_old: + // 4. [---------------------) + // 5. [----------------------------) + // 7. [-------------) + // 8. [--------------------) + + // In these cases, the bytes in next_new are fully accounted + // by the bytes in next_old, so we can move on to look at + // the next sack block in new_sacks + ++next_new_idx; + } + // Conditions for leaving loop: + // - we processed all remaining new_sacks - nothing left to do + // (next_new_idx == num_sack_ranges) + // OR + // - all remaining new_sacks start at least one byte after + // the rightmost edge of the last old_sack + // (meaning we can just add the remaining new_sacks to old_sacks list, + // and add them directly to the goodput seglen) + while (next_new_idx < num_sack_ranges) { + sack_t* next_new = &new_sacks[next_new_idx]; + *seglen += (next_new->second - next_new->first); + old_sacks.push_back(*next_new); + ++next_new_idx; + } +} +#endif // USE_SACKS_IN_GOODPUT_CALC + void TCPStreamDialog::fillThroughput() { QString dlg_title = QString(tr("Throughput")) + streamDescription(); @@ -793,7 +1101,9 @@ void TCPStreamDialog::fillThroughput() sp->yAxis2->setTickLabelColor(QColor(graph_color_2)); sp->yAxis2->setVisible(true); - tput_graph_->setVisible(true); + base_graph_->setVisible(ui->showSegLengthCheckBox->isChecked()); + tput_graph_->setVisible(ui->showThroughputCheckBox->isChecked()); + goodput_graph_->setVisible(ui->showGoodputCheckBox->isChecked()); #ifdef MA_1_SECOND if (!graph_.segments) { @@ -804,9 +1114,38 @@ void TCPStreamDialog::fillThroughput() return; } - QVector<double> rel_time, seg_len, tput_time, tput; - int oldest = 0; - guint64 sum = 0; + QVector<double> seg_rel_times, ack_rel_times; + QVector<double> seg_lens, ack_lens; + QVector<double> tput_times, gput_times; + QVector<double> tputs, gputs; + int oldest_seg = 0, oldest_ack = 0; + guint64 seg_sum = 0, ack_sum = 0; + guint32 seglen = 0; + +#ifdef USE_SACKS_IN_GOODPUT_CALC + // to incorporate SACKED segments into goodput calculation, + // need to keep track of all the SACK blocks we haven't yet + // fully ACKed. + sack_list_t old_sacks, new_sacks; + new_sacks.reserve(MAX_TCP_SACK_RANGES); + // statically allocate current_sacks vector + // [ std::array might be better, but that is C++11 ] + for (int i = 0; i < MAX_TCP_SACK_RANGES; ++i) { + new_sacks.push_back(sack_t(0,0)); + } + old_sacks.reserve(2*MAX_TCP_SACK_RANGES); +#endif // USE_SACKS_IN_GOODPUT_CALC + + // need first acked sequence number to jump-start + // computation of acked bytes per packet + guint32 last_ack = 0; + for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) { + // first reverse packet with ACK flag tells us first acked sequence # + if (!compareHeaders(seg) && (seg->th_flags & TH_ACK)) { + last_ack = seg->th_ack; + break; + } + } // 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 @@ -822,57 +1161,95 @@ void TCPStreamDialog::fillThroughput() #else for (struct segment *seg = graph_.segments->next; seg != NULL; seg = seg->next) { #endif - if (!compareHeaders(seg)) { - continue; - } + bool is_forward_seg = compareHeaders(seg); + QVector<double>& r_pkt_times = is_forward_seg ? seg_rel_times : ack_rel_times; + QVector<double>& r_lens = is_forward_seg ? seg_lens : ack_lens; + QVector<double>& r_Xput_times = is_forward_seg ? tput_times : gput_times; + QVector<double>& r_Xputs = is_forward_seg ? tputs : gputs; + int& r_oldest = is_forward_seg ? oldest_seg : oldest_ack; + guint64& r_sum = is_forward_seg ? seg_sum : ack_sum; double ts = (seg->rel_secs + seg->rel_usecs / 1000000.0) - ts_offset_; - rel_time.append(ts); - seg_len.append(seg->th_seglen); + if (is_forward_seg) { + seglen = seg->th_seglen; + } else { + if ((seg->th_flags & TH_ACK) && + tcp_seq_eq_or_after(seg->th_ack, last_ack)) { + seglen = seg->th_ack - last_ack; + last_ack = seg->th_ack; +#ifdef USE_SACKS_IN_GOODPUT_CALC + // copy any sack_ranges into new_sacks, and sort. + for(int i = 0; i < seg->num_sack_ranges; ++i) { + new_sacks[i].first = seg->sack_left_edge[i]; + new_sacks[i].second = seg->sack_right_edge[i]; + } + std::sort(new_sacks.begin(), + new_sacks.begin() + seg->num_sack_ranges, + compare_sack); + + // adjust the seglen based on new and old sacks, + // and update the old_sacks list + goodput_adjust_for_sacks(&seglen, last_ack, + new_sacks, seg->num_sack_ranges, + old_sacks); +#endif // USE_SACKS_IN_GOODPUT_CALC + } else { + seglen = 0; + } + } + + r_pkt_times.append(ts); + r_lens.append(seglen); #ifdef MA_1_SECOND - while (ts - rel_time[oldest] > ma_window_size_ && oldest < rel_time.size()) { - sum -= seg_len[oldest]; + while (ts - r_pkt_times[r_oldest] > ma_window_size_ && r_oldest < r_pkt_times.size()) { + r_sum -= r_lens[r_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++; + r_Xputs.append(r_sum * 8.0 / ma_window_size_); + r_Xput_times.append(r_pkt_times[r_oldest] + ma_window_size_); + r_oldest++; } #else - if (seg_len.size() > moving_avg_period_) { - sum -= seg_len[oldest]; - oldest++; + if (r_lens.size() > moving_avg_period_) { + r_sum -= r_lens[r_oldest]; + r_oldest++; } #endif - double av_tput; - sum += seg->th_seglen; + // av_Xput computes Xput, i.e.: + // throughput for forward packets + // goodput for reverse packets + double av_Xput; + r_sum += seglen; #ifdef MA_1_SECOND // for time-based MA, delta_t is constant - av_tput = sum * 8.0 / ma_window_size_; + av_Xput = r_sum * 8.0 / ma_window_size_; #else - double dtime = ts - rel_time[oldest]; + double dtime = 0.0; + if (r_oldest > 0) + dtime = ts - r_pkt_times[r_oldest-1]; if (dtime > 0.0) { - av_tput = sum * 8.0 / dtime; + av_Xput = r_sum * 8.0 / dtime; } else { - av_tput = 0.0; + av_Xput = 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 // for out-of-order packets.) - if (tput_time.size() > 0 && ts <= tput_time.last()) { - tput[tput.size() - 1] = av_tput; + if (r_Xput_times.size() > 0 && ts <= r_Xput_times.last()) { + r_Xputs[r_Xputs.size() - 1] = av_Xput; } else { - tput.append(av_tput); - tput_time.append(ts); + r_Xputs.append(av_Xput); + r_Xput_times.append(ts); } } - base_graph_->setData(rel_time, seg_len); - tput_graph_->setData(tput_time, tput); + base_graph_->setData(seg_rel_times, seg_lens); + tput_graph_->setData(tput_times, tputs); + goodput_graph_->setData(gput_times, gputs); } // rtt_selectively_ack_range: @@ -1054,7 +1431,8 @@ void TCPStreamDialog::fillWindowScale() base_graph_->setLineStyle(QCPGraph::lsStepLeft); // use rwin_graph_ here to show rwin window scale // (derived from ACK packets) - rwin_graph_->setVisible(true); + base_graph_->setVisible(ui->showBytesOutCheckBox->isChecked()); + rwin_graph_->setVisible(ui->showRcvWinCheckBox->isChecked()); QVector<double> rel_time, win_size; QVector<double> cwnd_time, cwnd_size; @@ -1144,12 +1522,13 @@ QRectF TCPStreamDialog::getZoomRanges(QRect zoom_rect) { QRectF zoom_ranges = QRectF(); - if (zoom_rect.width() < min_zoom_pixels_ && zoom_rect.height() < min_zoom_pixels_) { + QCustomPlot *sp = ui->streamPlot; + QRect zr = zoom_rect.normalized(); + + if (zr.width() < min_zoom_pixels_ && zr.height() < min_zoom_pixels_) { return zoom_ranges; } - QCustomPlot *sp = ui->streamPlot; - QRect zr = zoom_rect.normalized(); QRect ar = sp->axisRect()->rect(); if (ar.intersects(zr)) { QRect zsr = ar.intersected(zr); @@ -1169,6 +1548,9 @@ void TCPStreamDialog::graphClicked(QMouseEvent *event) { QCustomPlot *sp = ui->streamPlot; + // mouse press on graph should reset focus to graph + sp->setFocus(); + if (event->button() == Qt::RightButton) { // XXX We should find some way to get streamPlot to handle a // contextMenuEvent instead. @@ -1198,11 +1580,8 @@ void TCPStreamDialog::axisClicked(QCPAxis *axis, QCPAxis::SelectablePart, QMouse case GRAPH_TSEQ_STEVENS: case GRAPH_TSEQ_TCPTRACE: case GRAPH_WSCALE: - ts_origin_conn_ = ts_origin_conn_ ? false : true; - fillGraph(); - break; case GRAPH_RTT: - seq_origin_zero_ = seq_origin_zero_ ? false : true; + ts_origin_conn_ = ts_origin_conn_ ? false : true; fillGraph(); break; default: @@ -1313,7 +1692,7 @@ void TCPStreamDialog::mouseMoved(QMouseEvent *event) void TCPStreamDialog::mouseReleased(QMouseEvent *event) { - if (rubber_band_) { + if (rubber_band_ && rubber_band_->isVisible()) { rubber_band_->hide(); if (!mouse_drags_) { QRectF zoom_ranges = getZoomRanges(QRect(rb_origin_, event->pos())); @@ -1443,17 +1822,70 @@ void TCPStreamDialog::on_otherDirectionButton_clicked() void TCPStreamDialog::on_dragRadioButton_toggled(bool checked) { - if (checked) mouse_drags_ = true; - ui->streamPlot->setInteractions( - QCP::iRangeDrag | - QCP::iRangeZoom - ); + if (checked) { + mouse_drags_ = true; + if (rubber_band_ && rubber_band_->isVisible()) + rubber_band_->hide(); + ui->streamPlot->setInteractions( + QCP::iRangeDrag | + QCP::iRangeZoom + ); + } } void TCPStreamDialog::on_zoomRadioButton_toggled(bool checked) { - if (checked) mouse_drags_ = false; - ui->streamPlot->setInteractions(0); + if (checked) { + mouse_drags_ = false; + ui->streamPlot->setInteractions(0); + } +} + +void TCPStreamDialog::on_showSegLengthCheckBox_stateChanged(int state) +{ + bool visible = (state != 0); + if (graph_.type == GRAPH_THROUGHPUT && base_graph_ != NULL) { + base_graph_->setVisible(visible); + tracer_->setGraph(visible ? base_graph_ : NULL); + ui->streamPlot->replot(); + } +} + +void TCPStreamDialog::on_showThroughputCheckBox_stateChanged(int state) +{ + bool visible = (state != 0); + if (graph_.type == GRAPH_THROUGHPUT && tput_graph_ != NULL) { + tput_graph_->setVisible(visible); + ui->streamPlot->replot(); + } +} + +void TCPStreamDialog::on_showGoodputCheckBox_stateChanged(int state) +{ + bool visible = (state != 0); + if (graph_.type == GRAPH_THROUGHPUT && goodput_graph_ != NULL) { + goodput_graph_->setVisible(visible); + ui->streamPlot->replot(); + } +} + +void TCPStreamDialog::on_showRcvWinCheckBox_stateChanged(int state) +{ + bool visible = (state != 0); + if (graph_.type == GRAPH_WSCALE && rwin_graph_ != NULL) { + rwin_graph_->setVisible(visible); + ui->streamPlot->replot(); + } +} + +void TCPStreamDialog::on_showBytesOutCheckBox_stateChanged(int state) +{ + bool visible = (state != 0); + if (graph_.type == GRAPH_WSCALE && base_graph_ != NULL) { + base_graph_->setVisible(visible); + tracer_->setGraph(visible ? base_graph_ : NULL); + ui->streamPlot->replot(); + } } void TCPStreamDialog::on_actionZoomIn_triggered() diff --git a/ui/qt/tcp_stream_dialog.h b/ui/qt/tcp_stream_dialog.h index e313056c8e..c36d536dec 100644 --- a/ui/qt/tcp_stream_dialog.h +++ b/ui/qt/tcp_stream_dialog.h @@ -60,6 +60,7 @@ public slots: protected: void showEvent(QShowEvent *event); void keyPressEvent(QKeyEvent *event); + void mousePressEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); private: @@ -75,6 +76,7 @@ private: QString stream_desc_; QCPGraph *base_graph_; // Clickable packets QCPGraph *tput_graph_; + QCPGraph *goodput_graph_; QCPGraph *seg_graph_; QCPGraph *ack_graph_; QCPGraph *sack_graph_; @@ -148,6 +150,11 @@ private slots: void on_otherDirectionButton_clicked(); void on_dragRadioButton_toggled(bool checked); void on_zoomRadioButton_toggled(bool checked); + void on_showSegLengthCheckBox_stateChanged(int state); + void on_showThroughputCheckBox_stateChanged(int state); + void on_showGoodputCheckBox_stateChanged(int state); + void on_showRcvWinCheckBox_stateChanged(int state); + void on_showBytesOutCheckBox_stateChanged(int state); void on_actionZoomIn_triggered(); void on_actionZoomInX_triggered(); void on_actionZoomInY_triggered(); diff --git a/ui/qt/tcp_stream_dialog.ui b/ui/qt/tcp_stream_dialog.ui index cd49bb6f36..fd4800d895 100644 --- a/ui/qt/tcp_stream_dialog.ui +++ b/ui/qt/tcp_stream_dialog.ui @@ -99,6 +99,19 @@ </widget> </item> <item> + <spacer name="horizontalSpacer_1a"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> <widget class="QLabel" name="maWindowSizeLabel"> <property name="text"> <string>MA Window (s)</string> @@ -122,7 +135,7 @@ </widget> </item> <item> - <spacer name="horizontalSpacer"> + <spacer name="horizontalSpacer_1b"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> @@ -204,7 +217,85 @@ </widget> </item> <item> - <spacer name="horizontalSpacer_2"> + <spacer name="horizontalSpacer_2a"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QCheckBox" name="showSegLengthCheckBox"> + <property name="toolTip"> + <string>Display graph of Segment Length vs Time</string> + </property> + <property name="text"> + <string>Segment Length</string> + </property> + <property name="focusPolicy"> + <enum>Qt::TabFocus</enum> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="showThroughputCheckBox"> + <property name="toolTip"> + <string>Display graph of Mean Transmitted Bytes vs Time</string> + </property> + <property name="text"> + <string>Throughput</string> + </property> + <property name="focusPolicy"> + <enum>Qt::TabFocus</enum> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="showGoodputCheckBox"> + <property name="toolTip"> + <string>Display graph of Mean ACKed Bytes vs Time</string> + </property> + <property name="text"> + <string>Goodput</string> + </property> + <property name="focusPolicy"> + <enum>Qt::TabFocus</enum> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="showRcvWinCheckBox"> + <property name="toolTip"> + <string>Display graph of Receive Window Size vs Time</string> + </property> + <property name="text"> + <string>Rcv Win</string> + </property> + <property name="focusPolicy"> + <enum>Qt::TabFocus</enum> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="showBytesOutCheckBox"> + <property name="toolTip"> + <string>Display graph of Outstanding Bytes vs Time</string> + </property> + <property name="text"> + <string>Bytes Out</string> + </property> + <property name="focusPolicy"> + <enum>Qt::TabFocus</enum> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2b"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> diff --git a/ui/tap-tcp-stream.h b/ui/tap-tcp-stream.h index 5a2e3ec71b..452f20d1ea 100644 --- a/ui/tap-tcp-stream.h +++ b/ui/tap-tcp-stream.h @@ -128,7 +128,11 @@ tcp_seq_eq_or_after(guint32 s1, guint32 s2) { static inline int tcp_seq_after(guint32 s1, guint32 s2) { - return (s1 != s2) && !tcp_seq_before(s1, s2); + return (gint32)(s1 - s2) > 0; +} + +static inline int tcp_seq_before_or_eq(guint32 s1, guint32 s2) { + return !tcp_seq_after(s1, s2); } #ifdef __cplusplus |