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
10 #include "tcp_stream_dialog.h"
11 #include <ui_tcp_stream_dialog.h>
13 #include <algorithm> // for std::sort
14 #include <utility> // for std::pair
17 #include "epan/to_str.h"
19 #include "wsutil/str_util.h"
21 #include <wsutil/utf8_entities.h>
23 #include <ui/qt/utils/tango_colors.h>
24 #include <ui/qt/utils/qt_ui_utils.h>
25 #include "progress_frame.h"
26 #include "main_application.h"
27 #include "ui/qt/widgets/wireshark_file_dialog.h"
32 #include <QPushButton>
37 // - Make the Help button work.
38 // - Show a message or disable the graph if we don't have any data.
39 // - Add a bytes in flight graph
40 // - Make the crosshairs tracer a vertical band?
41 // - Implement File->Copy
43 // - Make the first throughput MA period a dotted/dashed line?
44 // - Add range scroll bars?
45 // - ACK & RWIN segment ticks in tcptrace graph
46 // - Add missing elements (retrans, URG, SACK, etc) to tcptrace. It probably makes
47 // sense to subclass QCPGraph for this.
48 // - Allow switching the tracer between graphs when there are two / selecting
49 // the other graph, at the very least if base_graph_ is disabled.
51 // The GTK+ version computes a 20 (or 21!) segment moving average. Comment
52 // out the line below to use that. By default we use a 1 second MA.
56 const int moving_avg_period_
= 20;
59 const QRgb graph_color_1
= tango_sky_blue_5
;
60 const QRgb graph_color_2
= tango_butter_6
;
61 const QRgb graph_color_3
= tango_chameleon_5
;
62 const QRgb graph_color_4
= tango_scarlet_red_4
;
63 const QRgb graph_color_5
= tango_scarlet_red_6
;
65 // Size of selectable packet points in the base graph
66 const double pkt_point_size_
= 3.0;
68 // Don't accidentally zoom into a 1x1 rect if you happen to click on the graph
70 const int min_zoom_pixels_
= 20;
72 const QString average_throughput_label_
= QObject::tr("Average Throughput (bits/s)");
73 const QString round_trip_time_ms_label_
= QObject::tr("Round Trip Time (ms)");
74 const QString segment_length_label_
= QObject::tr("Segment Length (B)");
75 const QString sequence_number_label_
= QObject::tr("Sequence Number (B)");
76 const QString time_s_label_
= QObject::tr("Time (s)");
77 const QString window_size_label_
= QObject::tr("Window Size (B)");
78 const QString cwnd_label_
= QObject::tr("Unacked (Outstanding) Bytes (B)");
80 QCPErrorBarsNotSelectable::QCPErrorBarsNotSelectable(QCPAxis
*keyAxis
, QCPAxis
*valueAxis
) :
81 QCPErrorBars(keyAxis
, valueAxis
)
85 QCPErrorBarsNotSelectable::~QCPErrorBarsNotSelectable()
89 double QCPErrorBarsNotSelectable::selectTest(const QPointF
&pos
, bool onlySelectable
, QVariant
*details
) const
92 Q_UNUSED(onlySelectable
);
97 TCPStreamDialog::TCPStreamDialog(QWidget
*parent
, capture_file
*cf
, tcp_graph_type graph_type
) :
98 GeometryStateDialog(parent
),
99 ui(new Ui::TCPStreamDialog
),
102 ts_origin_conn_(true),
104 seq_origin_zero_(true),
106 base_graph_(nullptr),
107 tput_graph_(nullptr),
108 goodput_graph_(nullptr),
112 sack_graph_(nullptr),
114 sack2_graph_(nullptr),
116 rwin_graph_(nullptr),
117 dup_ack_graph_(nullptr),
118 zero_win_graph_(nullptr),
122 rubber_band_(nullptr),
123 graph_updater_(this),
126 num_sack_ranges_(-1),
131 memset(&graph_
, 0, sizeof(graph_
));
134 if (parent
) loadGeometry(parent
->width() * 2 / 3, parent
->height() * 4 / 5);
135 setAttribute(Qt::WA_DeleteOnClose
, true);
137 ui
->streamNumberSpinBox
->setStyleSheet("QSpinBox { min-width: 2em; }");
139 uint32_t th_stream
= select_tcpip_session(cap_file_
);
140 if (th_stream
== UINT32_MAX
) {
141 done(QDialog::Rejected
);
145 QComboBox
*gtcb
= ui
->graphTypeComboBox
;
146 gtcb
->setUpdatesEnabled(false);
147 gtcb
->addItem(ui
->actionRoundTripTime
->text(), GRAPH_RTT
);
148 if (graph_type
== GRAPH_RTT
) graph_idx
= gtcb
->count() - 1;
149 gtcb
->addItem(ui
->actionThroughput
->text(), GRAPH_THROUGHPUT
);
150 if (graph_type
== GRAPH_THROUGHPUT
) graph_idx
= gtcb
->count() - 1;
151 gtcb
->addItem(ui
->actionStevens
->text(), GRAPH_TSEQ_STEVENS
);
152 if (graph_type
== GRAPH_TSEQ_STEVENS
) graph_idx
= gtcb
->count() - 1;
153 gtcb
->addItem(ui
->actionTcptrace
->text(), GRAPH_TSEQ_TCPTRACE
);
154 if (graph_type
== GRAPH_TSEQ_TCPTRACE
) graph_idx
= gtcb
->count() - 1;
155 gtcb
->addItem(ui
->actionWindowScaling
->text(), GRAPH_WSCALE
);
156 if (graph_type
== GRAPH_WSCALE
) graph_idx
= gtcb
->count() - 1;
157 gtcb
->setUpdatesEnabled(true);
159 ui
->dragRadioButton
->setChecked(mouse_drags_
);
161 ctx_menu_
.addAction(ui
->actionZoomIn
);
162 ctx_menu_
.addAction(ui
->actionZoomInX
);
163 ctx_menu_
.addAction(ui
->actionZoomInY
);
164 ctx_menu_
.addAction(ui
->actionZoomOut
);
165 ctx_menu_
.addAction(ui
->actionZoomOutX
);
166 ctx_menu_
.addAction(ui
->actionZoomOutY
);
167 ctx_menu_
.addAction(ui
->actionReset
);
168 ctx_menu_
.addSeparator();
169 ctx_menu_
.addAction(ui
->actionMoveRight10
);
170 ctx_menu_
.addAction(ui
->actionMoveLeft10
);
171 ctx_menu_
.addAction(ui
->actionMoveUp10
);
172 ctx_menu_
.addAction(ui
->actionMoveDown10
);
173 ctx_menu_
.addAction(ui
->actionMoveRight1
);
174 ctx_menu_
.addAction(ui
->actionMoveLeft1
);
175 ctx_menu_
.addAction(ui
->actionMoveUp1
);
176 ctx_menu_
.addAction(ui
->actionMoveDown1
);
177 ctx_menu_
.addSeparator();
178 ctx_menu_
.addAction(ui
->actionNextStream
);
179 ctx_menu_
.addAction(ui
->actionPreviousStream
);
180 ctx_menu_
.addAction(ui
->actionSwitchDirection
);
181 ctx_menu_
.addAction(ui
->actionGoToPacket
);
182 ctx_menu_
.addSeparator();
183 ctx_menu_
.addAction(ui
->actionDragZoom
);
184 ctx_menu_
.addAction(ui
->actionToggleSequenceNumbers
);
185 ctx_menu_
.addAction(ui
->actionToggleTimeOrigin
);
186 ctx_menu_
.addAction(ui
->actionCrosshairs
);
187 connect(ui
->actionCrosshairs
, &QAction::triggered
, this, &TCPStreamDialog::toggleTracerStyle
);
188 ctx_menu_
.addSeparator();
189 ctx_menu_
.addAction(ui
->actionRoundTripTime
);
190 ctx_menu_
.addAction(ui
->actionThroughput
);
191 ctx_menu_
.addAction(ui
->actionStevens
);
192 ctx_menu_
.addAction(ui
->actionTcptrace
);
193 ctx_menu_
.addAction(ui
->actionWindowScaling
);
194 set_action_shortcuts_visible_in_context_menu(ctx_menu_
.actions());
196 QCustomPlot
*sp
= ui
->streamPlot
;
198 sp
->setContextMenuPolicy(Qt::CustomContextMenu
);
199 connect(sp
, &QCustomPlot::customContextMenuRequested
, this, &TCPStreamDialog::showContextMenu
);
201 graph_
.type
= graph_type
;
202 graph_
.stream
= th_stream
;
205 showWidgetsForGraphType();
207 ui
->streamNumberSpinBox
->blockSignals(true);
208 ui
->streamNumberSpinBox
->setMaximum(get_tcp_stream_count() - 1);
209 ui
->streamNumberSpinBox
->setValue(graph_
.stream
);
210 ui
->streamNumberSpinBox
->blockSignals(false);
213 ui
->maWindowSizeSpinBox
->blockSignals(true);
214 ui
->maWindowSizeSpinBox
->setDecimals(6);
215 ui
->maWindowSizeSpinBox
->setMinimum(0.000001);
216 ui
->maWindowSizeSpinBox
->setValue(ma_window_size_
);
217 ui
->maWindowSizeSpinBox
->blockSignals(false);
220 // set which Throughput graphs are displayed by default
221 ui
->showSegLengthCheckBox
->blockSignals(true);
222 ui
->showSegLengthCheckBox
->setChecked(true);
223 ui
->showSegLengthCheckBox
->blockSignals(false);
225 ui
->showThroughputCheckBox
->blockSignals(true);
226 ui
->showThroughputCheckBox
->setChecked(true);
227 ui
->showThroughputCheckBox
->blockSignals(false);
229 // set which WScale graphs are displayed by default
230 ui
->showRcvWinCheckBox
->blockSignals(true);
231 ui
->showRcvWinCheckBox
->setChecked(true);
232 ui
->showRcvWinCheckBox
->blockSignals(false);
234 ui
->showBytesOutCheckBox
->blockSignals(true);
235 ui
->showBytesOutCheckBox
->setChecked(true);
236 ui
->showBytesOutCheckBox
->blockSignals(false);
238 QCPTextElement
*file_title
= new QCPTextElement(sp
, gchar_free_to_qstring(cf_get_display_name(cap_file_
)));
239 file_title
->setFont(sp
->xAxis
->labelFont());
240 title_
= new QCPTextElement(sp
);
241 sp
->plotLayout()->insertRow(0);
242 sp
->plotLayout()->addElement(0, 0, file_title
);
243 sp
->plotLayout()->insertRow(0);
244 sp
->plotLayout()->addElement(0, 0, title_
);
246 qreal pen_width
= 0.5;
247 // Base Graph - enables selecting segments (both data and SACKs)
248 base_graph_
= sp
->addGraph();
249 base_graph_
->setPen(QPen(QBrush(graph_color_1
), pen_width
));
251 // Throughput Graph - rate of sent bytes
252 tput_graph_
= sp
->addGraph(sp
->xAxis
, sp
->yAxis2
);
253 tput_graph_
->setPen(QPen(QBrush(graph_color_2
), pen_width
));
254 tput_graph_
->setLineStyle(QCPGraph::lsStepLeft
);
256 // Goodput Graph - rate of ACKed bytes
257 goodput_graph_
= sp
->addGraph(sp
->xAxis
, sp
->yAxis2
);
258 goodput_graph_
->setPen(QPen(QBrush(graph_color_3
), pen_width
));
259 goodput_graph_
->setLineStyle(QCPGraph::lsStepLeft
);
261 // Seg Graph - displays forward data segments on tcptrace graph
262 seg_graph_
= sp
->addGraph();
263 seg_graph_
->setLineStyle(QCPGraph::lsNone
);
264 seg_graph_
->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDot
, Qt::transparent
, 0));
265 seg_eb_
= new QCPErrorBarsNotSelectable(sp
->xAxis
, sp
->yAxis
);
266 seg_eb_
->setErrorType(QCPErrorBars::etValueError
);
267 seg_eb_
->setPen(QPen(QBrush(graph_color_1
), pen_width
));
268 seg_eb_
->setSymbolGap(0.0); // draw error spine as single line
269 seg_eb_
->setWhiskerWidth(pkt_point_size_
);
270 seg_eb_
->removeFromLegend();
271 seg_eb_
->setDataPlottable(seg_graph_
);
273 // Ack Graph - displays ack numbers from reverse packets
274 ack_graph_
= sp
->addGraph();
275 ack_graph_
->setPen(QPen(QBrush(graph_color_2
), pen_width
));
276 ack_graph_
->setLineStyle(QCPGraph::lsStepLeft
);
278 // Sack Graph - displays highest number (most recent) SACK block
279 sack_graph_
= sp
->addGraph();
280 sack_graph_
->setLineStyle(QCPGraph::lsNone
);
281 sack_graph_
->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDot
, Qt::transparent
, 0));
282 sack_eb_
= new QCPErrorBarsNotSelectable(sp
->xAxis
, sp
->yAxis
);
283 sack_eb_
->setErrorType(QCPErrorBars::etValueError
);
284 sack_eb_
->setPen(QPen(QBrush(graph_color_4
), pen_width
));
285 sack_eb_
->setSymbolGap(0.0); // draw error spine as single line
286 sack_eb_
->setWhiskerWidth(0.0);
287 sack_eb_
->removeFromLegend();
288 sack_eb_
->setDataPlottable(sack_graph_
);
290 // Sack Graph 2 - displays subsequent SACK blocks
291 sack2_graph_
= sp
->addGraph();
292 sack2_graph_
->setLineStyle(QCPGraph::lsNone
);
293 sack2_graph_
->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDot
, Qt::transparent
, 0));
294 sack2_eb_
= new QCPErrorBarsNotSelectable(sp
->xAxis
, sp
->yAxis
);
295 sack2_eb_
->setErrorType(QCPErrorBars::etValueError
);
296 sack2_eb_
->setPen(QPen(QBrush(graph_color_5
), pen_width
));
297 sack2_eb_
->setSymbolGap(0.0); // draw error spine as single line
298 sack2_eb_
->setWhiskerWidth(0.0);
299 sack2_eb_
->removeFromLegend();
300 sack2_eb_
->setDataPlottable(sack2_graph_
);
302 // RWin graph - displays upper extent of RWIN advertised on reverse packets
303 rwin_graph_
= sp
->addGraph();
304 rwin_graph_
->setPen(QPen(QBrush(graph_color_3
), pen_width
));
305 rwin_graph_
->setLineStyle(QCPGraph::lsStepLeft
);
307 // Duplicate ACK Graph - displays duplicate ack ticks
308 // QCustomPlot doesn't have QCPScatterStyle::ssTick so we have to make our own.
310 tick_len
*= devicePixelRatio();
311 QPixmap da_tick_pm
= QPixmap(1, tick_len
* 2);
312 da_tick_pm
.fill(Qt::transparent
);
313 QPainter
painter(&da_tick_pm
);
315 da_tick_pen
.setColor(graph_color_2
);
316 da_tick_pen
.setWidthF(pen_width
);
317 painter
.setPen(da_tick_pen
);
318 painter
.drawLine(0, tick_len
, 0, tick_len
* 2);
319 dup_ack_graph_
= sp
->addGraph();
320 dup_ack_graph_
->setLineStyle(QCPGraph::lsNone
);
321 QCPScatterStyle da_ss
= QCPScatterStyle(QCPScatterStyle::ssPixmap
, graph_color_2
, 0);
322 da_ss
.setPixmap(da_tick_pm
);
323 dup_ack_graph_
->setScatterStyle(da_ss
);
325 // Zero Window Graph - displays zero window crosses (x)
326 zero_win_graph_
= sp
->addGraph();
327 zero_win_graph_
->setLineStyle(QCPGraph::lsNone
);
328 zero_win_graph_
->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCross
, graph_color_1
, 5));
330 tracer_
= new QCPItemTracer(sp
);
332 // Triggers fillGraph() [ UNLESS the index is already graph_idx!! ]
333 if (graph_idx
!= ui
->graphTypeComboBox
->currentIndex())
334 // changing the current index will call fillGraph
335 ui
->graphTypeComboBox
->setCurrentIndex(graph_idx
);
337 // the current index is what we want - so fillGraph() manually
340 sp
->setMouseTracking(true);
342 sp
->yAxis
->setLabelColor(QColor(graph_color_1
));
343 sp
->yAxis
->setTickLabelColor(QColor(graph_color_1
));
345 tracer_
->setVisible(false);
346 toggleTracerStyle(true);
348 QPushButton
*save_bt
= ui
->buttonBox
->button(QDialogButtonBox::Save
);
349 save_bt
->setText(tr("Save As…"));
351 QPushButton
*close_bt
= ui
->buttonBox
->button(QDialogButtonBox::Close
);
353 close_bt
->setDefault(true);
356 ProgressFrame::addToButtonBox(ui
->buttonBox
, parent
);
358 connect(sp
, SIGNAL(mousePress(QMouseEvent
*)), this, SLOT(graphClicked(QMouseEvent
*)));
359 connect(sp
, SIGNAL(mouseMove(QMouseEvent
*)), this, SLOT(mouseMoved(QMouseEvent
*)));
360 connect(sp
, SIGNAL(mouseRelease(QMouseEvent
*)), this, SLOT(mouseReleased(QMouseEvent
*)));
361 connect(sp
, SIGNAL(axisClick(QCPAxis
*,QCPAxis::SelectablePart
,QMouseEvent
*)),
362 this, SLOT(axisClicked(QCPAxis
*,QCPAxis::SelectablePart
,QMouseEvent
*)));
363 connect(sp
->yAxis
, SIGNAL(rangeChanged(QCPRange
)), this, SLOT(transformYRange(QCPRange
)));
364 this->setResult(QDialog::Accepted
);
367 TCPStreamDialog::~TCPStreamDialog()
369 graph_segment_list_free(&graph_
);
374 void TCPStreamDialog::showEvent(QShowEvent
*)
379 void TCPStreamDialog::keyPressEvent(QKeyEvent
*event
)
381 int pan_pixels
= event
->modifiers() & Qt::ShiftModifier
? 1 : 10;
383 QWidget
* focusWidget
= QApplication::focusWidget();
385 // Block propagation of "Enter" key when focus is not default (e.g. SpinBox)
386 // [ Note that if focus was on, e.g. Close button, event would never reach
388 if ((event
->key() == Qt::Key_Enter
|| event
->key() == Qt::Key_Return
) &&
389 focusWidget
!=NULL
&& focusWidget
!= ui
->streamPlot
) {
391 // reset focus to default, and accept event
392 ui
->streamPlot
->setFocus();
397 // XXX - This differs from the main window but matches other applications (e.g. Mozilla and Safari)
398 switch(event
->key()) {
400 case Qt::Key_Underscore
: // Shifted minus on U.S. keyboards
401 case Qt::Key_O
: // GTK+
405 case Qt::Key_Equal
: // Unshifted plus on U.S. keyboards
406 case Qt::Key_I
: // GTK+
409 case Qt::Key_X
: // Zoom X axis only
410 if (event
->modifiers() & Qt::ShiftModifier
) {
411 zoomXAxis(false); // upper case X -> Zoom out
413 zoomXAxis(true); // lower case x -> Zoom in
416 case Qt::Key_Y
: // Zoom Y axis only
417 if (event
->modifiers() & Qt::ShiftModifier
) {
418 zoomYAxis(false); // upper case Y -> Zoom out
420 zoomYAxis(true); // lower case y -> Zoom in
425 panAxes(pan_pixels
, 0);
429 panAxes(-1 * pan_pixels
, 0);
433 panAxes(0, pan_pixels
);
437 panAxes(0, -1 * pan_pixels
);
445 case Qt::Key_ParenRight
: // Shifted 0 on U.S. keyboards
452 on_actionNextStream_triggered();
454 case Qt::Key_PageDown
:
455 on_actionPreviousStream_triggered();
459 on_actionSwitchDirection_triggered();
462 on_actionGoToPacket_triggered();
465 on_actionToggleSequenceNumbers_triggered();
468 on_actionToggleTimeOrigin_triggered();
471 on_actionDragZoom_triggered();
475 on_actionRoundTripTime_triggered();
478 on_actionThroughput_triggered();
481 on_actionStevens_triggered();
484 on_actionTcptrace_triggered();
487 on_actionWindowScaling_triggered();
489 // Alas, there is no Blade Runner-style Qt::Key_Enhance
492 QDialog::keyPressEvent(event
);
495 void TCPStreamDialog::mousePressEvent(QMouseEvent
*event
)
497 // if no-one else wants the event, then this is a click on blank space.
498 // Use this opportunity to set focus back to default, and accept event.
499 ui
->streamPlot
->setFocus();
503 void TCPStreamDialog::mouseReleaseEvent(QMouseEvent
*event
)
505 mouseReleased(event
);
508 void TCPStreamDialog::findStream()
510 QCustomPlot
*sp
= ui
->streamPlot
;
512 disconnect(sp
, SIGNAL(mouseMove(QMouseEvent
*)), this, SLOT(mouseMoved(QMouseEvent
*)));
513 // if streamNumberSpinBox has focus -
514 // first clear focus, then disable/enable, then restore focus
515 bool spin_box_focused
= ui
->streamNumberSpinBox
->hasFocus();
516 if (spin_box_focused
)
517 ui
->streamNumberSpinBox
->clearFocus();
518 ui
->streamNumberSpinBox
->setEnabled(false);
519 graph_segment_list_free(&graph_
);
520 graph_segment_list_get(cap_file_
, &graph_
);
521 ui
->streamNumberSpinBox
->setEnabled(true);
522 if (spin_box_focused
)
523 ui
->streamNumberSpinBox
->setFocus();
525 connect(sp
, SIGNAL(mouseMove(QMouseEvent
*)), this, SLOT(mouseMoved(QMouseEvent
*)));
528 void TCPStreamDialog::fillGraph(bool reset_axes
, bool set_focus
)
530 QCustomPlot
*sp
= ui
->streamPlot
;
532 if (sp
->graphCount() < 1) return;
534 base_graph_
->setLineStyle(QCPGraph::lsNone
);
535 tracer_
->setGraph(NULL
);
537 // base_graph_ is always visible.
538 for (int i
= 0; i
< sp
->graphCount(); i
++) {
539 sp
->graph(i
)->data()->clear();
540 sp
->graph(i
)->setVisible(i
== 0 ? true : false);
542 // also clear and hide ErrorBars plottables
543 seg_eb_
->setVisible(false);
544 seg_eb_
->data()->clear();
545 sack_eb_
->setVisible(false);
546 sack_eb_
->data()->clear();
547 sack2_eb_
->setVisible(false);
548 sack2_eb_
->data()->clear();
550 base_graph_
->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc
, pkt_point_size_
));
552 sp
->xAxis
->setLabel(time_s_label_
);
553 sp
->xAxis
->setNumberFormat("gb");
554 // Use enough precision to mark microseconds
555 // when zooming in on a <100s capture
556 sp
->xAxis
->setNumberPrecision(8);
557 sp
->yAxis
->setNumberFormat("f");
558 sp
->yAxis
->setNumberPrecision(0);
559 sp
->yAxis2
->setVisible(false);
560 sp
->yAxis2
->setLabel(QString());
562 /* For graphs other than receive window, the axes are not in sync. */
563 disconnect(sp
->yAxis
, QOverload
<const QCPRange
&>::of(&QCPAxis::rangeChanged
), sp
->yAxis2
, QOverload
<const QCPRange
&>::of(&QCPAxis::setRange
));
566 QString dlg_title
= tr("No Capture Data");
567 setWindowTitle(dlg_title
);
568 title_
->setText(dlg_title
);
569 sp
->setEnabled(false);
570 sp
->yAxis
->setLabel(QString());
577 bool ts_unset
= ts_origin_conn_
;
578 // seq_origin_zero_ defaults to true. It really means something like
579 // "use relative or absolute depending on the TCP dissector preferences".
580 // If it's false, then calculate the offset to convert to the other.
581 bool seq_unset
= !seq_origin_zero_
;
582 uint64_t bytes_fwd
= 0;
583 uint64_t bytes_rev
= 0;
587 time_stamp_map_
.clear();
588 for (struct segment
*seg
= graph_
.segments
; seg
!= NULL
; seg
= seg
->next
) {
589 // NOTE - adding both forward and reverse packets to time_stamp_map_
590 // so that both data and acks are selectable
591 // (this is important especially in selecting particular SACK pkts)
593 if (!compareHeaders(seg
)) {
594 bytes_rev
+= seg
->th_seglen
;
596 // only insert reverse packets if SACK present
597 insert
= (seg
->num_sack_ranges
!= 0);
599 bytes_fwd
+= seg
->th_seglen
;
602 double ts
= seg
->rel_secs
+ seg
->rel_usecs
/ 1000000.0;
608 if (compareHeaders(seg
)) {
609 if (seg
->th_seq
!= seg
->th_rawseq
) {
610 seq_offset_
= seg
->th_seq
- seg
->th_rawseq
;
612 // As with the TCP dissector, if this isn't the SYN or SYN-ACK,
613 // start the relative sequence numbers at 1.
614 if (seg
->th_flags
& TH_SYN
) {
615 seq_offset_
= seg
->th_seq
;
617 seq_offset_
= seg
->th_seq
- 1;
622 // A SYN in the reverse direction does not tell us the base
623 // sequence number, but for other segments (including SYN-ACK)
624 // start the offset at 1, like the TCP dissector.
625 if ((seg
->th_flags
& TH_SYN
) != TH_SYN
) {
626 if (seg
->th_seq
!= seg
->th_rawseq
) {
627 seq_offset_
= seg
->th_seq
- seg
->th_rawseq
;
629 seq_offset_
-= seg
->th_ack
- 1;
636 time_stamp_map_
.insert(ts
- ts_offset_
, seg
);
640 switch (graph_
.type
) {
641 case GRAPH_TSEQ_STEVENS
:
644 case GRAPH_TSEQ_TCPTRACE
:
647 case GRAPH_THROUGHPUT
:
659 sp
->setEnabled(true);
661 stream_desc_
= tr("%1 %2 pkts, %3 %4 %5 pkts, %6 ")
662 .arg(UTF8_RIGHTWARDS_ARROW
)
663 .arg(gchar_free_to_qstring(format_size(pkts_fwd
, FORMAT_SIZE_UNIT_NONE
, FORMAT_SIZE_PREFIX_SI
)))
664 .arg(gchar_free_to_qstring(format_size(bytes_fwd
, FORMAT_SIZE_UNIT_BYTES
, FORMAT_SIZE_PREFIX_SI
)))
665 .arg(UTF8_LEFTWARDS_ARROW
)
666 .arg(gchar_free_to_qstring(format_size(pkts_rev
, FORMAT_SIZE_UNIT_NONE
, FORMAT_SIZE_PREFIX_SI
)))
667 .arg(gchar_free_to_qstring(format_size(bytes_rev
, FORMAT_SIZE_UNIT_BYTES
, FORMAT_SIZE_PREFIX_SI
)));
673 // Throughput and Window Scale graphs can hide base_graph_
674 if (base_graph_
->visible())
675 tracer_
->setGraph(base_graph_
);
677 // XXX QCustomPlot doesn't seem to draw any sort of focus indicator.
682 void TCPStreamDialog::showWidgetsForGraphType()
684 if (graph_
.type
== GRAPH_RTT
) {
685 ui
->bySeqNumberCheckBox
->setVisible(true);
687 ui
->bySeqNumberCheckBox
->setVisible(false);
689 if (graph_
.type
== GRAPH_THROUGHPUT
) {
691 ui
->maWindowSizeLabel
->setVisible(true);
692 ui
->maWindowSizeSpinBox
->setVisible(true);
694 ui
->maWindowSizeLabel
->setVisible(false);
695 ui
->maWindowSizeSpinBox
->setVisible(false);
697 ui
->showSegLengthCheckBox
->setVisible(true);
698 ui
->showThroughputCheckBox
->setVisible(true);
699 ui
->showGoodputCheckBox
->setVisible(true);
701 ui
->maWindowSizeLabel
->setVisible(false);
702 ui
->maWindowSizeSpinBox
->setVisible(false);
703 ui
->showSegLengthCheckBox
->setVisible(false);
704 ui
->showThroughputCheckBox
->setVisible(false);
705 ui
->showGoodputCheckBox
->setVisible(false);
708 if (graph_
.type
== GRAPH_TSEQ_TCPTRACE
) {
709 ui
->selectSACKsCheckBox
->setVisible(true);
711 ui
->selectSACKsCheckBox
->setVisible(false);
714 if (graph_
.type
== GRAPH_WSCALE
) {
715 ui
->showRcvWinCheckBox
->setVisible(true);
716 ui
->showBytesOutCheckBox
->setVisible(true);
718 ui
->showRcvWinCheckBox
->setVisible(false);
719 ui
->showBytesOutCheckBox
->setVisible(false);
723 void TCPStreamDialog::zoomAxes(bool in
)
725 QCustomPlot
*sp
= ui
->streamPlot
;
726 double h_factor
= sp
->axisRect()->rangeZoomFactor(Qt::Horizontal
);
727 double v_factor
= sp
->axisRect()->rangeZoomFactor(Qt::Vertical
);
730 h_factor
= pow(h_factor
, -1);
731 v_factor
= pow(v_factor
, -1);
734 sp
->xAxis
->scaleRange(h_factor
, sp
->xAxis
->range().center());
735 sp
->yAxis
->scaleRange(v_factor
, sp
->yAxis
->range().center());
739 void TCPStreamDialog::zoomXAxis(bool in
)
741 QCustomPlot
*sp
= ui
->streamPlot
;
742 double h_factor
= sp
->axisRect()->rangeZoomFactor(Qt::Horizontal
);
745 h_factor
= pow(h_factor
, -1);
748 sp
->xAxis
->scaleRange(h_factor
, sp
->xAxis
->range().center());
752 void TCPStreamDialog::zoomYAxis(bool in
)
754 QCustomPlot
*sp
= ui
->streamPlot
;
755 double v_factor
= sp
->axisRect()->rangeZoomFactor(Qt::Vertical
);
758 v_factor
= pow(v_factor
, -1);
761 sp
->yAxis
->scaleRange(v_factor
, sp
->yAxis
->range().center());
765 void TCPStreamDialog::panAxes(int x_pixels
, int y_pixels
)
767 QCustomPlot
*sp
= ui
->streamPlot
;
771 h_pan
= sp
->xAxis
->range().size() * x_pixels
/ sp
->xAxis
->axisRect()->width();
772 v_pan
= sp
->yAxis
->range().size() * y_pixels
/ sp
->yAxis
->axisRect()->height();
773 // The GTK+ version won't pan unless we're zoomed. Should we do the same here?
775 sp
->xAxis
->moveRange(h_pan
);
779 sp
->yAxis
->moveRange(v_pan
);
784 void TCPStreamDialog::resetAxes()
786 QCustomPlot
*sp
= ui
->streamPlot
;
788 y_axis_xfrm_
.reset();
789 double pixel_pad
= 10.0; // per side
791 sp
->rescaleAxes(true);
792 // tput_graph_->rescaleValueAxis(false, true);
793 // base_graph_->rescaleAxes(false, true);
794 // for (int i = 0; i < sp->graphCount(); i++) {
795 // sp->graph(i)->rescaleValueAxis(false, true);
798 double axis_pixels
= sp
->xAxis
->axisRect()->width();
799 sp
->xAxis
->scaleRange((axis_pixels
+ (pixel_pad
* 2)) / axis_pixels
,
800 sp
->xAxis
->range().center());
802 if (sp
->yAxis2
->visible()) {
803 double ratio
= sp
->yAxis2
->range().size() / sp
->yAxis
->range().size();
804 y_axis_xfrm_
.translate(0.0, sp
->yAxis2
->range().lower
- (sp
->yAxis
->range().lower
* ratio
));
805 y_axis_xfrm_
.scale(1.0, ratio
);
808 axis_pixels
= sp
->yAxis
->axisRect()->height();
809 sp
->yAxis
->scaleRange((axis_pixels
+ (pixel_pad
* 2)) / axis_pixels
,
810 sp
->yAxis
->range().center());
815 void TCPStreamDialog::fillStevens()
817 QString dlg_title
= tr("Sequence Numbers (Stevens)") + streamDescription();
818 setWindowTitle(dlg_title
);
819 title_
->setText(dlg_title
);
821 QCustomPlot
*sp
= ui
->streamPlot
;
822 sp
->yAxis
->setLabel(sequence_number_label_
);
824 // True Stevens-style graphs don't have lines but I like them - gcc
825 base_graph_
->setLineStyle(QCPGraph::lsStepLeft
);
827 QVector
<double> rel_time
, seq
;
828 for (struct segment
*seg
= graph_
.segments
; seg
!= NULL
; seg
= seg
->next
) {
829 if (!compareHeaders(seg
)) {
833 double ts
= seg
->rel_secs
+ seg
->rel_usecs
/ 1000000.0;
834 rel_time
.append(ts
- ts_offset_
);
835 seq
.append(seg
->th_seq
- seq_offset_
);
837 base_graph_
->setData(rel_time
, seq
);
840 void TCPStreamDialog::fillTcptrace()
842 QString dlg_title
= tr("Sequence Numbers (tcptrace)") + streamDescription();
843 setWindowTitle(dlg_title
);
844 title_
->setText(dlg_title
);
846 bool allow_sack_select
= ui
->selectSACKsCheckBox
->isChecked();
848 QCustomPlot
*sp
= ui
->streamPlot
;
849 sp
->yAxis
->setLabel(sequence_number_label_
);
851 base_graph_
->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDot
));
853 seg_graph_
->setVisible(true);
854 seg_eb_
->setVisible(true);
855 ack_graph_
->setVisible(true);
856 sack_graph_
->setVisible(true);
857 sack_eb_
->setVisible(true);
858 sack2_graph_
->setVisible(true);
859 sack2_eb_
->setVisible(true);
860 rwin_graph_
->setVisible(true);
861 dup_ack_graph_
->setVisible(true);
862 zero_win_graph_
->setVisible(true);
864 QVector
<double> pkt_time
, pkt_seqnums
;
865 QVector
<double> sb_time
, sb_center
, sb_span
;
866 QVector
<double> ackrwin_time
, ack
, rwin
;
867 QVector
<double> sack_time
, sack_center
, sack_span
;
868 QVector
<double> sack2_time
, sack2_center
, sack2_span
;
869 QVector
<double> dup_ack_time
, dup_ack
;
870 QVector
<double> zero_win_time
, zero_win
;
872 for (struct segment
*seg
= graph_
.segments
; seg
!= NULL
; seg
= seg
->next
) {
873 double ts
= (seg
->rel_secs
+ seg
->rel_usecs
/ 1000000.0) - ts_offset_
;
874 if (compareHeaders(seg
)) {
875 double half
= seg
->th_seglen
/ 2.0;
876 double center
= seg
->th_seq
- seq_offset_
+ half
;
878 // Add forward direction to base_graph_ (to select data packets)
879 // Forward direction: seq + data
881 pkt_seqnums
.append(center
);
883 // QCP doesn't have a segment graph type. For now, fake
884 // it with error bars.
885 if (seg
->th_seglen
> 0) {
887 sb_center
.append(center
);
888 sb_span
.append(half
);
891 // Look for zero window sizes.
892 // Should match the TCP_A_ZERO_WINDOW test in packet-tcp.c.
893 if (seg
->th_win
== 0 && (seg
->th_flags
& (TH_RST
|TH_FIN
|TH_SYN
)) == 0) {
894 zero_win_time
.append(ts
);
895 zero_win
.append(center
);
898 // Reverse direction: ACK + RWIN
899 if (! (seg
->th_flags
& TH_ACK
)) {
900 // SYNs and RSTs do not necessarily have ACKs
903 double ackno
= seg
->th_ack
- seq_offset_
;
904 // add SACK segments to sack, sack2, and selectable packet graph
905 for (int i
= 0; i
< seg
->num_sack_ranges
; ++i
) {
906 double half
= seg
->sack_right_edge
[i
] - seg
->sack_left_edge
[i
];
908 double center
= seg
->sack_left_edge
[i
] - seq_offset_
+ half
;
910 sack_time
.append(ts
);
911 sack_center
.append(center
);
912 sack_span
.append(half
);
913 if (allow_sack_select
) {
915 pkt_seqnums
.append(center
);
918 sack2_time
.append(ts
);
919 sack2_center
.append(center
);
920 sack2_span
.append(half
);
923 // If ackno is the same as our last one mark it as a duplicate.
924 // (but don't mark window updates as duplicate acks)
925 if (ack
.size() > 0 && ack
.last() == ackno
926 && rwin
.last() == ackno
+ seg
->th_win
) {
927 dup_ack_time
.append(ts
);
928 dup_ack
.append(ackno
);
930 // Also add reverse packets to the ack_graph_
931 ackrwin_time
.append(ts
);
933 rwin
.append(ackno
+ seg
->th_win
);
936 base_graph_
->setData(pkt_time
, pkt_seqnums
, true);
937 ack_graph_
->setData(ackrwin_time
, ack
, true);
938 seg_graph_
->setData(sb_time
, sb_center
, true);
939 seg_eb_
->setData(sb_span
);
940 sack_graph_
->setData(sack_time
, sack_center
, true);
941 sack_eb_
->setData(sack_span
);
942 sack2_graph_
->setData(sack2_time
, sack2_center
, true);
943 sack2_eb_
->setData(sack2_span
);
944 rwin_graph_
->setValueAxis(sp
->yAxis
);
945 rwin_graph_
->setData(ackrwin_time
, rwin
, true);
946 dup_ack_graph_
->setData(dup_ack_time
, dup_ack
, true);
947 zero_win_graph_
->setData(zero_win_time
, zero_win
, true);
950 // If the current implementation of incorporating SACKs in goodput calc
951 // is slow, comment out the following line to ignore SACKs in goodput calc.
952 #define USE_SACKS_IN_GOODPUT_CALC
954 #ifdef USE_SACKS_IN_GOODPUT_CALC
955 // to incorporate SACKED segments into goodput calculation,
956 // need to keep track of all the SACK blocks we haven't yet
958 // I expect this to be _relatively_ small, so using vector to store
959 // them. If this performs badly, it can be refactored with std::list
961 typedef std::pair
<uint32_t, uint32_t> sack_t
;
962 typedef std::vector
<sack_t
> sack_list_t
;
963 static inline bool compare_sack(const sack_t
& s1
, const sack_t
& s2
) {
964 return tcp_seq_before(s1
.first
, s2
.first
);
967 // Helper function to adjust an acked seglen for goodput:
968 // - removes previously sacked ranges from seglen (and from old_sacks),
969 // - adds newly sacked ranges to seglen (and to old_sacks)
971 goodput_adjust_for_sacks(uint32_t *seglen
, uint32_t last_ack
,
972 sack_list_t
& new_sacks
, uint8_t num_sack_ranges
,
973 sack_list_t
& old_sacks
) {
975 // Step 1 - For any old_sacks acked by last_ack,
976 // delete their acked length from seglen,
977 // and remove the sack block (or portion)
978 // from (sorted) old_sacks.
979 sack_list_t::iterator unacked
= old_sacks
.begin();
980 while (unacked
!= old_sacks
.end()) {
981 // break on first sack not fully acked
982 if (tcp_seq_before(last_ack
, unacked
->second
)) {
983 if (tcp_seq_after(last_ack
, unacked
->first
)) {
984 // partially acked - modify to remove acked part
985 *seglen
-= (last_ack
- unacked
->first
);
986 unacked
->first
= last_ack
;
990 // remove fully acked sacks from seglen and move on
991 // (we'll actually remove from the list when loop is done)
992 *seglen
-= (unacked
->second
- unacked
->first
);
995 // actually remove all fully acked sacks from old_sacks list
996 if (unacked
!= old_sacks
.begin())
997 old_sacks
.erase(old_sacks
.begin(), unacked
);
999 // Step 2 - for any new_sacks that precede last_ack,
1000 // ignore them. (These would generally be SACKed dup-acks of
1001 // a retransmitted seg).
1002 // [ in the unlikely case that any new SACK straddles last_ack,
1003 // the sack block will be modified to remove the acked portion ]
1004 int next_new_idx
= 0;
1005 while (next_new_idx
< num_sack_ranges
) {
1006 if (tcp_seq_before(last_ack
, new_sacks
[next_new_idx
].second
)) {
1007 // if a new SACK block is unacked by its own packet, then it's
1008 // likely fully unacked, but let's check for partial ack anyway,
1009 // and truncate the SACK so that it's fully unacked:
1010 if (tcp_seq_before(new_sacks
[next_new_idx
].first
, last_ack
))
1011 new_sacks
[next_new_idx
].first
= last_ack
;
1017 // Step 3 - for any byte ranges in remaining new_sacks
1018 // that don't already exist in old_sacks, add
1019 // their length to seglen
1020 // and add that range (by extension, if possible) to
1021 // the list of old_sacks.
1023 sack_list_t::iterator next_old
= old_sacks
.begin();
1025 while (next_new_idx
< num_sack_ranges
&&
1026 next_old
!= old_sacks
.end()) {
1027 sack_t
* next_new
= &new_sacks
[next_new_idx
];
1029 // Assumptions / Invariants:
1030 // - new and old lists are sorted
1031 // - span of leftmost to rightmost endpt. is less than half uint32 range
1032 // [ensures transitivity - e.g. before(a,b) and before(b,c) ==> before(a,c)]
1033 // - all SACKs are non-empty (sack.left before sack.right)
1034 // - adjacent SACKs in list always have a gap between them
1035 // (sack.right before next_sack.left)
1037 // Given these assumptions, and noting that there are only three
1038 // possible comparisons for a pair of points (before/equal/after),
1039 // there are only a few possible relative configurations
1040 // of next_old and next_new:
1046 // 3. [----------------)
1047 // 4. [---------------------)
1048 // 5. [----------------------------)
1050 // 7. [-------------)
1051 // 8. [--------------------)
1054 // 11. [---------------)
1058 // Case 1: end of next_old is before beginning of next_new
1060 // [-------------) ... <end>
1062 // 1. [---) ... <end>
1063 if (tcp_seq_before(next_old
->second
, next_new
->first
)) {
1065 // advance to the next sack in old_sacks
1067 // retry from the top
1071 // Case 13: end of next_new is before beginning of next_old
1073 // [-------------) ... <end>
1075 // 13. [--) ... <end>
1076 if (tcp_seq_before(next_new
->second
, next_old
->first
)) {
1078 // add then entire length of next_new into seglen
1079 *seglen
+= (next_new
->second
- next_new
->first
);
1080 // insert next_new before next_old in old_sacks
1081 // (be sure to save and restore next_old iterator around insert!)
1082 int next_old_idx
= int(next_old
- old_sacks
.begin());
1083 old_sacks
.insert(next_old
, *next_new
);
1084 next_old
= old_sacks
.begin() + next_old_idx
+ 1;
1085 // advance to the next remaining sack in new_sacks
1087 // retry from the top
1091 // Remaining possible configurations:
1096 // 3. [----------------)
1097 // 4. [---------------------)
1098 // 5. [----------------------------)
1100 // 7. [-------------)
1101 // 8. [--------------------)
1104 // 11. [---------------)
1107 // Cases 2,3,6,9: end of next_old is before end of next_new
1112 // 3. [----------------)
1116 // until end of next_old is equal or after end of next_new,
1117 // repeatedly extend next_old, coalescing with next_next_old
1118 // if necessary. (and add extended bytes to seglen)
1119 while (tcp_seq_before(next_old
->second
, next_new
->second
)) {
1120 // if end of next_new doesn't collide with start of next_next_old,
1121 if (((next_old
+1) == old_sacks
.end()) ||
1122 tcp_seq_before(next_new
->second
, (next_old
+ 1)->first
)) {
1123 // extend end of next_old up to end of next_new,
1124 // adding extended bytes to seglen
1125 *seglen
+= (next_new
->second
- next_old
->second
);
1126 next_old
->second
= next_new
->second
;
1128 // otherwise, coalesce next_old with next_next_old
1130 // add bytes to close gap between sacks to seglen
1131 *seglen
+= ((next_old
+ 1)->first
- next_old
->second
);
1132 // coalesce next_next_old into next_old
1133 next_old
->second
= (next_old
+ 1)->second
;
1134 old_sacks
.erase(next_old
+ 1);
1137 // This operation turns:
1138 // Cases 2 and 3 into Case 4 or 5
1139 // Case 6 into Case 7
1140 // Case 9 into Case 10
1143 // Remaining possible configurations:
1147 // 4. [---------------------)
1148 // 5. [----------------------------)
1149 // 7. [-------------)
1150 // 8. [--------------------)
1152 // 11. [---------------)
1155 // Cases 10,11,12: start of next_new is before start of next_old
1160 // 11. [---------------)
1162 if (tcp_seq_before(next_new
->first
, next_old
->first
)) {
1164 // add the unaccounted bytes in next_new to seglen
1165 *seglen
+= (next_old
->first
- next_new
->first
);
1166 // then pull the start of next_old back to the start of next_new
1167 next_old
->first
= next_new
->first
;
1169 // This operation turns:
1170 // Case 10 into Case 7
1171 // Cases 11 and 12 into Case 8
1174 // Remaining possible configurations:
1178 // 4. [---------------------)
1179 // 5. [----------------------------)
1180 // 7. [-------------)
1181 // 8. [--------------------)
1183 // In these cases, the bytes in next_new are fully accounted
1184 // by the bytes in next_old, so we can move on to look at
1185 // the next sack block in new_sacks
1188 // Conditions for leaving loop:
1189 // - we processed all remaining new_sacks - nothing left to do
1190 // (next_new_idx == num_sack_ranges)
1192 // - all remaining new_sacks start at least one byte after
1193 // the rightmost edge of the last old_sack
1194 // (meaning we can just add the remaining new_sacks to old_sacks list,
1195 // and add them directly to the goodput seglen)
1196 while (next_new_idx
< num_sack_ranges
) {
1197 sack_t
* next_new
= &new_sacks
[next_new_idx
];
1198 *seglen
+= (next_new
->second
- next_new
->first
);
1199 old_sacks
.push_back(*next_new
);
1203 #endif // USE_SACKS_IN_GOODPUT_CALC
1205 void TCPStreamDialog::fillThroughput()
1207 QString dlg_title
= tr("Throughput") + streamDescription();
1209 dlg_title
.append(tr(" (MA)"));
1211 dlg_title
.append(tr(" (%1 Segment MA)").arg(moving_avg_period_
));
1213 setWindowTitle(dlg_title
);
1214 title_
->setText(dlg_title
);
1216 QCustomPlot
*sp
= ui
->streamPlot
;
1217 sp
->yAxis
->setLabel(segment_length_label_
);
1218 sp
->yAxis2
->setLabel(average_throughput_label_
);
1219 sp
->yAxis2
->setLabelColor(QColor(graph_color_2
));
1220 sp
->yAxis2
->setTickLabelColor(QColor(graph_color_2
));
1221 sp
->yAxis2
->setVisible(true);
1223 base_graph_
->setVisible(ui
->showSegLengthCheckBox
->isChecked());
1224 tput_graph_
->setVisible(ui
->showThroughputCheckBox
->isChecked());
1225 goodput_graph_
->setVisible(ui
->showGoodputCheckBox
->isChecked());
1228 if (!graph_
.segments
) {
1230 if (!graph_
.segments
|| !graph_
.segments
->next
) {
1232 dlg_title
.append(tr(" [not enough data]"));
1236 QVector
<double> seg_rel_times
, ack_rel_times
;
1237 QVector
<double> seg_lens
, ack_lens
;
1238 QVector
<double> tput_times
, gput_times
;
1239 QVector
<double> tputs
, gputs
;
1240 int oldest_seg
= 0, oldest_ack
= 0;
1241 uint64_t seg_sum
= 0, ack_sum
= 0;
1242 uint32_t seglen
= 0;
1244 #ifdef USE_SACKS_IN_GOODPUT_CALC
1245 // to incorporate SACKED segments into goodput calculation,
1246 // need to keep track of all the SACK blocks we haven't yet
1248 sack_list_t old_sacks
, new_sacks
;
1249 new_sacks
.reserve(MAX_TCP_SACK_RANGES
);
1250 // statically allocate current_sacks vector
1251 // [ std::array might be better, but that is C++11 ]
1252 for (int i
= 0; i
< MAX_TCP_SACK_RANGES
; ++i
) {
1253 new_sacks
.push_back(sack_t(0,0));
1255 old_sacks
.reserve(2*MAX_TCP_SACK_RANGES
);
1256 #endif // USE_SACKS_IN_GOODPUT_CALC
1258 // need first acked sequence number to jump-start
1259 // computation of acked bytes per packet
1260 uint32_t last_ack
= 0;
1261 for (struct segment
*seg
= graph_
.segments
; seg
!= NULL
; seg
= seg
->next
) {
1262 // first reverse packet with ACK flag tells us first acked sequence #
1263 if (!compareHeaders(seg
) && (seg
->th_flags
& TH_ACK
)) {
1264 last_ack
= seg
->th_ack
;
1268 // Financial charts don't show MA data until a full period has elapsed.
1269 // [ NOTE - this is because they assume that there's old data that they
1270 // don't have access to - but in our case we know that there's NO
1271 // data prior to the first packet in the stream - so it's fine to
1272 // spit out the MA immediately... ]
1273 // The Rosetta Code MA examples start spitting out values immediately.
1274 // For now use not-really-correct initial values just to keep our vector
1275 // lengths the same.
1277 // NOTE that for the time-based MA case, you certainly can start with the
1279 for (struct segment
*seg
= graph_
.segments
; seg
!= NULL
; seg
= seg
->next
) {
1281 for (struct segment
*seg
= graph_
.segments
->next
; seg
!= NULL
; seg
= seg
->next
) {
1283 bool is_forward_seg
= compareHeaders(seg
);
1284 QVector
<double>& r_pkt_times
= is_forward_seg
? seg_rel_times
: ack_rel_times
;
1285 QVector
<double>& r_lens
= is_forward_seg
? seg_lens
: ack_lens
;
1286 QVector
<double>& r_Xput_times
= is_forward_seg
? tput_times
: gput_times
;
1287 QVector
<double>& r_Xputs
= is_forward_seg
? tputs
: gputs
;
1288 int& r_oldest
= is_forward_seg
? oldest_seg
: oldest_ack
;
1289 uint64_t& r_sum
= is_forward_seg
? seg_sum
: ack_sum
;
1291 double ts
= (seg
->rel_secs
+ seg
->rel_usecs
/ 1000000.0) - ts_offset_
;
1293 if (is_forward_seg
) {
1294 seglen
= seg
->th_seglen
;
1296 if ((seg
->th_flags
& TH_ACK
) &&
1297 tcp_seq_eq_or_after(seg
->th_ack
, last_ack
)) {
1298 seglen
= seg
->th_ack
- last_ack
;
1299 last_ack
= seg
->th_ack
;
1300 #ifdef USE_SACKS_IN_GOODPUT_CALC
1301 // copy any sack_ranges into new_sacks, and sort.
1302 for (int i
= 0; i
< seg
->num_sack_ranges
; ++i
) {
1303 new_sacks
[i
].first
= seg
->sack_left_edge
[i
];
1304 new_sacks
[i
].second
= seg
->sack_right_edge
[i
];
1306 std::sort(new_sacks
.begin(),
1307 new_sacks
.begin() + seg
->num_sack_ranges
,
1310 // adjust the seglen based on new and old sacks,
1311 // and update the old_sacks list
1312 goodput_adjust_for_sacks(&seglen
, last_ack
,
1313 new_sacks
, seg
->num_sack_ranges
,
1315 #endif // USE_SACKS_IN_GOODPUT_CALC
1321 r_pkt_times
.append(ts
);
1322 r_lens
.append(seglen
);
1325 while (r_oldest
< r_pkt_times
.size() && ts
- r_pkt_times
[r_oldest
] > ma_window_size_
) {
1326 r_sum
-= r_lens
[r_oldest
];
1327 // append points where a packet LEAVES the MA window
1328 // (as well as, below, where they ENTER the MA window)
1329 r_Xputs
.append(r_sum
* 8.0 / ma_window_size_
);
1330 r_Xput_times
.append(r_pkt_times
[r_oldest
] + ma_window_size_
);
1334 if (r_lens
.size() > moving_avg_period_
) {
1335 r_sum
-= r_lens
[r_oldest
];
1340 // av_Xput computes Xput, i.e.:
1341 // throughput for forward packets
1342 // goodput for reverse packets
1346 // for time-based MA, delta_t is constant
1347 av_Xput
= r_sum
* 8.0 / ma_window_size_
;
1351 dtime
= ts
- r_pkt_times
[r_oldest
-1];
1353 av_Xput
= r_sum
* 8.0 / dtime
;
1359 // Add a data point only if our time window has advanced. Otherwise
1360 // update the most recent point. (We might want to show a warning
1361 // for out-of-order packets.)
1362 if (r_Xput_times
.size() > 0 && ts
<= r_Xput_times
.last()) {
1363 r_Xputs
[r_Xputs
.size() - 1] = av_Xput
;
1365 r_Xputs
.append(av_Xput
);
1366 r_Xput_times
.append(ts
);
1369 base_graph_
->setData(seg_rel_times
, seg_lens
);
1370 tput_graph_
->setData(tput_times
, tputs
);
1371 goodput_graph_
->setData(gput_times
, gputs
);
1374 // rtt_selectively_ack_range:
1375 // "Helper" function for fillRoundTripTime
1376 // given an rtt_unack list, two pointers to a range of segments in the list,
1377 // and the [left,right) edges of a SACK block, selectively ACKs the range
1378 // from "begin" to "end" - possibly splitting one segment in the range
1379 // into two (and relinking the new segment in order after the first)
1382 // "begin must be non-NULL
1383 // "begin" must precede "end" (or "end" must be NULL)
1384 // [ there are minor optimizations that could be added if
1385 // the range from "begin" to "end" are in sequence number order.
1386 // (this function would preserve that as an invariant). ]
1387 static struct rtt_unack
*
1388 rtt_selectively_ack_range(QVector
<double>& x_vals
, bool bySeqNumber
,
1389 QVector
<double>& rtt
,
1390 struct rtt_unack
**list
,
1391 struct rtt_unack
*begin
, struct rtt_unack
*end
,
1392 unsigned int left
, unsigned int right
, double rt_val
) {
1393 struct rtt_unack
*cur
, *next
;
1395 if (tcp_seq_eq_or_after(left
, right
))
1398 for (cur
= begin
; cur
!= end
; cur
= next
) {
1400 // check #1: does [left,right) intersect current unack at all?
1401 // (if not, we can just move on to the next unack)
1402 if (tcp_seq_eq_or_after(cur
->seqno
, right
) ||
1403 tcp_seq_eq_or_after(left
, cur
->end_seqno
)) {
1404 // no intersection - just skip this.
1407 // yes, we intersect!
1408 int left_end_acked
= tcp_seq_eq_or_after(cur
->seqno
, left
);
1409 int right_end_acked
= tcp_seq_eq_or_after(right
, cur
->end_seqno
);
1410 // check #2 - did we fully ack the current unack?
1411 // (if so, we can delete it and move on)
1412 if (left_end_acked
&& right_end_acked
) {
1413 // ACK the whole segment
1415 x_vals
.append(cur
->seqno
);
1417 x_vals
.append(cur
->time
);
1419 rtt
.append((rt_val
- cur
->time
) * 1000.0);
1420 // in this case, we will delete current unack
1421 // [ update "begin" if necessary - we will return it to the
1422 // caller to let them know we deleted it ]
1425 rtt_delete_unack_from_list(list
, cur
);
1428 // check #3 - did we ACK the left-hand side of the current unack?
1429 // (if so, we can just modify it and move on)
1430 if (left_end_acked
) { // and right_end_not_acked
1433 x_vals
.append(cur
->seqno
);
1435 x_vals
.append(cur
->time
);
1437 rtt
.append((rt_val
- cur
->time
) * 1000.0);
1438 // in this case, "right" marks the start of remaining bytes
1442 // check #4 - did we ACK the right-hand side of the current unack?
1443 // (if so, we can just modify it and move on)
1444 if (right_end_acked
) { // and left_end_not_acked
1445 // ACK the right end
1447 x_vals
.append(left
);
1449 x_vals
.append(cur
->time
);
1451 rtt
.append((rt_val
- cur
->time
) * 1000.0);
1452 // in this case, "left" is just beyond the remaining bytes
1453 cur
->end_seqno
= left
;
1456 // at this point, we know:
1457 // - the SACK block does intersect this unack, but
1458 // - it does not intersect the left or right endpoints
1459 // Therefore, it must intersect the middle, so we must split the unack
1460 // into left and right unacked segments:
1461 // ACK the SACK block
1463 x_vals
.append(left
);
1465 x_vals
.append(cur
->time
);
1467 rtt
.append((rt_val
- cur
->time
) * 1000.0);
1468 // then split cur into two unacked segments
1469 // (linking the right-hand unack after the left)
1470 cur
->next
= rtt_get_new_unack(cur
->time
, right
, cur
->end_seqno
- right
);
1471 cur
->next
->next
= next
;
1472 cur
->end_seqno
= left
;
1477 void TCPStreamDialog::fillRoundTripTime()
1479 QString dlg_title
= tr("Round Trip Time") + streamDescription();
1480 setWindowTitle(dlg_title
);
1481 title_
->setText(dlg_title
);
1483 QCustomPlot
*sp
= ui
->streamPlot
;
1484 bool bySeqNumber
= ui
->bySeqNumberCheckBox
->isChecked();
1487 sequence_num_map_
.clear();
1488 sp
->xAxis
->setLabel(sequence_number_label_
);
1489 sp
->xAxis
->setNumberFormat("f");
1490 sp
->xAxis
->setNumberPrecision(0);
1492 sp
->yAxis
->setLabel(round_trip_time_ms_label_
);
1493 sp
->yAxis
->setNumberFormat("gb");
1494 sp
->yAxis
->setNumberPrecision(3);
1496 base_graph_
->setLineStyle(QCPGraph::lsLine
);
1498 QVector
<double> x_vals
, rtt
;
1499 uint32_t seq_base
= 0;
1500 struct rtt_unack
*unack_list
= NULL
, *u
= NULL
;
1501 for (struct segment
*seg
= graph_
.segments
; seg
!= NULL
; seg
= seg
->next
) {
1502 // XXX - Should this just use seq_offset_? Our comparisons are
1503 // wraparound now and should be fine without computing a base
1504 // (we're not doing anything to extend sequence numbers to handle
1505 // connections longer than 4 GiB), and that would let the user swap.
1506 // (We should make clicking the X axis swap seq_origin_zero_ if
1507 // bySeqNumber is checked.)
1508 if (compareHeaders(seg
)) {
1509 seq_base
= seg
->th_seq
;
1513 for (struct segment
*seg
= graph_
.segments
; seg
!= NULL
; seg
= seg
->next
) {
1514 if (compareHeaders(seg
)) {
1515 uint32_t seqno
= seg
->th_seq
- seq_base
;
1516 if (seg
->th_seglen
&& !rtt_is_retrans(unack_list
, seqno
)) {
1517 double rt_val
= seg
->rel_secs
+ seg
->rel_usecs
/ 1000000.0;
1518 rt_val
-= ts_offset_
;
1519 u
= rtt_get_new_unack(rt_val
, seqno
, seg
->th_seglen
);
1521 // make sure to free list before returning!
1522 rtt_destroy_unack_list(&unack_list
);
1525 rtt_put_unack_on_list(&unack_list
, u
);
1528 uint32_t ack_no
= seg
->th_ack
- seq_base
;
1529 double rt_val
= seg
->rel_secs
+ seg
->rel_usecs
/ 1000000.0;
1530 rt_val
-= ts_offset_
;
1531 struct rtt_unack
*v
;
1533 for (u
= unack_list
; u
; u
= v
) {
1534 if (tcp_seq_after(ack_no
, u
->seqno
)) {
1535 // full or partial ack of seg by ack_no
1537 x_vals
.append(u
->seqno
);
1538 sequence_num_map_
.insert(u
->seqno
, seg
);
1540 x_vals
.append(u
->time
);
1542 rtt
.append((rt_val
- u
->time
) * 1000.0);
1543 if (tcp_seq_eq_or_after(ack_no
, u
->end_seqno
)) {
1544 // fully acked segment - nothing more to see here
1546 rtt_delete_unack_from_list(&unack_list
, u
);
1547 // no need to compare SACK blocks for fully ACKed seg
1550 // partial ack of GSO seg
1552 // (keep going - still need to compare SACK blocks...)
1556 // selectively acking u more than once
1557 // can shatter it into multiple intervals.
1558 // If we link those back into the list between u and v,
1559 // then each subsequent SACK selectively ACKs that range.
1560 for (int i
= 0; i
< seg
->num_sack_ranges
; ++i
) {
1561 uint32_t left
= seg
->sack_left_edge
[i
] - seq_base
;
1562 uint32_t right
= seg
->sack_right_edge
[i
] - seq_base
;
1563 u
= rtt_selectively_ack_range(x_vals
, bySeqNumber
, rtt
,
1565 left
, right
, rt_val
);
1566 // if range is empty after selective ack, we can
1567 // skip the rest of the SACK blocks
1573 // it's possible there's still unacked segs - so be sure to free list!
1574 rtt_destroy_unack_list(&unack_list
);
1575 base_graph_
->setData(x_vals
, rtt
);
1578 void TCPStreamDialog::fillWindowScale()
1580 QString dlg_title
= tr("Window Scaling") + streamDescription();
1581 setWindowTitle(dlg_title
);
1582 title_
->setText(dlg_title
);
1584 QCustomPlot
*sp
= ui
->streamPlot
;
1585 // use base_graph_ to represent unacked window size
1586 // (estimate of congestion window)
1587 base_graph_
->setLineStyle(QCPGraph::lsStepLeft
);
1588 // use rwin_graph_ here to show rwin window scale
1589 // (derived from ACK packets)
1590 base_graph_
->setVisible(ui
->showBytesOutCheckBox
->isChecked());
1591 rwin_graph_
->setVisible(ui
->showRcvWinCheckBox
->isChecked());
1593 QVector
<double> rel_time
, win_size
;
1594 QVector
<double> cwnd_time
, cwnd_size
;
1595 uint32_t last_ack
= 0;
1597 /* highest expected SEQ seen so far */
1598 uint32_t max_next_seq
= 0;
1600 bool found_first_ack
= false;
1601 for (struct segment
*seg
= graph_
.segments
; seg
!= NULL
; seg
= seg
->next
) {
1602 double ts
= seg
->rel_secs
+ seg
->rel_usecs
/ 1000000.0;
1604 // The receive window that applies to this flow comes
1605 // from packets in the opposite direction
1606 if (compareHeaders(seg
)) {
1607 /* compute bytes_in_flight for cwnd graph,
1608 * by comparing the highest next SEQ to the latest ACK
1610 uint32_t end_seq
= seg
->th_seq
+ seg
->th_seglen
;
1611 if(end_seq
> max_next_seq
) {
1612 max_next_seq
= end_seq
;
1614 if (found_first_ack
&&
1615 tcp_seq_eq_or_after(end_seq
, last_ack
)) {
1616 cwnd_time
.append(ts
- ts_offset_
);
1617 cwnd_size
.append((double)(max_next_seq
- last_ack
));
1620 // packet in opposite direction - has advertised rwin
1621 uint16_t flags
= seg
->th_flags
;
1623 if ((flags
& (TH_SYN
|TH_RST
)) == 0) {
1624 rel_time
.append(ts
- ts_offset_
);
1625 win_size
.append(seg
->th_win
);
1627 if ((flags
& (TH_ACK
)) != 0) {
1628 // use this to update last_ack
1629 if (!found_first_ack
||
1630 tcp_seq_eq_or_after(seg
->th_ack
, last_ack
)) {
1631 last_ack
= seg
->th_ack
;
1632 found_first_ack
= true;
1637 /* base_graph_ is the one that the tracer is on and allows selecting
1638 * segments. XXX - Is the congestion window more interesting to see
1639 * the exact value and select?
1641 * We'll put the graphs on the same axis so they'll use the same scale.
1643 base_graph_
->setData(cwnd_time
, cwnd_size
);
1644 rwin_graph_
->setValueAxis(sp
->yAxis
);
1645 rwin_graph_
->setData(rel_time
, win_size
);
1647 /* The left axis has the color and label for the unacked bytes,
1648 * and the right axis will have the color and label for the window size.
1650 sp
->yAxis
->setLabel(cwnd_label_
);
1651 sp
->yAxis2
->setLabel(window_size_label_
);
1652 sp
->yAxis2
->setLabelColor(QColor(graph_color_3
));
1653 sp
->yAxis2
->setTickLabelColor(QColor(graph_color_3
));
1654 sp
->yAxis2
->setVisible(true);
1656 /* Keep the ticks on the two axes in sync. */
1657 connect(sp
->yAxis
, QOverload
<const QCPRange
&>::of(&QCPAxis::rangeChanged
), sp
->yAxis2
, QOverload
<const QCPRange
&>::of(&QCPAxis::setRange
));
1660 QString
TCPStreamDialog::streamDescription()
1662 QString
description(tr(" for %1:%2 %3 %4:%5")
1663 .arg(address_to_qstring(&graph_
.src_address
))
1664 .arg(graph_
.src_port
)
1665 .arg(UTF8_RIGHTWARDS_ARROW
)
1666 .arg(address_to_qstring(&graph_
.dst_address
))
1667 .arg(graph_
.dst_port
));
1671 bool TCPStreamDialog::compareHeaders(segment
*seg
)
1673 return (compare_headers(&graph_
.src_address
, &graph_
.dst_address
,
1674 graph_
.src_port
, graph_
.dst_port
,
1675 &seg
->ip_src
, &seg
->ip_dst
,
1676 seg
->th_sport
, seg
->th_dport
,
1680 void TCPStreamDialog::toggleTracerStyle(bool force_default
)
1682 if (!tracer_
->visible() && !force_default
) return;
1684 QPen sp_pen
= ui
->streamPlot
->graph(0)->pen();
1685 QCPItemTracer::TracerStyle tstyle
= QCPItemTracer::tsCrosshair
;
1686 QPen tr_pen
= QPen(tracer_
->pen());
1687 QColor tr_color
= sp_pen
.color();
1689 if (force_default
|| tracer_
->style() != QCPItemTracer::tsCircle
) {
1690 tstyle
= QCPItemTracer::tsCircle
;
1691 tr_color
.setAlphaF(1.0);
1692 tr_pen
.setWidthF(1.5);
1694 tr_color
.setAlphaF(0.5);
1695 tr_pen
.setWidthF(1.0);
1698 tracer_
->setStyle(tstyle
);
1699 tr_pen
.setColor(tr_color
);
1700 tracer_
->setPen(tr_pen
);
1701 ui
->streamPlot
->replot();
1704 QRectF
TCPStreamDialog::getZoomRanges(QRect zoom_rect
)
1706 QRectF zoom_ranges
= QRectF();
1708 QCustomPlot
*sp
= ui
->streamPlot
;
1709 QRect zr
= zoom_rect
.normalized();
1711 if (zr
.width() < min_zoom_pixels_
&& zr
.height() < min_zoom_pixels_
) {
1715 QRect ar
= sp
->axisRect()->rect();
1716 if (ar
.intersects(zr
)) {
1717 QRect zsr
= ar
.intersected(zr
);
1718 zoom_ranges
.setX(sp
->xAxis
->range().lower
1719 + sp
->xAxis
->range().size() * (zsr
.left() - ar
.left()) / ar
.width());
1720 zoom_ranges
.setWidth(sp
->xAxis
->range().size() * zsr
.width() / ar
.width());
1723 zoom_ranges
.setY(sp
->yAxis
->range().lower
1724 + sp
->yAxis
->range().size() * (ar
.bottom() - zsr
.bottom()) / ar
.height());
1725 zoom_ranges
.setHeight(sp
->yAxis
->range().size() * zsr
.height() / ar
.height());
1730 void TCPStreamDialog::showContextMenu(const QPoint
& pos
)
1732 ctx_menu_
.popup(ui
->streamPlot
->mapToGlobal(pos
));
1735 void TCPStreamDialog::graphClicked(QMouseEvent
*event
)
1737 QCustomPlot
*sp
= ui
->streamPlot
;
1739 // mouse press on graph should reset focus to graph
1743 if (sp
->axisRect()->rect().contains(event
->pos())) {
1744 sp
->setCursor(QCursor(Qt::ClosedHandCursor
));
1746 on_actionGoToPacket_triggered();
1748 if (!rubber_band_
) {
1749 rubber_band_
= new QRubberBand(QRubberBand::Rectangle
, sp
);
1751 rb_origin_
= event
->pos();
1752 rubber_band_
->setGeometry(QRect(rb_origin_
, QSize()));
1753 rubber_band_
->show();
1757 void TCPStreamDialog::axisClicked(QCPAxis
*axis
, QCPAxis::SelectablePart
, QMouseEvent
*)
1759 QCustomPlot
*sp
= ui
->streamPlot
;
1761 if (axis
== sp
->xAxis
) {
1762 switch (graph_
.type
) {
1763 case GRAPH_THROUGHPUT
:
1764 case GRAPH_TSEQ_STEVENS
:
1765 case GRAPH_TSEQ_TCPTRACE
:
1768 ts_origin_conn_
= ts_origin_conn_
? false : true;
1774 } else if (axis
== sp
->yAxis
) {
1775 switch (graph_
.type
) {
1776 case GRAPH_TSEQ_STEVENS
:
1777 case GRAPH_TSEQ_TCPTRACE
:
1778 seq_origin_zero_
= seq_origin_zero_
? false : true;
1787 // Setting mouseTracking on our streamPlot may not be as reliable
1788 // as we need. If it's not we might want to poll the mouse position
1789 // using a QTimer instead.
1790 void TCPStreamDialog::mouseMoved(QMouseEvent
*event
)
1792 QCustomPlot
*sp
= ui
->streamPlot
;
1793 Qt::CursorShape shape
= Qt::ArrowCursor
;
1795 if (event
->buttons().testFlag(Qt::LeftButton
)) {
1797 shape
= Qt::ClosedHandCursor
;
1799 shape
= Qt::CrossCursor
;
1802 if (sp
->axisRect()->rect().contains(event
->pos())) {
1804 shape
= Qt::OpenHandCursor
;
1806 shape
= Qt::CrossCursor
;
1811 sp
->setCursor(QCursor(shape
));
1813 QString hint
= "<small><i>";
1815 double tr_key
= tracer_
->position
->key();
1816 struct segment
*packet_seg
= NULL
;
1819 // XXX If we have multiple packets with the same timestamp tr_key
1820 // may not return the packet we want. It might be possible to fudge
1821 // unique keys using nextafter().
1822 if (event
&& tracer_
->graph() && tracer_
->position
->axisRect()->rect().contains(event
->pos())) {
1823 switch (graph_
.type
) {
1824 case GRAPH_TSEQ_STEVENS
:
1825 case GRAPH_TSEQ_TCPTRACE
:
1826 case GRAPH_THROUGHPUT
:
1828 packet_seg
= time_stamp_map_
.value(tr_key
, NULL
);
1831 if (ui
->bySeqNumberCheckBox
->isChecked())
1832 packet_seg
= sequence_num_map_
.value(tr_key
, NULL
);
1834 packet_seg
= time_stamp_map_
.value(tr_key
, NULL
);
1841 tracer_
->setVisible(false);
1842 hint
+= "Hover over the graph for details. " + stream_desc_
+ "</i></small>";
1843 ui
->hintLabel
->setText(hint
);
1844 ui
->streamPlot
->replot(QCustomPlot::rpQueuedReplot
);
1848 tracer_
->setVisible(true);
1849 packet_num_
= packet_seg
->num
;
1850 // XXX - We should probably change the sequence number displayed by
1851 // seq_offset_ but in that case we should also store a base sequence
1852 // number for the other direction so the th_ack can also be adjusted
1853 // to a relative sequence number.
1854 hint
+= tr("%1 %2 (%3s len %4 seq %5 ack %6 win %7)")
1855 .arg(cap_file_
? tr("Click to select packet") : tr("Packet"))
1857 .arg(QString::number(packet_seg
->rel_secs
+ packet_seg
->rel_usecs
/ 1000000.0, 'g', 4))
1858 .arg(packet_seg
->th_seglen
)
1859 .arg(packet_seg
->th_seq
) // - seq_offset_)
1860 .arg(packet_seg
->th_ack
)
1861 .arg(packet_seg
->th_win
);
1862 tracer_
->setGraphKey(ui
->streamPlot
->xAxis
->pixelToCoord(event
->pos().x()));
1863 sp
->replot(QCustomPlot::rpQueuedReplot
);
1865 if (rubber_band_
&& rubber_band_
->isVisible() && event
) {
1866 rubber_band_
->setGeometry(QRect(rb_origin_
, event
->pos()).normalized());
1867 QRectF zoom_ranges
= getZoomRanges(QRect(rb_origin_
, event
->pos()));
1868 if (zoom_ranges
.width() > 0.0 && zoom_ranges
.height() > 0.0) {
1869 hint
+= tr("Release to zoom, x = %1 to %2, y = %3 to %4")
1870 .arg(zoom_ranges
.x())
1871 .arg(zoom_ranges
.x() + zoom_ranges
.width())
1872 .arg(zoom_ranges
.y())
1873 .arg(zoom_ranges
.y() + zoom_ranges
.height());
1875 hint
+= tr("Unable to select range.");
1878 hint
+= tr("Click to select a portion of the graph.");
1881 hint
+= " " + stream_desc_
+ "</i></small>";
1882 ui
->hintLabel
->setText(hint
);
1885 void TCPStreamDialog::mouseReleased(QMouseEvent
*event
)
1887 if (rubber_band_
&& rubber_band_
->isVisible()) {
1888 rubber_band_
->hide();
1889 if (!mouse_drags_
) {
1890 QRectF zoom_ranges
= getZoomRanges(QRect(rb_origin_
, event
->pos()));
1891 if (zoom_ranges
.width() > 0.0 && zoom_ranges
.height() > 0.0) {
1892 QCustomPlot
*sp
= ui
->streamPlot
;
1893 sp
->xAxis
->setRangeLower(zoom_ranges
.x());
1894 sp
->xAxis
->setRangeUpper(zoom_ranges
.x() + zoom_ranges
.width());
1895 sp
->yAxis
->setRangeLower(zoom_ranges
.y());
1896 sp
->yAxis
->setRangeUpper(zoom_ranges
.y() + zoom_ranges
.height());
1900 } else if (ui
->streamPlot
->cursor().shape() == Qt::ClosedHandCursor
) {
1901 ui
->streamPlot
->setCursor(QCursor(Qt::OpenHandCursor
));
1905 void TCPStreamDialog::transformYRange(const QCPRange
&y_range1
)
1907 if (y_axis_xfrm_
.isIdentity()) return;
1909 QCustomPlot
*sp
= ui
->streamPlot
;
1910 QLineF yp1
= QLineF(1.0, y_range1
.lower
, 1.0, y_range1
.upper
);
1911 QLineF yp2
= y_axis_xfrm_
.map(yp1
);
1913 sp
->yAxis2
->setRangeUpper(yp2
.y2());
1914 sp
->yAxis2
->setRangeLower(yp2
.y1());
1917 // XXX - We have similar code in io_graph_dialog and packet_diagram. Should this be a common routine?
1918 void TCPStreamDialog::on_buttonBox_accepted()
1920 QString file_name
, extension
;
1921 QDir
path(mainApp
->openDialogInitialDir());
1922 QString pdf_filter
= tr("Portable Document Format (*.pdf)");
1923 QString png_filter
= tr("Portable Network Graphics (*.png)");
1924 QString bmp_filter
= tr("Windows Bitmap (*.bmp)");
1925 // Gaze upon my beautiful graph with lossy artifacts!
1926 QString jpeg_filter
= tr("JPEG File Interchange Format (*.jpeg *.jpg)");
1927 QString filter
= QStringLiteral("%1;;%2;;%3;;%4")
1933 file_name
= WiresharkFileDialog::getSaveFileName(this, mainApp
->windowTitleString(tr("Save Graph As…")),
1934 path
.canonicalPath(), filter
, &extension
);
1936 if (file_name
.length() > 0) {
1937 bool save_ok
= false;
1938 if (extension
.compare(pdf_filter
) == 0) {
1939 save_ok
= ui
->streamPlot
->savePdf(file_name
);
1940 } else if (extension
.compare(png_filter
) == 0) {
1941 save_ok
= ui
->streamPlot
->savePng(file_name
);
1942 } else if (extension
.compare(bmp_filter
) == 0) {
1943 save_ok
= ui
->streamPlot
->saveBmp(file_name
);
1944 } else if (extension
.compare(jpeg_filter
) == 0) {
1945 save_ok
= ui
->streamPlot
->saveJpg(file_name
);
1947 // else error dialog?
1949 mainApp
->setLastOpenDirFromFilename(file_name
);
1954 void TCPStreamDialog::on_graphTypeComboBox_currentIndexChanged(int index
)
1956 if (index
< 0) return;
1957 graph_
.type
= static_cast<tcp_graph_type
>(ui
->graphTypeComboBox
->itemData(index
).toInt());
1958 showWidgetsForGraphType();
1960 fillGraph(/*reset_axes=*/true, /*set_focus=*/false);
1963 void TCPStreamDialog::on_resetButton_clicked()
1968 void TCPStreamDialog::setCaptureFile(capture_file
*cf
)
1970 if (!cf
) { // We only want to know when the file closes.
1975 void TCPStreamDialog::updateGraph()
1977 graph_updater_
.doUpdate();
1980 void TCPStreamDialog::on_streamNumberSpinBox_valueChanged(int new_stream
)
1982 if (new_stream
>= 0 && new_stream
< int(get_tcp_stream_count())) {
1983 graph_updater_
.triggerUpdate(1000, /*reset_axes =*/true);
1987 void TCPStreamDialog::on_streamNumberSpinBox_editingFinished()
1992 void TCPStreamDialog::on_maWindowSizeSpinBox_valueChanged(double new_ma_size
)
1994 if (new_ma_size
> 0.0) {
1995 ma_window_size_
= new_ma_size
;
1996 graph_updater_
.triggerUpdate(1000, /*reset_axes =*/false);
2000 void TCPStreamDialog::on_maWindowSizeSpinBox_editingFinished()
2005 void TCPStreamDialog::on_selectSACKsCheckBox_stateChanged(int /* state */)
2007 fillGraph(/*reset_axes=*/false, /*set_focus=*/false);
2010 void TCPStreamDialog::on_otherDirectionButton_clicked()
2012 on_actionSwitchDirection_triggered();
2015 void TCPStreamDialog::on_dragRadioButton_toggled(bool checked
)
2018 mouse_drags_
= true;
2019 if (rubber_band_
&& rubber_band_
->isVisible())
2020 rubber_band_
->hide();
2021 ui
->streamPlot
->setInteractions(
2028 void TCPStreamDialog::on_zoomRadioButton_toggled(bool checked
)
2031 mouse_drags_
= false;
2032 ui
->streamPlot
->setInteractions(QCP::Interactions());
2036 void TCPStreamDialog::on_bySeqNumberCheckBox_stateChanged(int /* state */)
2038 fillGraph(/*reset_axes=*/true, /*set_focus=*/false);
2041 void TCPStreamDialog::on_showSegLengthCheckBox_stateChanged(int state
)
2043 bool visible
= (state
!= 0);
2044 if (graph_
.type
== GRAPH_THROUGHPUT
&& base_graph_
!= NULL
) {
2045 base_graph_
->setVisible(visible
);
2046 tracer_
->setGraph(visible
? base_graph_
: NULL
);
2047 ui
->streamPlot
->replot();
2051 void TCPStreamDialog::on_showThroughputCheckBox_stateChanged(int state
)
2053 bool visible
= (state
!= 0);
2054 if (graph_
.type
== GRAPH_THROUGHPUT
&& tput_graph_
!= NULL
) {
2055 tput_graph_
->setVisible(visible
);
2056 ui
->streamPlot
->replot();
2060 void TCPStreamDialog::on_showGoodputCheckBox_stateChanged(int state
)
2062 bool visible
= (state
!= 0);
2063 if (graph_
.type
== GRAPH_THROUGHPUT
&& goodput_graph_
!= NULL
) {
2064 goodput_graph_
->setVisible(visible
);
2065 ui
->streamPlot
->replot();
2069 void TCPStreamDialog::on_showRcvWinCheckBox_stateChanged(int state
)
2071 bool visible
= (state
!= 0);
2072 if (graph_
.type
== GRAPH_WSCALE
&& rwin_graph_
!= NULL
) {
2073 rwin_graph_
->setVisible(visible
);
2074 ui
->streamPlot
->replot();
2078 void TCPStreamDialog::on_showBytesOutCheckBox_stateChanged(int state
)
2080 bool visible
= (state
!= 0);
2081 if (graph_
.type
== GRAPH_WSCALE
&& base_graph_
!= NULL
) {
2082 base_graph_
->setVisible(visible
);
2083 tracer_
->setGraph(visible
? base_graph_
: NULL
);
2084 ui
->streamPlot
->replot();
2088 void TCPStreamDialog::on_actionZoomIn_triggered()
2093 void TCPStreamDialog::on_actionZoomInX_triggered()
2098 void TCPStreamDialog::on_actionZoomInY_triggered()
2103 void TCPStreamDialog::on_actionZoomOut_triggered()
2108 void TCPStreamDialog::on_actionZoomOutX_triggered()
2113 void TCPStreamDialog::on_actionZoomOutY_triggered()
2118 void TCPStreamDialog::on_actionReset_triggered()
2120 on_resetButton_clicked();
2123 void TCPStreamDialog::on_actionMoveRight10_triggered()
2128 void TCPStreamDialog::on_actionMoveLeft10_triggered()
2133 void TCPStreamDialog::on_actionMoveUp10_triggered()
2138 void TCPStreamDialog::on_actionMoveDown10_triggered()
2143 void TCPStreamDialog::on_actionMoveRight1_triggered()
2148 void TCPStreamDialog::on_actionMoveLeft1_triggered()
2153 void TCPStreamDialog::on_actionMoveUp1_triggered()
2158 void TCPStreamDialog::on_actionMoveDown1_triggered()
2163 void TCPStreamDialog::on_actionNextStream_triggered()
2165 if (int(graph_
.stream
) < int(get_tcp_stream_count()) - 1) {
2166 ui
->streamNumberSpinBox
->setValue(graph_
.stream
+ 1);
2171 void TCPStreamDialog::on_actionPreviousStream_triggered()
2173 if (graph_
.stream
> 0) {
2174 ui
->streamNumberSpinBox
->setValue(graph_
.stream
- 1);
2179 void TCPStreamDialog::on_actionSwitchDirection_triggered()
2184 copy_address(&tmp_addr
, &graph_
.src_address
);
2185 tmp_port
= graph_
.src_port
;
2186 free_address(&graph_
.src_address
);
2187 copy_address(&graph_
.src_address
, &graph_
.dst_address
);
2188 graph_
.src_port
= graph_
.dst_port
;
2189 free_address(&graph_
.dst_address
);
2190 copy_address(&graph_
.dst_address
, &tmp_addr
);
2191 graph_
.dst_port
= tmp_port
;
2192 free_address(&tmp_addr
);
2194 fillGraph(/*reset_axes=*/true, /*set_focus=*/false);
2197 void TCPStreamDialog::on_actionGoToPacket_triggered()
2199 if (tracer_
->visible() && cap_file_
&& packet_num_
> 0) {
2200 emit
goToPacket(packet_num_
);
2204 void TCPStreamDialog::on_actionDragZoom_triggered()
2207 ui
->zoomRadioButton
->toggle();
2209 ui
->dragRadioButton
->toggle();
2213 void TCPStreamDialog::on_actionToggleSequenceNumbers_triggered()
2215 seq_origin_zero_
= seq_origin_zero_
? false : true;
2219 void TCPStreamDialog::on_actionToggleTimeOrigin_triggered()
2221 ts_origin_conn_
= ts_origin_conn_
? false : true;
2225 void TCPStreamDialog::on_actionRoundTripTime_triggered()
2227 ui
->graphTypeComboBox
->setCurrentIndex(ui
->graphTypeComboBox
->findData(GRAPH_RTT
));
2230 void TCPStreamDialog::on_actionThroughput_triggered()
2232 ui
->graphTypeComboBox
->setCurrentIndex(ui
->graphTypeComboBox
->findData(GRAPH_THROUGHPUT
));
2235 void TCPStreamDialog::on_actionStevens_triggered()
2237 ui
->graphTypeComboBox
->setCurrentIndex(ui
->graphTypeComboBox
->findData(GRAPH_TSEQ_STEVENS
));
2240 void TCPStreamDialog::on_actionTcptrace_triggered()
2242 ui
->graphTypeComboBox
->setCurrentIndex(ui
->graphTypeComboBox
->findData(GRAPH_TSEQ_TCPTRACE
));
2245 void TCPStreamDialog::on_actionWindowScaling_triggered()
2247 ui
->graphTypeComboBox
->setCurrentIndex(ui
->graphTypeComboBox
->findData(GRAPH_WSCALE
));
2250 void TCPStreamDialog::GraphUpdater::triggerUpdate(int timeout
, bool reset_axes
)
2252 if (!hasPendingUpdate()) {
2253 graph_update_timer_
= new QTimer(dialog_
);
2254 graph_update_timer_
->setSingleShot(true);
2255 dialog_
->connect(graph_update_timer_
, SIGNAL(timeout()), dialog_
, SLOT(updateGraph()));
2257 reset_axes_
= (reset_axes_
|| reset_axes
);
2258 graph_update_timer_
->start(timeout
);
2261 void TCPStreamDialog::GraphUpdater::clearPendingUpdate()
2263 if (hasPendingUpdate()) {
2264 if (graph_update_timer_
->isActive())
2265 graph_update_timer_
->stop();
2266 delete graph_update_timer_
;
2267 graph_update_timer_
= NULL
;
2268 reset_axes_
= false;
2272 void TCPStreamDialog::GraphUpdater::doUpdate()
2274 if (hasPendingUpdate()) {
2275 bool reset_axes
= reset_axes_
;
2276 clearPendingUpdate();
2277 // if the stream has changed, update the data here
2278 int new_stream
= dialog_
->ui
->streamNumberSpinBox
->value();
2279 if ((int(dialog_
->graph_
.stream
) != new_stream
) &&
2280 (new_stream
>= 0 && new_stream
< int(get_tcp_stream_count()))) {
2281 dialog_
->graph_
.stream
= new_stream
;
2282 dialog_
->findStream();
2284 dialog_
->fillGraph(reset_axes
, /*set_focus =*/false);
2288 void TCPStreamDialog::on_buttonBox_helpRequested()
2290 mainApp
->helpTopicAction(HELP_STATS_TCP_STREAM_GRAPHS_DIALOG
);