1 /* tcp_stream_dialog.cpp
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later*/
9 #include "tcp_stream_dialog.h"
10 #include <ui_tcp_stream_dialog.h>
12 #include <algorithm> // for std::sort
13 #include <utility> // for std::pair
16 #include "epan/to_str.h"
18 #include "wsutil/str_util.h"
20 #include <wsutil/utf8_entities.h>
22 #include <ui/qt/utils/tango_colors.h>
23 #include <ui/qt/utils/qt_ui_utils.h>
24 #include "progress_frame.h"
25 #include "wireshark_application.h"
29 #include <QFileDialog>
31 #include <QPushButton>
36 // - Make the Help button work.
37 // - Show a message or disable the graph if we don't have any data.
38 // - Add a bytes in flight graph
39 // - Make the crosshairs tracer a vertical band?
40 // - Implement File->Copy
42 // - Make the first throughput MA period a dotted/dashed line?
43 // - Add range scroll bars?
44 // - ACK & RWIN segment ticks in tcptrace graph
45 // - Add missing elements (retrans, URG, SACK, etc) to tcptrace. It probably makes
46 // sense to subclass QCPGraph for this.
48 // The GTK+ version computes a 20 (or 21!) segment moving average. Comment
49 // out the line below to use that. By default we use a 1 second MA.
53 const int moving_avg_period_ = 20;
56 const QRgb graph_color_1 = tango_sky_blue_5;
57 const QRgb graph_color_2 = tango_butter_6;
58 const QRgb graph_color_3 = tango_chameleon_5;
59 const QRgb graph_color_4 = tango_scarlet_red_4;
60 const QRgb graph_color_5 = tango_scarlet_red_6;
62 // Size of selectable packet points in the base graph
63 const double pkt_point_size_ = 3.0;
65 // Don't accidentally zoom into a 1x1 rect if you happen to click on the graph
67 const int min_zoom_pixels_ = 20;
69 const QString average_throughput_label_ = QObject::tr("Average Throughput (bits/s)");
70 const QString round_trip_time_ms_label_ = QObject::tr("Round Trip Time (ms)");
71 const QString segment_length_label_ = QObject::tr("Segment Length (B)");
72 const QString sequence_number_label_ = QObject::tr("Sequence Number (B)");
73 const QString time_s_label_ = QObject::tr("Time (s)");
74 const QString window_size_label_ = QObject::tr("Window Size (B)");
76 TCPStreamDialog::TCPStreamDialog(QWidget *parent, capture_file *cf, tcp_graph_type graph_type) :
77 GeometryStateDialog(parent),
78 ui(new Ui::TCPStreamDialog),
81 ts_origin_conn_(true),
83 seq_origin_zero_(true),
94 zero_win_graph_(NULL),
102 num_sack_ranges_(-1),
105 struct segment current;
109 if (parent) loadGeometry(parent->width() * 2 / 3, parent->height() * 4 / 5);
110 setAttribute(Qt::WA_DeleteOnClose, true);
112 graph_.type = GRAPH_UNDEFINED;
113 set_address(&graph_.src_address, AT_NONE, 0, NULL);
115 set_address(&graph_.dst_address, AT_NONE, 0, NULL);
118 graph_.segments = NULL;
120 struct tcpheader *header = select_tcpip_session(cap_file_, ¤t);
122 done(QDialog::Rejected);
126 QComboBox *gtcb = ui->graphTypeComboBox;
127 gtcb->setUpdatesEnabled(false);
128 gtcb->addItem(ui->actionRoundTripTime->text(), GRAPH_RTT);
129 if (graph_type == GRAPH_RTT) graph_idx = gtcb->count() - 1;
130 gtcb->addItem(ui->actionThroughput->text(), GRAPH_THROUGHPUT);
131 if (graph_type == GRAPH_THROUGHPUT) graph_idx = gtcb->count() - 1;
132 gtcb->addItem(ui->actionStevens->text(), GRAPH_TSEQ_STEVENS);
133 if (graph_type == GRAPH_TSEQ_STEVENS) graph_idx = gtcb->count() - 1;
134 gtcb->addItem(ui->actionTcptrace->text(), GRAPH_TSEQ_TCPTRACE);
135 if (graph_type == GRAPH_TSEQ_TCPTRACE) graph_idx = gtcb->count() - 1;
136 gtcb->addItem(ui->actionWindowScaling->text(), GRAPH_WSCALE);
137 if (graph_type == GRAPH_WSCALE) graph_idx = gtcb->count() - 1;
138 gtcb->setUpdatesEnabled(true);
140 ui->dragRadioButton->setChecked(mouse_drags_);
142 ctx_menu_.addAction(ui->actionZoomIn);
143 ctx_menu_.addAction(ui->actionZoomInX);
144 ctx_menu_.addAction(ui->actionZoomInY);
145 ctx_menu_.addAction(ui->actionZoomOut);
146 ctx_menu_.addAction(ui->actionZoomOutX);
147 ctx_menu_.addAction(ui->actionZoomOutY);
148 ctx_menu_.addAction(ui->actionReset);
149 ctx_menu_.addSeparator();
150 ctx_menu_.addAction(ui->actionMoveRight10);
151 ctx_menu_.addAction(ui->actionMoveLeft10);
152 ctx_menu_.addAction(ui->actionMoveUp10);
153 ctx_menu_.addAction(ui->actionMoveDown10);
154 ctx_menu_.addAction(ui->actionMoveRight1);
155 ctx_menu_.addAction(ui->actionMoveLeft1);
156 ctx_menu_.addAction(ui->actionMoveUp1);
157 ctx_menu_.addAction(ui->actionMoveDown1);
158 ctx_menu_.addSeparator();
159 ctx_menu_.addAction(ui->actionNextStream);
160 ctx_menu_.addAction(ui->actionPreviousStream);
161 ctx_menu_.addAction(ui->actionSwitchDirection);
162 ctx_menu_.addAction(ui->actionGoToPacket);
163 ctx_menu_.addSeparator();
164 ctx_menu_.addAction(ui->actionDragZoom);
165 ctx_menu_.addAction(ui->actionToggleSequenceNumbers);
166 ctx_menu_.addAction(ui->actionToggleTimeOrigin);
167 ctx_menu_.addAction(ui->actionCrosshairs);
168 ctx_menu_.addSeparator();
169 ctx_menu_.addAction(ui->actionRoundTripTime);
170 ctx_menu_.addAction(ui->actionThroughput);
171 ctx_menu_.addAction(ui->actionStevens);
172 ctx_menu_.addAction(ui->actionTcptrace);
173 ctx_menu_.addAction(ui->actionWindowScaling);
175 memset (&graph_, 0, sizeof(graph_));
176 graph_.type = graph_type;
177 copy_address(&graph_.src_address, ¤t.ip_src);
178 graph_.src_port = current.th_sport;
179 copy_address(&graph_.dst_address, ¤t.ip_dst);
180 graph_.dst_port = current.th_dport;
181 graph_.stream = header->th_stream;
184 showWidgetsForGraphType();
186 ui->streamNumberSpinBox->blockSignals(true);
187 ui->streamNumberSpinBox->setMaximum(get_tcp_stream_count() - 1);
188 ui->streamNumberSpinBox->setValue(graph_.stream);
189 ui->streamNumberSpinBox->blockSignals(false);
192 ui->maWindowSizeSpinBox->blockSignals(true);
193 ui->maWindowSizeSpinBox->setDecimals(6);
194 ui->maWindowSizeSpinBox->setMinimum(0.000001);
195 ui->maWindowSizeSpinBox->setValue(ma_window_size_);
196 ui->maWindowSizeSpinBox->blockSignals(false);
199 // set which Throughput graphs are displayed by default
200 ui->showSegLengthCheckBox->blockSignals(true);
201 ui->showSegLengthCheckBox->setChecked(true);
202 ui->showSegLengthCheckBox->blockSignals(false);
204 ui->showThroughputCheckBox->blockSignals(true);
205 ui->showThroughputCheckBox->setChecked(true);
206 ui->showThroughputCheckBox->blockSignals(false);
208 // set which WScale graphs are displayed by default
209 ui->showRcvWinCheckBox->blockSignals(true);
210 ui->showRcvWinCheckBox->setChecked(true);
211 ui->showRcvWinCheckBox->blockSignals(false);
213 ui->showBytesOutCheckBox->blockSignals(true);
214 ui->showBytesOutCheckBox->setChecked(true);
215 ui->showBytesOutCheckBox->blockSignals(false);
217 QCustomPlot *sp = ui->streamPlot;
218 QCPPlotTitle *file_title = new QCPPlotTitle(sp, cf_get_display_name(cap_file_));
219 file_title->setFont(sp->xAxis->labelFont());
220 title_ = new QCPPlotTitle(sp);
221 sp->plotLayout()->insertRow(0);
222 sp->plotLayout()->addElement(0, 0, file_title);
223 sp->plotLayout()->insertRow(0);
224 sp->plotLayout()->addElement(0, 0, title_);
226 qreal pen_width = 0.5;
227 // Base Graph - enables selecting segments (both data and SACKs)
228 base_graph_ = sp->addGraph();
229 base_graph_->setPen(QPen(QBrush(graph_color_1), pen_width));
230 // Throughput Graph - rate of sent bytes
231 tput_graph_ = sp->addGraph(sp->xAxis, sp->yAxis2);
232 tput_graph_->setPen(QPen(QBrush(graph_color_2), pen_width));
233 tput_graph_->setLineStyle(QCPGraph::lsStepLeft);
234 // Goodput Graph - rate of ACKed bytes
235 goodput_graph_ = sp->addGraph(sp->xAxis, sp->yAxis2);
236 goodput_graph_->setPen(QPen(QBrush(graph_color_3), pen_width));
237 goodput_graph_->setLineStyle(QCPGraph::lsStepLeft);
238 // Seg Graph - displays forward data segments on tcptrace graph
239 seg_graph_ = sp->addGraph();
240 seg_graph_->setErrorType(QCPGraph::etValue);
241 seg_graph_->setLineStyle(QCPGraph::lsNone);
242 seg_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDot, Qt::transparent, 0));
243 seg_graph_->setErrorPen(QPen(QBrush(graph_color_1), pen_width));
244 seg_graph_->setErrorBarSkipSymbol(false); // draw error spine as single line
245 seg_graph_->setErrorBarSize(pkt_point_size_);
246 // Ack Graph - displays ack numbers from reverse packets
247 ack_graph_ = sp->addGraph();
248 ack_graph_->setPen(QPen(QBrush(graph_color_2), pen_width));
249 ack_graph_->setLineStyle(QCPGraph::lsStepLeft);
250 // Sack Graph - displays highest number (most recent) SACK block
251 sack_graph_ = sp->addGraph();
252 sack_graph_->setErrorType(QCPGraph::etValue);
253 sack_graph_->setLineStyle(QCPGraph::lsNone);
254 sack_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDot, Qt::transparent, 0));
255 sack_graph_->setErrorPen(QPen(QBrush(graph_color_4), pen_width));
256 sack_graph_->setErrorBarSkipSymbol(false);
257 sack_graph_->setErrorBarSize(0.0);
258 // Sack Graph 2 - displays subsequent SACK blocks
259 sack2_graph_ = sp->addGraph();
260 sack2_graph_->setErrorType(QCPGraph::etValue);
261 sack2_graph_->setLineStyle(QCPGraph::lsNone);
262 sack2_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDot, Qt::transparent, 0));
263 sack2_graph_->setErrorPen(QPen(QBrush(graph_color_5), pen_width));
264 sack2_graph_->setErrorBarSkipSymbol(false);
265 sack2_graph_->setErrorBarSize(0.0);
266 // RWin graph - displays upper extent of RWIN advertised on reverse packets
267 rwin_graph_ = sp->addGraph();
268 rwin_graph_->setPen(QPen(QBrush(graph_color_3), pen_width));
269 rwin_graph_->setLineStyle(QCPGraph::lsStepLeft);
270 // Duplicate ACK Graph - displays duplicate ack ticks
271 // QCustomPlot doesn't have QCPScatterStyle::ssTick so we have to make our own.
273 #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
274 tick_len *= devicePixelRatio();
276 QPixmap da_tick_pm = QPixmap(1, tick_len * 2);
277 da_tick_pm.fill(Qt::transparent);
278 QPainter painter(&da_tick_pm);
280 da_tick_pen.setColor(graph_color_2);
281 da_tick_pen.setWidthF(pen_width);
282 painter.setPen(da_tick_pen);
283 painter.drawLine(0, tick_len, 0, tick_len * 2);
284 dup_ack_graph_ = sp->addGraph();
285 dup_ack_graph_->setLineStyle(QCPGraph::lsNone);
286 QCPScatterStyle da_ss = QCPScatterStyle(QCPScatterStyle::ssPixmap, graph_color_2, 0);
287 da_ss.setPixmap(da_tick_pm);
288 dup_ack_graph_->setScatterStyle(da_ss);
289 // Zero Window Graph - displays zero window crosses (x)
290 zero_win_graph_ = sp->addGraph();
291 zero_win_graph_->setLineStyle(QCPGraph::lsNone);
292 zero_win_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCross, graph_color_1, 5));
294 tracer_ = new QCPItemTracer(sp);
295 sp->addItem(tracer_);
297 // Triggers fillGraph() [ UNLESS the index is already graph_idx!! ]
298 if (graph_idx != ui->graphTypeComboBox->currentIndex())
299 // changing the current index will call fillGraph
300 ui->graphTypeComboBox->setCurrentIndex(graph_idx);
302 // the current index is what we want - so fillGraph() manually
305 sp->setMouseTracking(true);
307 sp->yAxis->setLabelColor(QColor(graph_color_1));
308 sp->yAxis->setTickLabelColor(QColor(graph_color_1));
310 tracer_->setVisible(false);
311 toggleTracerStyle(true);
313 QPushButton *save_bt = ui->buttonBox->button(QDialogButtonBox::Save);
314 save_bt->setText(tr("Save As" UTF8_HORIZONTAL_ELLIPSIS));
316 QPushButton *close_bt = ui->buttonBox->button(QDialogButtonBox::Close);
318 close_bt->setDefault(true);
321 ProgressFrame::addToButtonBox(ui->buttonBox, parent);
323 connect(sp, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(graphClicked(QMouseEvent*)));
324 connect(sp, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMoved(QMouseEvent*)));
325 connect(sp, SIGNAL(mouseRelease(QMouseEvent*)), this, SLOT(mouseReleased(QMouseEvent*)));
326 connect(sp, SIGNAL(axisClick(QCPAxis*,QCPAxis::SelectablePart,QMouseEvent*)),
327 this, SLOT(axisClicked(QCPAxis*,QCPAxis::SelectablePart,QMouseEvent*)));
328 connect(sp->yAxis, SIGNAL(rangeChanged(QCPRange)), this, SLOT(transformYRange(QCPRange)));
329 disconnect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
330 this->setResult(QDialog::Accepted);
333 TCPStreamDialog::~TCPStreamDialog()
338 void TCPStreamDialog::showEvent(QShowEvent *)
343 void TCPStreamDialog::keyPressEvent(QKeyEvent *event)
345 int pan_pixels = event->modifiers() & Qt::ShiftModifier ? 1 : 10;
347 QWidget* focusWidget = QApplication::focusWidget();
349 // Block propagation of "Enter" key when focus is not default (e.g. SpinBox)
350 // [ Note that if focus was on, e.g. Close button, event would never reach
352 if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) &&
353 focusWidget !=NULL && focusWidget != ui->streamPlot) {
355 // reset focus to default, and accept event
356 ui->streamPlot->setFocus();
361 // XXX - This differs from the main window but matches other applications (e.g. Mozilla and Safari)
362 switch(event->key()) {
364 case Qt::Key_Underscore: // Shifted minus on U.S. keyboards
365 case Qt::Key_O: // GTK+
369 case Qt::Key_Equal: // Unshifted plus on U.S. keyboards
370 case Qt::Key_I: // GTK+
373 case Qt::Key_X: // Zoom X axis only
374 if(event->modifiers() & Qt::ShiftModifier){
375 zoomXAxis(false); // upper case X -> Zoom out
377 zoomXAxis(true); // lower case x -> Zoom in
380 case Qt::Key_Y: // Zoom Y axis only
381 if(event->modifiers() & Qt::ShiftModifier){
382 zoomYAxis(false); // upper case Y -> Zoom out
384 zoomYAxis(true); // lower case y -> Zoom in
389 panAxes(pan_pixels, 0);
393 panAxes(-1 * pan_pixels, 0);
397 panAxes(0, pan_pixels);
401 panAxes(0, -1 * pan_pixels);
409 case Qt::Key_ParenRight: // Shifted 0 on U.S. keyboards
416 on_actionNextStream_triggered();
418 case Qt::Key_PageDown:
419 on_actionPreviousStream_triggered();
423 on_actionSwitchDirection_triggered();
426 on_actionGoToPacket_triggered();
429 on_actionToggleSequenceNumbers_triggered();
432 on_actionToggleTimeOrigin_triggered();
435 on_actionDragZoom_triggered();
439 on_actionRoundTripTime_triggered();
442 on_actionThroughput_triggered();
445 on_actionStevens_triggered();
448 on_actionTcptrace_triggered();
451 on_actionWindowScaling_triggered();
453 // Alas, there is no Blade Runner-style Qt::Key_Enhance
456 QDialog::keyPressEvent(event);
459 void TCPStreamDialog::mousePressEvent(QMouseEvent *event)
461 // if no-one else wants the event, then this is a click on blank space.
462 // Use this opportunity to set focus back to default, and accept event.
463 ui->streamPlot->setFocus();
467 void TCPStreamDialog::mouseReleaseEvent(QMouseEvent *event)
469 mouseReleased(event);
472 void TCPStreamDialog::findStream()
474 QCustomPlot *sp = ui->streamPlot;
476 disconnect(sp, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMoved(QMouseEvent*)));
477 // if streamNumberSpinBox has focus -
478 // first clear focus, then disable/enable, then restore focus
479 bool spin_box_focused = ui->streamNumberSpinBox->hasFocus();
480 if (spin_box_focused)
481 ui->streamNumberSpinBox->clearFocus();
482 ui->streamNumberSpinBox->setEnabled(false);
483 graph_segment_list_free(&graph_);
484 graph_segment_list_get(cap_file_, &graph_, TRUE);
485 ui->streamNumberSpinBox->setEnabled(true);
486 if (spin_box_focused)
487 ui->streamNumberSpinBox->setFocus();
489 connect(sp, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMoved(QMouseEvent*)));
492 void TCPStreamDialog::fillGraph(bool reset_axes, bool set_focus)
494 QCustomPlot *sp = ui->streamPlot;
496 if (sp->graphCount() < 1) return;
498 base_graph_->setLineStyle(QCPGraph::lsNone);
499 tracer_->setGraph(NULL);
501 // base_graph_ is always visible.
502 for (int i = 0; i < sp->graphCount(); i++) {
503 sp->graph(i)->clearData();
504 sp->graph(i)->setVisible(i == 0 ? true : false);
507 base_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, pkt_point_size_));
509 sp->xAxis->setLabel(time_s_label_);
510 sp->xAxis->setNumberFormat("gb");
511 // Use enough precision to mark microseconds
512 // when zooming in on a <100s capture
513 sp->xAxis->setNumberPrecision(8);
514 sp->yAxis->setNumberFormat("f");
515 sp->yAxis->setNumberPrecision(0);
516 sp->yAxis2->setVisible(false);
517 sp->yAxis2->setLabel(QString());
520 QString dlg_title = QString(tr("No Capture Data"));
521 setWindowTitle(dlg_title);
522 title_->setText(dlg_title);
523 sp->setEnabled(false);
524 sp->yAxis->setLabel(QString());
532 guint64 bytes_fwd = 0;
533 guint64 bytes_rev = 0;
537 time_stamp_map_.clear();
538 for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
539 // NOTE - adding both forward and reverse packets to time_stamp_map_
540 // so that both data and acks are selectable
541 // (this is important especially in selecting particular SACK pkts)
543 if (!compareHeaders(seg)) {
544 bytes_rev += seg->th_seglen;
546 // only insert reverse packets if SACK present
547 insert = (seg->num_sack_ranges != 0);
549 bytes_fwd += seg->th_seglen;
552 double ts = seg->rel_secs + seg->rel_usecs / 1000000.0;
554 if (ts_origin_conn_) ts_offset_ = ts;
555 if (seq_origin_zero_) {
556 if (compareHeaders(seg))
557 seq_offset_ = seg->th_seq;
559 seq_offset_ = seg->th_ack;
564 time_stamp_map_.insertMulti(ts - ts_offset_, seg);
568 switch (graph_.type) {
569 case GRAPH_TSEQ_STEVENS:
572 case GRAPH_TSEQ_TCPTRACE:
575 case GRAPH_THROUGHPUT:
587 sp->setEnabled(true);
589 stream_desc_ = tr("%1 %2 pkts, %3 %4 %5 pkts, %6 ")
590 .arg(UTF8_RIGHTWARDS_ARROW)
591 .arg(gchar_free_to_qstring(format_size(pkts_fwd, format_size_unit_none|format_size_prefix_si)))
592 .arg(gchar_free_to_qstring(format_size(bytes_fwd, format_size_unit_bytes|format_size_prefix_si)))
593 .arg(UTF8_LEFTWARDS_ARROW)
594 .arg(gchar_free_to_qstring(format_size(pkts_rev, format_size_unit_none|format_size_prefix_si)))
595 .arg(gchar_free_to_qstring(format_size(bytes_rev, format_size_unit_bytes|format_size_prefix_si)));
601 // Throughput and Window Scale graphs can hide base_graph_
602 if (base_graph_->visible())
603 tracer_->setGraph(base_graph_);
605 // XXX QCustomPlot doesn't seem to draw any sort of focus indicator.
610 void TCPStreamDialog::showWidgetsForGraphType()
612 if (graph_.type == GRAPH_RTT) {
613 ui->bySeqNumberCheckBox->setVisible(true);
615 ui->bySeqNumberCheckBox->setVisible(false);
617 if (graph_.type == GRAPH_THROUGHPUT) {
619 ui->maWindowSizeLabel->setVisible(true);
620 ui->maWindowSizeSpinBox->setVisible(true);
622 ui->maWindowSizeLabel->setVisible(false);
623 ui->maWindowSizeSpinBox->setVisible(false);
625 ui->showSegLengthCheckBox->setVisible(true);
626 ui->showThroughputCheckBox->setVisible(true);
627 ui->showGoodputCheckBox->setVisible(true);
629 ui->maWindowSizeLabel->setVisible(false);
630 ui->maWindowSizeSpinBox->setVisible(false);
631 ui->showSegLengthCheckBox->setVisible(false);
632 ui->showThroughputCheckBox->setVisible(false);
633 ui->showGoodputCheckBox->setVisible(false);
636 if (graph_.type == GRAPH_TSEQ_TCPTRACE) {
637 ui->selectSACKsCheckBox->setVisible(true);
639 ui->selectSACKsCheckBox->setVisible(false);
642 if (graph_.type == GRAPH_WSCALE) {
643 ui->showRcvWinCheckBox->setVisible(true);
644 ui->showBytesOutCheckBox->setVisible(true);
646 ui->showRcvWinCheckBox->setVisible(false);
647 ui->showBytesOutCheckBox->setVisible(false);
651 void TCPStreamDialog::zoomAxes(bool in)
653 QCustomPlot *sp = ui->streamPlot;
654 double h_factor = sp->axisRect()->rangeZoomFactor(Qt::Horizontal);
655 double v_factor = sp->axisRect()->rangeZoomFactor(Qt::Vertical);
658 h_factor = pow(h_factor, -1);
659 v_factor = pow(v_factor, -1);
662 sp->xAxis->scaleRange(h_factor, sp->xAxis->range().center());
663 sp->yAxis->scaleRange(v_factor, sp->yAxis->range().center());
667 void TCPStreamDialog::zoomXAxis(bool in)
669 QCustomPlot *sp = ui->streamPlot;
670 double h_factor = sp->axisRect()->rangeZoomFactor(Qt::Horizontal);
673 h_factor = pow(h_factor, -1);
676 sp->xAxis->scaleRange(h_factor, sp->xAxis->range().center());
680 void TCPStreamDialog::zoomYAxis(bool in)
682 QCustomPlot *sp = ui->streamPlot;
683 double v_factor = sp->axisRect()->rangeZoomFactor(Qt::Vertical);
686 v_factor = pow(v_factor, -1);
689 sp->yAxis->scaleRange(v_factor, sp->yAxis->range().center());
693 void TCPStreamDialog::panAxes(int x_pixels, int y_pixels)
695 QCustomPlot *sp = ui->streamPlot;
699 h_pan = sp->xAxis->range().size() * x_pixels / sp->xAxis->axisRect()->width();
700 v_pan = sp->yAxis->range().size() * y_pixels / sp->yAxis->axisRect()->height();
701 // The GTK+ version won't pan unless we're zoomed. Should we do the same here?
703 sp->xAxis->moveRange(h_pan);
707 sp->yAxis->moveRange(v_pan);
712 void TCPStreamDialog::resetAxes()
714 QCustomPlot *sp = ui->streamPlot;
716 y_axis_xfrm_.reset();
717 double pixel_pad = 10.0; // per side
719 sp->rescaleAxes(true);
720 // tput_graph_->rescaleValueAxis(false, true);
721 // base_graph_->rescaleAxes(false, true);
722 // for (int i = 0; i < sp->graphCount(); i++) {
723 // sp->graph(i)->rescaleValueAxis(false, true);
726 double axis_pixels = sp->xAxis->axisRect()->width();
727 sp->xAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, sp->xAxis->range().center());
729 if (sp->yAxis2->visible()) {
730 double ratio = sp->yAxis2->range().size() / sp->yAxis->range().size();
731 y_axis_xfrm_.translate(0.0, sp->yAxis2->range().lower - (sp->yAxis->range().lower * ratio));
732 y_axis_xfrm_.scale(1.0, ratio);
735 axis_pixels = sp->yAxis->axisRect()->height();
736 sp->yAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, sp->yAxis->range().center());
741 void TCPStreamDialog::fillStevens()
743 QString dlg_title = QString(tr("Sequence Numbers (Stevens)")) + streamDescription();
744 setWindowTitle(dlg_title);
745 title_->setText(dlg_title);
747 QCustomPlot *sp = ui->streamPlot;
748 sp->yAxis->setLabel(sequence_number_label_);
750 // True Stevens-style graphs don't have lines but I like them - gcc
751 base_graph_->setLineStyle(QCPGraph::lsStepLeft);
753 QVector<double> rel_time, seq;
754 for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
755 if (!compareHeaders(seg)) {
759 double ts = seg->rel_secs + seg->rel_usecs / 1000000.0;
760 rel_time.append(ts - ts_offset_);
761 seq.append(seg->th_seq - seq_offset_);
763 base_graph_->setData(rel_time, seq);
766 void TCPStreamDialog::fillTcptrace()
768 QString dlg_title = QString(tr("Sequence Numbers (tcptrace)")) + streamDescription();
769 setWindowTitle(dlg_title);
770 title_->setText(dlg_title);
772 bool allow_sack_select = ui->selectSACKsCheckBox->isChecked();
774 QCustomPlot *sp = ui->streamPlot;
775 sp->yAxis->setLabel(sequence_number_label_);
777 base_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDot));
779 seg_graph_->setVisible(true);
780 ack_graph_->setVisible(true);
781 sack_graph_->setVisible(true);
782 sack2_graph_->setVisible(true);
783 rwin_graph_->setVisible(true);
784 dup_ack_graph_->setVisible(true);
785 zero_win_graph_->setVisible(true);
787 QVector<double> pkt_time, pkt_seqnums;
788 QVector<double> sb_time, sb_center, sb_span;
789 QVector<double> ackrwin_time, ack, rwin;
790 QVector<double> sack_time, sack_center, sack_span;
791 QVector<double> sack2_time, sack2_center, sack2_span;
792 QVector<double> dup_ack_time, dup_ack;
793 QVector<double> zero_win_time, zero_win;
795 for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
796 double ts = (seg->rel_secs + seg->rel_usecs / 1000000.0) - ts_offset_;
797 if (compareHeaders(seg)) {
798 double half = seg->th_seglen / 2.0;
799 double center = seg->th_seq - seq_offset_ + half;
801 // Add forward direction to base_graph_ (to select data packets)
802 // Forward direction: seq + data
804 pkt_seqnums.append(center);
806 // QCP doesn't have a segment graph type. For now, fake
807 // it with error bars.
808 if (seg->th_seglen > 0) {
810 sb_center.append(center);
811 sb_span.append(half);
814 // Look for zero window sizes.
815 // Should match the TCP_A_ZERO_WINDOW test in packet-tcp.c.
816 if (seg->th_win == 0 && (seg->th_flags & (TH_RST|TH_FIN|TH_SYN)) == 0) {
817 zero_win_time.append(ts);
818 zero_win.append(center);
821 // Reverse direction: ACK + RWIN
822 if (! (seg->th_flags & TH_ACK)) {
823 // SYNs and RSTs do not necessarily have ACKs
826 double ackno = seg->th_ack - seq_offset_;
827 // add SACK segments to sack, sack2, and selectable packet graph
828 for (int i = 0; i < seg->num_sack_ranges; ++i) {
829 double half = seg->sack_right_edge[i] - seg->sack_left_edge[i];
831 double center = seg->sack_left_edge[i] - seq_offset_ + half;
833 sack_time.append(ts);
834 sack_center.append(center);
835 sack_span.append(half);
836 if (allow_sack_select) {
838 pkt_seqnums.append(center);
841 sack2_time.append(ts);
842 sack2_center.append(center);
843 sack2_span.append(half);
846 // If ackno is the same as our last one mark it as a duplicate.
847 if (ack.size() > 0 && ack.last() == ackno) {
848 dup_ack_time.append(ts);
849 dup_ack.append(ackno);
851 // Also add reverse packets to the ack_graph_
852 ackrwin_time.append(ts);
854 rwin.append(ackno + seg->th_win);
857 base_graph_->setData(pkt_time, pkt_seqnums);
858 seg_graph_->setDataValueError(sb_time, sb_center, sb_span);
859 ack_graph_->setData(ackrwin_time, ack);
860 sack_graph_->setDataValueError(sack_time, sack_center, sack_span);
861 sack2_graph_->setDataValueError(sack2_time, sack2_center, sack2_span);
862 rwin_graph_->setData(ackrwin_time, rwin);
863 dup_ack_graph_->setData(dup_ack_time, dup_ack);
864 zero_win_graph_->setData(zero_win_time, zero_win);
867 // If the current implementation of incorporating SACKs in goodput calc
868 // is slow, comment out the following line to ignore SACKs in goodput calc.
869 #define USE_SACKS_IN_GOODPUT_CALC
871 #ifdef USE_SACKS_IN_GOODPUT_CALC
872 // to incorporate SACKED segments into goodput calculation,
873 // need to keep track of all the SACK blocks we haven't yet
875 // I expect this to be _relatively_ small, so using vector to store
876 // them. If this performs badly, it can be refactored with std::list
878 typedef std::pair<guint32, guint32> sack_t;
879 typedef std::vector<sack_t> sack_list_t;
880 static inline bool compare_sack(const sack_t& s1, const sack_t& s2) {
881 return tcp_seq_before(s1.first, s2.first);
884 // Helper function to adjust an acked seglen for goodput:
885 // - removes previously sacked ranges from seglen (and from old_sacks),
886 // - adds newly sacked ranges to seglen (and to old_sacks)
888 goodput_adjust_for_sacks(guint32 *seglen, guint32 last_ack,
889 sack_list_t& new_sacks, guint8 num_sack_ranges,
890 sack_list_t& old_sacks) {
892 // Step 1 - For any old_sacks acked by last_ack,
893 // delete their acked length from seglen,
894 // and remove the sack block (or portion)
895 // from (sorted) old_sacks.
896 sack_list_t::iterator unacked = old_sacks.begin();
897 while (unacked != old_sacks.end()) {
898 // break on first sack not fully acked
899 if (tcp_seq_before(last_ack, unacked->second)) {
900 if (tcp_seq_after(last_ack, unacked->first)) {
901 // partially acked - modify to remove acked part
902 *seglen -= (last_ack - unacked->first);
903 unacked->first = last_ack;
907 // remove fully acked sacks from seglen and move on
908 // (we'll actually remove from the list when loop is done)
909 *seglen -= (unacked->second - unacked->first);
912 // actually remove all fully acked sacks from old_sacks list
913 if (unacked != old_sacks.begin())
914 old_sacks.erase(old_sacks.begin(), unacked);
916 // Step 2 - for any new_sacks that precede last_ack,
917 // ignore them. (These would generally be SACKed dup-acks of
918 // a retransmitted seg).
919 // [ in the unlikely case that any new SACK straddles last_ack,
920 // the sack block will be modified to remove the acked portion ]
921 int next_new_idx = 0;
922 while (next_new_idx < num_sack_ranges) {
923 if (tcp_seq_before(last_ack, new_sacks[next_new_idx].second)) {
924 // if a new SACK block is unacked by its own packet, then it's
925 // likely fully unacked, but let's check for partial ack anyway,
926 // and truncate the SACK so that it's fully unacked:
927 if (tcp_seq_before(new_sacks[next_new_idx].first, last_ack))
928 new_sacks[next_new_idx].first = last_ack;
934 // Step 3 - for any byte ranges in remaining new_sacks
935 // that don't already exist in old_sacks, add
936 // their length to seglen
937 // and add that range (by extension, if possible) to
938 // the list of old_sacks.
940 sack_list_t::iterator next_old = old_sacks.begin();
942 while (next_new_idx < num_sack_ranges &&
943 next_old != old_sacks.end()) {
944 sack_t* next_new = &new_sacks[next_new_idx];
946 // Assumptions / Invariants:
947 // - new and old lists are sorted
948 // - span of leftmost to rightmost endpt. is less than half uint32 range
949 // [ensures transitivity - e.g. before(a,b) and before(b,c) ==> before(a,c)]
950 // - all SACKs are non-empty (sack.left before sack.right)
951 // - adjacent SACKs in list always have a gap between them
952 // (sack.right before next_sack.left)
954 // Given these assumptions, and noting that there are only three
955 // possible comparisons for a pair of points (before/equal/after),
956 // there are only a few possible relative configurations
957 // of next_old and next_new:
963 // 3. [----------------)
964 // 4. [---------------------)
965 // 5. [----------------------------)
967 // 7. [-------------)
968 // 8. [--------------------)
971 // 11. [---------------)
975 // Case 1: end of next_old is before beginning of next_new
977 // [-------------) ... <end>
979 // 1. [---) ... <end>
980 if (tcp_seq_before(next_old->second, next_new->first)) {
982 // advance to the next sack in old_sacks
984 // retry from the top
988 // Case 13: end of next_new is before beginning of next_old
990 // [-------------) ... <end>
992 // 13. [--) ... <end>
993 if (tcp_seq_before(next_new->second, next_old->first)) {
995 // add then entire length of next_new into seglen
996 *seglen += (next_new->second - next_new->first);
997 // insert next_new before next_old in old_sacks
998 // (be sure to save and restore next_old iterator around insert!)
999 int next_old_idx = int(next_old - old_sacks.begin());
1000 old_sacks.insert(next_old, *next_new);
1001 next_old = old_sacks.begin() + next_old_idx + 1;
1002 // advance to the next remaining sack in new_sacks
1004 // retry from the top
1008 // Remaining possible configurations:
1013 // 3. [----------------)
1014 // 4. [---------------------)
1015 // 5. [----------------------------)
1017 // 7. [-------------)
1018 // 8. [--------------------)
1021 // 11. [---------------)
1024 // Cases 2,3,6,9: end of next_old is before end of next_new
1029 // 3. [----------------)
1033 // until end of next_old is equal or after end of next_new,
1034 // repeatedly extend next_old, coalescing with next_next_old
1035 // if necessary. (and add extended bytes to seglen)
1036 while (tcp_seq_before(next_old->second, next_new->second)) {
1037 // if end of next_new doesn't collide with start of next_next_old,
1038 if (((next_old+1) == old_sacks.end()) ||
1039 tcp_seq_before(next_new->second, (next_old + 1)->first)) {
1040 // extend end of next_old up to end of next_new,
1041 // adding extended bytes to seglen
1042 *seglen += (next_new->second - next_old->second);
1043 next_old->second = next_new->second;
1045 // otherwise, coalesce next_old with next_next_old
1047 // add bytes to close gap between sacks to seglen
1048 *seglen += ((next_old + 1)->first - next_old->second);
1049 // coalesce next_next_old into next_old
1050 next_old->second = (next_old + 1)->second;
1051 old_sacks.erase(next_old + 1);
1054 // This operation turns:
1055 // Cases 2 and 3 into Case 4 or 5
1056 // Case 6 into Case 7
1057 // Case 9 into Case 10
1060 // Remaining possible configurations:
1064 // 4. [---------------------)
1065 // 5. [----------------------------)
1066 // 7. [-------------)
1067 // 8. [--------------------)
1069 // 11. [---------------)
1072 // Cases 10,11,12: start of next_new is before start of next_old
1077 // 11. [---------------)
1079 if (tcp_seq_before(next_new->first, next_old->first)) {
1081 // add the unaccounted bytes in next_new to seglen
1082 *seglen += (next_old->first - next_new->first);
1083 // then pull the start of next_old back to the start of next_new
1084 next_old->first = next_new->first;
1086 // This operation turns:
1087 // Case 10 into Case 7
1088 // Cases 11 and 12 into Case 8
1091 // Remaining possible configurations:
1095 // 4. [---------------------)
1096 // 5. [----------------------------)
1097 // 7. [-------------)
1098 // 8. [--------------------)
1100 // In these cases, the bytes in next_new are fully accounted
1101 // by the bytes in next_old, so we can move on to look at
1102 // the next sack block in new_sacks
1105 // Conditions for leaving loop:
1106 // - we processed all remaining new_sacks - nothing left to do
1107 // (next_new_idx == num_sack_ranges)
1109 // - all remaining new_sacks start at least one byte after
1110 // the rightmost edge of the last old_sack
1111 // (meaning we can just add the remaining new_sacks to old_sacks list,
1112 // and add them directly to the goodput seglen)
1113 while (next_new_idx < num_sack_ranges) {
1114 sack_t* next_new = &new_sacks[next_new_idx];
1115 *seglen += (next_new->second - next_new->first);
1116 old_sacks.push_back(*next_new);
1120 #endif // USE_SACKS_IN_GOODPUT_CALC
1122 void TCPStreamDialog::fillThroughput()
1124 QString dlg_title = QString(tr("Throughput")) + streamDescription();
1126 dlg_title.append(tr(" (MA)"));
1128 dlg_title.append(QString(tr(" (%1 Segment MA)")).arg(moving_avg_period_));
1130 setWindowTitle(dlg_title);
1131 title_->setText(dlg_title);
1133 QCustomPlot *sp = ui->streamPlot;
1134 sp->yAxis->setLabel(segment_length_label_);
1135 sp->yAxis2->setLabel(average_throughput_label_);
1136 sp->yAxis2->setLabelColor(QColor(graph_color_2));
1137 sp->yAxis2->setTickLabelColor(QColor(graph_color_2));
1138 sp->yAxis2->setVisible(true);
1140 base_graph_->setVisible(ui->showSegLengthCheckBox->isChecked());
1141 tput_graph_->setVisible(ui->showThroughputCheckBox->isChecked());
1142 goodput_graph_->setVisible(ui->showGoodputCheckBox->isChecked());
1145 if (!graph_.segments) {
1147 if (!graph_.segments || !graph_.segments->next) {
1149 dlg_title.append(tr(" [not enough data]"));
1153 QVector<double> seg_rel_times, ack_rel_times;
1154 QVector<double> seg_lens, ack_lens;
1155 QVector<double> tput_times, gput_times;
1156 QVector<double> tputs, gputs;
1157 int oldest_seg = 0, oldest_ack = 0;
1158 guint64 seg_sum = 0, ack_sum = 0;
1161 #ifdef USE_SACKS_IN_GOODPUT_CALC
1162 // to incorporate SACKED segments into goodput calculation,
1163 // need to keep track of all the SACK blocks we haven't yet
1165 sack_list_t old_sacks, new_sacks;
1166 new_sacks.reserve(MAX_TCP_SACK_RANGES);
1167 // statically allocate current_sacks vector
1168 // [ std::array might be better, but that is C++11 ]
1169 for (int i = 0; i < MAX_TCP_SACK_RANGES; ++i) {
1170 new_sacks.push_back(sack_t(0,0));
1172 old_sacks.reserve(2*MAX_TCP_SACK_RANGES);
1173 #endif // USE_SACKS_IN_GOODPUT_CALC
1175 // need first acked sequence number to jump-start
1176 // computation of acked bytes per packet
1177 guint32 last_ack = 0;
1178 for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
1179 // first reverse packet with ACK flag tells us first acked sequence #
1180 if (!compareHeaders(seg) && (seg->th_flags & TH_ACK)) {
1181 last_ack = seg->th_ack;
1185 // Financial charts don't show MA data until a full period has elapsed.
1186 // [ NOTE - this is because they assume that there's old data that they
1187 // don't have access to - but in our case we know that there's NO
1188 // data prior to the first packet in the stream - so it's fine to
1189 // spit out the MA immediately... ]
1190 // The Rosetta Code MA examples start spitting out values immediately.
1191 // For now use not-really-correct initial values just to keep our vector
1192 // lengths the same.
1194 // NOTE that for the time-based MA case, you certainly can start with the
1196 for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
1198 for (struct segment *seg = graph_.segments->next; seg != NULL; seg = seg->next) {
1200 bool is_forward_seg = compareHeaders(seg);
1201 QVector<double>& r_pkt_times = is_forward_seg ? seg_rel_times : ack_rel_times;
1202 QVector<double>& r_lens = is_forward_seg ? seg_lens : ack_lens;
1203 QVector<double>& r_Xput_times = is_forward_seg ? tput_times : gput_times;
1204 QVector<double>& r_Xputs = is_forward_seg ? tputs : gputs;
1205 int& r_oldest = is_forward_seg ? oldest_seg : oldest_ack;
1206 guint64& r_sum = is_forward_seg ? seg_sum : ack_sum;
1208 double ts = (seg->rel_secs + seg->rel_usecs / 1000000.0) - ts_offset_;
1210 if (is_forward_seg) {
1211 seglen = seg->th_seglen;
1213 if ((seg->th_flags & TH_ACK) &&
1214 tcp_seq_eq_or_after(seg->th_ack, last_ack)) {
1215 seglen = seg->th_ack - last_ack;
1216 last_ack = seg->th_ack;
1217 #ifdef USE_SACKS_IN_GOODPUT_CALC
1218 // copy any sack_ranges into new_sacks, and sort.
1219 for(int i = 0; i < seg->num_sack_ranges; ++i) {
1220 new_sacks[i].first = seg->sack_left_edge[i];
1221 new_sacks[i].second = seg->sack_right_edge[i];
1223 std::sort(new_sacks.begin(),
1224 new_sacks.begin() + seg->num_sack_ranges,
1227 // adjust the seglen based on new and old sacks,
1228 // and update the old_sacks list
1229 goodput_adjust_for_sacks(&seglen, last_ack,
1230 new_sacks, seg->num_sack_ranges,
1232 #endif // USE_SACKS_IN_GOODPUT_CALC
1238 r_pkt_times.append(ts);
1239 r_lens.append(seglen);
1242 while (r_oldest < r_pkt_times.size() && ts - r_pkt_times[r_oldest] > ma_window_size_) {
1243 r_sum -= r_lens[r_oldest];
1244 // append points where a packet LEAVES the MA window
1245 // (as well as, below, where they ENTER the MA window)
1246 r_Xputs.append(r_sum * 8.0 / ma_window_size_);
1247 r_Xput_times.append(r_pkt_times[r_oldest] + ma_window_size_);
1251 if (r_lens.size() > moving_avg_period_) {
1252 r_sum -= r_lens[r_oldest];
1257 // av_Xput computes Xput, i.e.:
1258 // throughput for forward packets
1259 // goodput for reverse packets
1263 // for time-based MA, delta_t is constant
1264 av_Xput = r_sum * 8.0 / ma_window_size_;
1268 dtime = ts - r_pkt_times[r_oldest-1];
1270 av_Xput = r_sum * 8.0 / dtime;
1276 // Add a data point only if our time window has advanced. Otherwise
1277 // update the most recent point. (We might want to show a warning
1278 // for out-of-order packets.)
1279 if (r_Xput_times.size() > 0 && ts <= r_Xput_times.last()) {
1280 r_Xputs[r_Xputs.size() - 1] = av_Xput;
1282 r_Xputs.append(av_Xput);
1283 r_Xput_times.append(ts);
1286 base_graph_->setData(seg_rel_times, seg_lens);
1287 tput_graph_->setData(tput_times, tputs);
1288 goodput_graph_->setData(gput_times, gputs);
1291 // rtt_selectively_ack_range:
1292 // "Helper" function for fillRoundTripTime
1293 // given an rtt_unack list, two pointers to a range of segments in the list,
1294 // and the [left,right) edges of a SACK block, selectively ACKs the range
1295 // from "begin" to "end" - possibly splitting one segment in the range
1296 // into two (and relinking the new segment in order after the first)
1299 // "begin must be non-NULL
1300 // "begin" must precede "end" (or "end" must be NULL)
1301 // [ there are minor optimizations that could be added if
1302 // the range from "begin" to "end" are in sequence number order.
1303 // (this function would preserve that as an invariant). ]
1304 static struct rtt_unack *
1305 rtt_selectively_ack_range(QVector<double>& x_vals, bool bySeqNumber,
1306 QVector<double>& rtt,
1307 struct rtt_unack **list,
1308 struct rtt_unack *begin, struct rtt_unack *end,
1309 unsigned int left, unsigned int right, double rt_val) {
1310 struct rtt_unack *cur, *next;
1312 if (tcp_seq_eq_or_after(left, right))
1315 for (cur = begin; cur != end; cur = next) {
1317 // check #1: does [left,right) intersect current unack at all?
1318 // (if not, we can just move on to the next unack)
1319 if (tcp_seq_eq_or_after(cur->seqno, right) ||
1320 tcp_seq_eq_or_after(left, cur->end_seqno)) {
1321 // no intersection - just skip this.
1324 // yes, we intersect!
1325 int left_end_acked = tcp_seq_eq_or_after(cur->seqno, left);
1326 int right_end_acked = tcp_seq_eq_or_after(right, cur->end_seqno);
1327 // check #2 - did we fully ack the current unack?
1328 // (if so, we can delete it and move on)
1329 if (left_end_acked && right_end_acked) {
1330 // ACK the whole segment
1332 x_vals.append(cur->seqno);
1334 x_vals.append(cur->time);
1336 rtt.append((rt_val - cur->time) * 1000.0);
1337 // in this case, we will delete current unack
1338 // [ update "begin" if necessary - we will return it to the
1339 // caller to let them know we deleted it ]
1342 rtt_delete_unack_from_list(list, cur);
1345 // check #3 - did we ACK the left-hand side of the current unack?
1346 // (if so, we can just modify it and move on)
1347 if (left_end_acked) { // and right_end_not_acked
1350 x_vals.append(cur->seqno);
1352 x_vals.append(cur->time);
1354 rtt.append((rt_val - cur->time) * 1000.0);
1355 // in this case, "right" marks the start of remaining bytes
1359 // check #4 - did we ACK the right-hand side of the current unack?
1360 // (if so, we can just modify it and move on)
1361 if (right_end_acked) { // and left_end_not_acked
1362 // ACK the right end
1364 x_vals.append(left);
1366 x_vals.append(cur->time);
1368 rtt.append((rt_val - cur->time) * 1000.0);
1369 // in this case, "left" is just beyond the remaining bytes
1370 cur->end_seqno = left;
1373 // at this point, we know:
1374 // - the SACK block does intersect this unack, but
1375 // - it does not intersect the left or right endpoints
1376 // Therefore, it must intersect the middle, so we must split the unack
1377 // into left and right unacked segments:
1378 // ACK the SACK block
1380 x_vals.append(left);
1382 x_vals.append(cur->time);
1384 rtt.append((rt_val - cur->time) * 1000.0);
1385 // then split cur into two unacked segments
1386 // (linking the right-hand unack after the left)
1387 cur->next = rtt_get_new_unack(cur->time, right, cur->end_seqno - right);
1388 cur->next->next = next;
1389 cur->end_seqno = left;
1394 void TCPStreamDialog::fillRoundTripTime()
1396 QString dlg_title = QString(tr("Round Trip Time")) + streamDescription();
1397 setWindowTitle(dlg_title);
1398 title_->setText(dlg_title);
1400 QCustomPlot *sp = ui->streamPlot;
1401 bool bySeqNumber = ui->bySeqNumberCheckBox->isChecked();
1404 sequence_num_map_.clear();
1405 sp->xAxis->setLabel(sequence_number_label_);
1406 sp->xAxis->setNumberFormat("f");
1407 sp->xAxis->setNumberPrecision(0);
1409 sp->yAxis->setLabel(round_trip_time_ms_label_);
1410 sp->yAxis->setNumberFormat("gb");
1411 sp->yAxis->setNumberPrecision(3);
1413 base_graph_->setLineStyle(QCPGraph::lsLine);
1415 QVector<double> x_vals, rtt;
1416 guint32 seq_base = 0;
1417 struct rtt_unack *unack_list = NULL, *u = NULL;
1418 for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
1419 if (compareHeaders(seg)) {
1420 seq_base = seg->th_seq;
1424 for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
1425 if (compareHeaders(seg)) {
1426 guint32 seqno = seg->th_seq - seq_base;
1427 if (seg->th_seglen && !rtt_is_retrans(unack_list, seqno)) {
1428 double rt_val = seg->rel_secs + seg->rel_usecs / 1000000.0;
1429 rt_val -= ts_offset_;
1430 u = rtt_get_new_unack(rt_val, seqno, seg->th_seglen);
1432 // make sure to free list before returning!
1433 rtt_destroy_unack_list(&unack_list);
1436 rtt_put_unack_on_list(&unack_list, u);
1439 guint32 ack_no = seg->th_ack - seq_base;
1440 double rt_val = seg->rel_secs + seg->rel_usecs / 1000000.0;
1441 rt_val -= ts_offset_;
1442 struct rtt_unack *v;
1444 for (u = unack_list; u; u = v) {
1445 if (tcp_seq_after(ack_no, u->seqno)) {
1446 // full or partial ack of seg by ack_no
1448 x_vals.append(u->seqno);
1449 sequence_num_map_.insert(u->seqno, seg);
1451 x_vals.append(u->time);
1453 rtt.append((rt_val - u->time) * 1000.0);
1454 if (tcp_seq_eq_or_after(ack_no, u->end_seqno)) {
1455 // fully acked segment - nothing more to see here
1457 rtt_delete_unack_from_list(&unack_list, u);
1458 // no need to compare SACK blocks for fully ACKed seg
1461 // partial ack of GSO seg
1463 // (keep going - still need to compare SACK blocks...)
1467 // selectively acking u more than once
1468 // can shatter it into multiple intervals.
1469 // If we link those back into the list between u and v,
1470 // then each subsequent SACK selectively ACKs that range.
1471 for (int i = 0; i < seg->num_sack_ranges; ++i) {
1472 guint32 left = seg->sack_left_edge[i] - seq_base;
1473 guint32 right = seg->sack_right_edge[i] - seq_base;
1474 u = rtt_selectively_ack_range(x_vals, bySeqNumber, rtt,
1476 left, right, rt_val);
1477 // if range is empty after selective ack, we can
1478 // skip the rest of the SACK blocks
1484 // it's possible there's still unacked segs - so be sure to free list!
1485 rtt_destroy_unack_list(&unack_list);
1486 base_graph_->setData(x_vals, rtt);
1489 void TCPStreamDialog::fillWindowScale()
1491 QString dlg_title = QString(tr("Window Scaling")) + streamDescription();
1492 setWindowTitle(dlg_title);
1493 title_->setText(dlg_title);
1495 QCustomPlot *sp = ui->streamPlot;
1496 // use base_graph_ to represent unacked window size
1497 // (estimate of congestion window)
1498 base_graph_->setLineStyle(QCPGraph::lsStepLeft);
1499 // use rwin_graph_ here to show rwin window scale
1500 // (derived from ACK packets)
1501 base_graph_->setVisible(ui->showBytesOutCheckBox->isChecked());
1502 rwin_graph_->setVisible(ui->showRcvWinCheckBox->isChecked());
1504 QVector<double> rel_time, win_size;
1505 QVector<double> cwnd_time, cwnd_size;
1506 guint32 last_ack = 0;
1507 bool found_first_ack = false;
1508 for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
1509 double ts = seg->rel_secs + seg->rel_usecs / 1000000.0;
1511 // The receive window that applies to this flow comes
1512 // from packets in the opposite direction
1513 if (compareHeaders(seg)) {
1514 // compute bytes_in_flight for cwnd graph
1515 guint32 end_seq = seg->th_seq + seg->th_seglen;
1516 if (found_first_ack &&
1517 tcp_seq_eq_or_after(end_seq, last_ack)) {
1518 cwnd_time.append(ts - ts_offset_);
1519 cwnd_size.append((double)(end_seq - last_ack));
1522 // packet in opposite direction - has advertised rwin
1523 guint16 flags = seg->th_flags;
1525 if ((flags & (TH_SYN|TH_RST)) == 0) {
1526 rel_time.append(ts - ts_offset_);
1527 win_size.append(seg->th_win);
1529 if ((flags & (TH_ACK)) != 0) {
1530 // use this to update last_ack
1531 if (!found_first_ack ||
1532 tcp_seq_eq_or_after(seg->th_ack, last_ack)) {
1533 last_ack = seg->th_ack;
1534 found_first_ack = true;
1539 base_graph_->setData(cwnd_time, cwnd_size);
1540 rwin_graph_->setData(rel_time, win_size);
1541 sp->yAxis->setLabel(window_size_label_);
1544 QString TCPStreamDialog::streamDescription()
1546 QString description(tr(" for %1:%2 %3 %4:%5")
1547 .arg(address_to_qstring(&graph_.src_address))
1548 .arg(graph_.src_port)
1549 .arg(UTF8_RIGHTWARDS_ARROW)
1550 .arg(address_to_qstring(&graph_.dst_address))
1551 .arg(graph_.dst_port));
1555 bool TCPStreamDialog::compareHeaders(segment *seg)
1557 return (compare_headers(&graph_.src_address, &graph_.dst_address,
1558 graph_.src_port, graph_.dst_port,
1559 &seg->ip_src, &seg->ip_dst,
1560 seg->th_sport, seg->th_dport,
1564 void TCPStreamDialog::toggleTracerStyle(bool force_default)
1566 if (!tracer_->visible() && !force_default) return;
1568 QPen sp_pen = ui->streamPlot->graph(0)->pen();
1569 QCPItemTracer::TracerStyle tstyle = QCPItemTracer::tsCrosshair;
1570 QPen tr_pen = QPen(tracer_->pen());
1571 QColor tr_color = sp_pen.color();
1573 if (force_default || tracer_->style() != QCPItemTracer::tsCircle) {
1574 tstyle = QCPItemTracer::tsCircle;
1575 tr_color.setAlphaF(1.0);
1576 tr_pen.setWidthF(1.5);
1578 tr_color.setAlphaF(0.5);
1579 tr_pen.setWidthF(1.0);
1582 tracer_->setStyle(tstyle);
1583 tr_pen.setColor(tr_color);
1584 tracer_->setPen(tr_pen);
1585 ui->streamPlot->replot();
1588 QRectF TCPStreamDialog::getZoomRanges(QRect zoom_rect)
1590 QRectF zoom_ranges = QRectF();
1592 QCustomPlot *sp = ui->streamPlot;
1593 QRect zr = zoom_rect.normalized();
1595 if (zr.width() < min_zoom_pixels_ && zr.height() < min_zoom_pixels_) {
1599 QRect ar = sp->axisRect()->rect();
1600 if (ar.intersects(zr)) {
1601 QRect zsr = ar.intersected(zr);
1602 zoom_ranges.setX(sp->xAxis->range().lower
1603 + sp->xAxis->range().size() * (zsr.left() - ar.left()) / ar.width());
1604 zoom_ranges.setWidth(sp->xAxis->range().size() * zsr.width() / ar.width());
1607 zoom_ranges.setY(sp->yAxis->range().lower
1608 + sp->yAxis->range().size() * (ar.bottom() - zsr.bottom()) / ar.height());
1609 zoom_ranges.setHeight(sp->yAxis->range().size() * zsr.height() / ar.height());
1614 void TCPStreamDialog::graphClicked(QMouseEvent *event)
1616 QCustomPlot *sp = ui->streamPlot;
1618 // mouse press on graph should reset focus to graph
1621 if (event->button() == Qt::RightButton) {
1622 // XXX We should find some way to get streamPlot to handle a
1623 // contextMenuEvent instead.
1624 ctx_menu_.exec(event->globalPos());
1625 } else if (mouse_drags_) {
1626 if (sp->axisRect()->rect().contains(event->pos())) {
1627 sp->setCursor(QCursor(Qt::ClosedHandCursor));
1629 on_actionGoToPacket_triggered();
1631 if (!rubber_band_) {
1632 rubber_band_ = new QRubberBand(QRubberBand::Rectangle, sp);
1634 rb_origin_ = event->pos();
1635 rubber_band_->setGeometry(QRect(rb_origin_, QSize()));
1636 rubber_band_->show();
1640 void TCPStreamDialog::axisClicked(QCPAxis *axis, QCPAxis::SelectablePart, QMouseEvent *)
1642 QCustomPlot *sp = ui->streamPlot;
1644 if (axis == sp->xAxis) {
1645 switch (graph_.type) {
1646 case GRAPH_THROUGHPUT:
1647 case GRAPH_TSEQ_STEVENS:
1648 case GRAPH_TSEQ_TCPTRACE:
1651 ts_origin_conn_ = ts_origin_conn_ ? false : true;
1657 } else if (axis == sp->yAxis) {
1658 switch (graph_.type) {
1659 case GRAPH_TSEQ_STEVENS:
1660 case GRAPH_TSEQ_TCPTRACE:
1661 seq_origin_zero_ = seq_origin_zero_ ? false : true;
1670 // Setting mouseTracking on our streamPlot may not be as reliable
1671 // as we need. If it's not we might want to poll the mouse position
1672 // using a QTimer instead.
1673 void TCPStreamDialog::mouseMoved(QMouseEvent *event)
1675 QCustomPlot *sp = ui->streamPlot;
1676 Qt::CursorShape shape = Qt::ArrowCursor;
1678 if (event->buttons().testFlag(Qt::LeftButton)) {
1680 shape = Qt::ClosedHandCursor;
1682 shape = Qt::CrossCursor;
1685 if (sp->axisRect()->rect().contains(event->pos())) {
1687 shape = Qt::OpenHandCursor;
1689 shape = Qt::CrossCursor;
1694 sp->setCursor(QCursor(shape));
1696 QString hint = "<small><i>";
1698 double tr_key = tracer_->position->key();
1699 struct segment *packet_seg = NULL;
1702 // XXX If we have multiple packets with the same timestamp tr_key
1703 // may not return the packet we want. It might be possible to fudge
1704 // unique keys using nextafter().
1705 if (event && tracer_->graph() && tracer_->position->axisRect()->rect().contains(event->pos())) {
1706 switch (graph_.type) {
1707 case GRAPH_TSEQ_STEVENS:
1708 case GRAPH_TSEQ_TCPTRACE:
1709 case GRAPH_THROUGHPUT:
1711 packet_seg = time_stamp_map_.value(tr_key, NULL);
1714 if (ui->bySeqNumberCheckBox->isChecked())
1715 packet_seg = sequence_num_map_.value(tr_key, NULL);
1717 packet_seg = time_stamp_map_.value(tr_key, NULL);
1724 tracer_->setVisible(false);
1725 hint += "Hover over the graph for details. " + stream_desc_ + "</i></small>";
1726 ui->hintLabel->setText(hint);
1727 ui->streamPlot->replot();
1731 tracer_->setVisible(true);
1732 packet_num_ = packet_seg->num;
1733 hint += tr("%1 %2 (%3s len %4 seq %5 ack %6 win %7)")
1734 .arg(cap_file_ ? tr("Click to select packet") : tr("Packet"))
1736 .arg(QString::number(packet_seg->rel_secs + packet_seg->rel_usecs / 1000000.0, 'g', 4))
1737 .arg(packet_seg->th_seglen)
1738 .arg(packet_seg->th_seq)
1739 .arg(packet_seg->th_ack)
1740 .arg(packet_seg->th_win);
1741 tracer_->setGraphKey(ui->streamPlot->xAxis->pixelToCoord(event->pos().x()));
1744 if (rubber_band_ && rubber_band_->isVisible() && event) {
1745 rubber_band_->setGeometry(QRect(rb_origin_, event->pos()).normalized());
1746 QRectF zoom_ranges = getZoomRanges(QRect(rb_origin_, event->pos()));
1747 if (zoom_ranges.width() > 0.0 && zoom_ranges.height() > 0.0) {
1748 hint += tr("Release to zoom, x = %1 to %2, y = %3 to %4")
1749 .arg(zoom_ranges.x())
1750 .arg(zoom_ranges.x() + zoom_ranges.width())
1751 .arg(zoom_ranges.y())
1752 .arg(zoom_ranges.y() + zoom_ranges.height());
1754 hint += tr("Unable to select range.");
1757 hint += tr("Click to select a portion of the graph.");
1760 hint += " " + stream_desc_ + "</i></small>";
1761 ui->hintLabel->setText(hint);
1764 void TCPStreamDialog::mouseReleased(QMouseEvent *event)
1766 if (rubber_band_ && rubber_band_->isVisible()) {
1767 rubber_band_->hide();
1768 if (!mouse_drags_) {
1769 QRectF zoom_ranges = getZoomRanges(QRect(rb_origin_, event->pos()));
1770 if (zoom_ranges.width() > 0.0 && zoom_ranges.height() > 0.0) {
1771 QCustomPlot *sp = ui->streamPlot;
1772 sp->xAxis->setRangeLower(zoom_ranges.x());
1773 sp->xAxis->setRangeUpper(zoom_ranges.x() + zoom_ranges.width());
1774 sp->yAxis->setRangeLower(zoom_ranges.y());
1775 sp->yAxis->setRangeUpper(zoom_ranges.y() + zoom_ranges.height());
1779 } else if (ui->streamPlot->cursor().shape() == Qt::ClosedHandCursor) {
1780 ui->streamPlot->setCursor(QCursor(Qt::OpenHandCursor));
1784 void TCPStreamDialog::transformYRange(const QCPRange &y_range1)
1786 if (y_axis_xfrm_.isIdentity()) return;
1788 QCustomPlot *sp = ui->streamPlot;
1789 QLineF yp1 = QLineF(1.0, y_range1.lower, 1.0, y_range1.upper);
1790 QLineF yp2 = y_axis_xfrm_.map(yp1);
1792 sp->yAxis2->setRangeUpper(yp2.y2());
1793 sp->yAxis2->setRangeLower(yp2.y1());
1796 void TCPStreamDialog::on_buttonBox_accepted()
1798 QString file_name, extension;
1799 QDir path(wsApp->lastOpenDir());
1800 QString pdf_filter = tr("Portable Document Format (*.pdf)");
1801 QString png_filter = tr("Portable Network Graphics (*.png)");
1802 QString bmp_filter = tr("Windows Bitmap (*.bmp)");
1803 // Gaze upon my beautiful graph with lossy artifacts!
1804 QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)");
1805 QString filter = QString("%1;;%2;;%3;;%4")
1811 file_name = QFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Graph As" UTF8_HORIZONTAL_ELLIPSIS)),
1812 path.canonicalPath(), filter, &extension);
1814 if (file_name.length() > 0) {
1815 bool save_ok = false;
1816 if (extension.compare(pdf_filter) == 0) {
1817 save_ok = ui->streamPlot->savePdf(file_name);
1818 } else if (extension.compare(png_filter) == 0) {
1819 save_ok = ui->streamPlot->savePng(file_name);
1820 } else if (extension.compare(bmp_filter) == 0) {
1821 save_ok = ui->streamPlot->saveBmp(file_name);
1822 } else if (extension.compare(jpeg_filter) == 0) {
1823 save_ok = ui->streamPlot->saveJpg(file_name);
1825 // else error dialog?
1827 path = QDir(file_name);
1828 wsApp->setLastOpenDir(path.canonicalPath().toUtf8().constData());
1833 void TCPStreamDialog::on_graphTypeComboBox_currentIndexChanged(int index)
1835 if (index < 0) return;
1836 graph_.type = static_cast<tcp_graph_type>(ui->graphTypeComboBox->itemData(index).toInt());
1837 showWidgetsForGraphType();
1839 fillGraph(/*reset_axes=*/true, /*set_focus=*/false);
1842 void TCPStreamDialog::on_resetButton_clicked()
1847 void TCPStreamDialog::setCaptureFile(capture_file *cf)
1849 if (!cf) { // We only want to know when the file closes.
1854 void TCPStreamDialog::updateGraph()
1856 graph_updater_.doUpdate();
1859 void TCPStreamDialog::on_streamNumberSpinBox_valueChanged(int new_stream)
1861 if (new_stream >= 0 && new_stream < int(get_tcp_stream_count())) {
1862 graph_updater_.triggerUpdate(1000, /*reset_axes =*/true);
1866 void TCPStreamDialog::on_streamNumberSpinBox_editingFinished()
1871 void TCPStreamDialog::on_maWindowSizeSpinBox_valueChanged(double new_ma_size)
1873 if (new_ma_size > 0.0) {
1874 ma_window_size_ = new_ma_size;
1875 graph_updater_.triggerUpdate(1000, /*reset_axes =*/false);
1879 void TCPStreamDialog::on_maWindowSizeSpinBox_editingFinished()
1884 void TCPStreamDialog::on_selectSACKsCheckBox_stateChanged(int /* state */)
1886 fillGraph(/*reset_axes=*/false, /*set_focus=*/false);
1889 void TCPStreamDialog::on_otherDirectionButton_clicked()
1891 on_actionSwitchDirection_triggered();
1894 void TCPStreamDialog::on_dragRadioButton_toggled(bool checked)
1897 mouse_drags_ = true;
1898 if (rubber_band_ && rubber_band_->isVisible())
1899 rubber_band_->hide();
1900 ui->streamPlot->setInteractions(
1907 void TCPStreamDialog::on_zoomRadioButton_toggled(bool checked)
1910 mouse_drags_ = false;
1911 ui->streamPlot->setInteractions(0);
1915 void TCPStreamDialog::on_bySeqNumberCheckBox_stateChanged(int /* state */)
1917 fillGraph(/*reset_axes=*/true, /*set_focus=*/false);
1920 void TCPStreamDialog::on_showSegLengthCheckBox_stateChanged(int state)
1922 bool visible = (state != 0);
1923 if (graph_.type == GRAPH_THROUGHPUT && base_graph_ != NULL) {
1924 base_graph_->setVisible(visible);
1925 tracer_->setGraph(visible ? base_graph_ : NULL);
1926 ui->streamPlot->replot();
1930 void TCPStreamDialog::on_showThroughputCheckBox_stateChanged(int state)
1932 bool visible = (state != 0);
1933 if (graph_.type == GRAPH_THROUGHPUT && tput_graph_ != NULL) {
1934 tput_graph_->setVisible(visible);
1935 ui->streamPlot->replot();
1939 void TCPStreamDialog::on_showGoodputCheckBox_stateChanged(int state)
1941 bool visible = (state != 0);
1942 if (graph_.type == GRAPH_THROUGHPUT && goodput_graph_ != NULL) {
1943 goodput_graph_->setVisible(visible);
1944 ui->streamPlot->replot();
1948 void TCPStreamDialog::on_showRcvWinCheckBox_stateChanged(int state)
1950 bool visible = (state != 0);
1951 if (graph_.type == GRAPH_WSCALE && rwin_graph_ != NULL) {
1952 rwin_graph_->setVisible(visible);
1953 ui->streamPlot->replot();
1957 void TCPStreamDialog::on_showBytesOutCheckBox_stateChanged(int state)
1959 bool visible = (state != 0);
1960 if (graph_.type == GRAPH_WSCALE && base_graph_ != NULL) {
1961 base_graph_->setVisible(visible);
1962 tracer_->setGraph(visible ? base_graph_ : NULL);
1963 ui->streamPlot->replot();
1967 void TCPStreamDialog::on_actionZoomIn_triggered()
1972 void TCPStreamDialog::on_actionZoomInX_triggered()
1977 void TCPStreamDialog::on_actionZoomInY_triggered()
1982 void TCPStreamDialog::on_actionZoomOut_triggered()
1987 void TCPStreamDialog::on_actionZoomOutX_triggered()
1992 void TCPStreamDialog::on_actionZoomOutY_triggered()
1997 void TCPStreamDialog::on_actionReset_triggered()
1999 on_resetButton_clicked();
2002 void TCPStreamDialog::on_actionMoveRight10_triggered()
2007 void TCPStreamDialog::on_actionMoveLeft10_triggered()
2012 void TCPStreamDialog::on_actionMoveUp10_triggered()
2017 void TCPStreamDialog::on_actionMoveDown10_triggered()
2022 void TCPStreamDialog::on_actionMoveRight1_triggered()
2027 void TCPStreamDialog::on_actionMoveLeft1_triggered()
2032 void TCPStreamDialog::on_actionMoveUp1_triggered()
2037 void TCPStreamDialog::on_actionMoveDown1_triggered()
2042 void TCPStreamDialog::on_actionNextStream_triggered()
2044 if (int(graph_.stream) < int(get_tcp_stream_count()) - 1) {
2045 ui->streamNumberSpinBox->setValue(graph_.stream + 1);
2050 void TCPStreamDialog::on_actionPreviousStream_triggered()
2052 if (graph_.stream > 0) {
2053 ui->streamNumberSpinBox->setValue(graph_.stream - 1);
2058 void TCPStreamDialog::on_actionSwitchDirection_triggered()
2063 copy_address(&tmp_addr, &graph_.src_address);
2064 tmp_port = graph_.src_port;
2065 copy_address(&graph_.src_address, &graph_.dst_address);
2066 graph_.src_port = graph_.dst_port;
2067 copy_address(&graph_.dst_address, &tmp_addr);
2068 graph_.dst_port = tmp_port;
2070 fillGraph(/*reset_axes=*/true, /*set_focus=*/false);
2073 void TCPStreamDialog::on_actionGoToPacket_triggered()
2075 if (tracer_->visible() && cap_file_ && packet_num_ > 0) {
2076 emit goToPacket(packet_num_);
2080 void TCPStreamDialog::on_actionDragZoom_triggered()
2083 ui->zoomRadioButton->toggle();
2085 ui->dragRadioButton->toggle();
2089 void TCPStreamDialog::on_actionToggleSequenceNumbers_triggered()
2091 seq_origin_zero_ = seq_origin_zero_ ? false : true;
2095 void TCPStreamDialog::on_actionToggleTimeOrigin_triggered()
2097 ts_origin_conn_ = ts_origin_conn_ ? false : true;
2101 void TCPStreamDialog::on_actionRoundTripTime_triggered()
2103 ui->graphTypeComboBox->setCurrentIndex(ui->graphTypeComboBox->findData(GRAPH_RTT));
2106 void TCPStreamDialog::on_actionThroughput_triggered()
2108 ui->graphTypeComboBox->setCurrentIndex(ui->graphTypeComboBox->findData(GRAPH_THROUGHPUT));
2111 void TCPStreamDialog::on_actionStevens_triggered()
2113 ui->graphTypeComboBox->setCurrentIndex(ui->graphTypeComboBox->findData(GRAPH_TSEQ_STEVENS));
2116 void TCPStreamDialog::on_actionTcptrace_triggered()
2118 ui->graphTypeComboBox->setCurrentIndex(ui->graphTypeComboBox->findData(GRAPH_TSEQ_TCPTRACE));
2121 void TCPStreamDialog::on_actionWindowScaling_triggered()
2123 ui->graphTypeComboBox->setCurrentIndex(ui->graphTypeComboBox->findData(GRAPH_WSCALE));
2126 void TCPStreamDialog::GraphUpdater::triggerUpdate(int timeout, bool reset_axes)
2128 if (!hasPendingUpdate()) {
2129 graph_update_timer_ = new QTimer(dialog_);
2130 graph_update_timer_->setSingleShot(true);
2131 dialog_->connect(graph_update_timer_, SIGNAL(timeout()), dialog_, SLOT(updateGraph()));
2133 reset_axes_ = (reset_axes_ || reset_axes);
2134 graph_update_timer_->start(timeout);
2137 void TCPStreamDialog::GraphUpdater::clearPendingUpdate()
2139 if (hasPendingUpdate()) {
2140 if (graph_update_timer_->isActive())
2141 graph_update_timer_->stop();
2142 delete graph_update_timer_;
2143 graph_update_timer_ = NULL;
2144 reset_axes_ = false;
2148 void TCPStreamDialog::GraphUpdater::doUpdate()
2150 if (hasPendingUpdate()) {
2151 bool reset_axes = reset_axes_;
2152 clearPendingUpdate();
2153 // if the stream has changed, update the data here
2154 int new_stream = dialog_->ui->streamNumberSpinBox->value();
2155 if ((int(dialog_->graph_.stream) != new_stream) &&
2156 (new_stream >= 0 && new_stream < int(get_tcp_stream_count()))) {
2157 dialog_->graph_.stream = new_stream;
2158 clear_address(&dialog_->graph_.src_address);
2159 clear_address(&dialog_->graph_.dst_address);
2160 dialog_->findStream();
2162 dialog_->fillGraph(reset_axes, /*set_focus =*/false);
2166 void TCPStreamDialog::on_buttonBox_helpRequested()
2168 wsApp->helpTopicAction(HELP_STATS_TCP_STREAM_GRAPHS_DIALOG);
2177 * indent-tabs-mode: nil
2180 * ex: set shiftwidth=4 tabstop=8 expandtab:
2181 * :indentSize=4:tabSize=8:noTabs=true: