update epan/dissectors/pidl/drsuapi/drsuapi.idl from samba
[wireshark-sm.git] / ui / qt / tcp_stream_dialog.cpp
blob14f73c5f6a619e5b23e73142dda07c29f9df35f1
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
8 */
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
15 #include <vector>
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"
29 #include <QCursor>
30 #include <QDir>
31 #include <QIcon>
32 #include <QPushButton>
34 #include <QDebug>
36 // To do:
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
42 // - Add UDP graphs
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.
53 #define MA_1_SECOND
55 #ifndef MA_1_SECOND
56 const int moving_avg_period_ = 20;
57 #endif
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
69 // in zoom mode.
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
91 Q_UNUSED(pos);
92 Q_UNUSED(onlySelectable);
93 Q_UNUSED(details);
94 return -1.0;
97 TCPStreamDialog::TCPStreamDialog(QWidget *parent, capture_file *cf, tcp_graph_type graph_type) :
98 GeometryStateDialog(parent),
99 ui(new Ui::TCPStreamDialog),
100 cap_file_(cf),
101 ts_offset_(0),
102 ts_origin_conn_(true),
103 seq_offset_(0),
104 seq_origin_zero_(true),
105 title_(nullptr),
106 base_graph_(nullptr),
107 tput_graph_(nullptr),
108 goodput_graph_(nullptr),
109 seg_graph_(nullptr),
110 seg_eb_(nullptr),
111 ack_graph_(nullptr),
112 sack_graph_(nullptr),
113 sack_eb_(nullptr),
114 sack2_graph_(nullptr),
115 sack2_eb_(nullptr),
116 rwin_graph_(nullptr),
117 dup_ack_graph_(nullptr),
118 zero_win_graph_(nullptr),
119 tracer_(nullptr),
120 packet_num_(0),
121 mouse_drags_(true),
122 rubber_band_(nullptr),
123 graph_updater_(this),
124 num_dsegs_(-1),
125 num_acks_(-1),
126 num_sack_ranges_(-1),
127 ma_window_size_(1.0)
129 int graph_idx = -1;
131 memset(&graph_, 0, sizeof(graph_));
133 ui->setupUi(this);
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);
142 return;
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;
203 findStream();
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);
212 #ifdef MA_1_SECOND
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);
218 #endif
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.
309 int tick_len = 3;
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);
314 QPen da_tick_pen;
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);
336 else
337 // the current index is what we want - so fillGraph() manually
338 fillGraph();
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);
352 if (close_bt) {
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_);
371 delete ui;
374 void TCPStreamDialog::showEvent(QShowEvent *)
376 resetAxes();
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
387 // here ]
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();
393 event->accept();
394 return;
397 // XXX - This differs from the main window but matches other applications (e.g. Mozilla and Safari)
398 switch(event->key()) {
399 case Qt::Key_Minus:
400 case Qt::Key_Underscore: // Shifted minus on U.S. keyboards
401 case Qt::Key_O: // GTK+
402 zoomAxes(false);
403 break;
404 case Qt::Key_Plus:
405 case Qt::Key_Equal: // Unshifted plus on U.S. keyboards
406 case Qt::Key_I: // GTK+
407 zoomAxes(true);
408 break;
409 case Qt::Key_X: // Zoom X axis only
410 if (event->modifiers() & Qt::ShiftModifier) {
411 zoomXAxis(false); // upper case X -> Zoom out
412 } else {
413 zoomXAxis(true); // lower case x -> Zoom in
415 break;
416 case Qt::Key_Y: // Zoom Y axis only
417 if (event->modifiers() & Qt::ShiftModifier) {
418 zoomYAxis(false); // upper case Y -> Zoom out
419 } else {
420 zoomYAxis(true); // lower case y -> Zoom in
422 break;
423 case Qt::Key_Right:
424 case Qt::Key_L:
425 panAxes(pan_pixels, 0);
426 break;
427 case Qt::Key_Left:
428 case Qt::Key_H:
429 panAxes(-1 * pan_pixels, 0);
430 break;
431 case Qt::Key_Up:
432 case Qt::Key_K:
433 panAxes(0, pan_pixels);
434 break;
435 case Qt::Key_Down:
436 case Qt::Key_J:
437 panAxes(0, -1 * pan_pixels);
438 break;
440 case Qt::Key_Space:
441 toggleTracerStyle();
442 break;
444 case Qt::Key_0:
445 case Qt::Key_ParenRight: // Shifted 0 on U.S. keyboards
446 case Qt::Key_R:
447 case Qt::Key_Home:
448 resetAxes();
449 break;
451 case Qt::Key_PageUp:
452 on_actionNextStream_triggered();
453 break;
454 case Qt::Key_PageDown:
455 on_actionPreviousStream_triggered();
456 break;
458 case Qt::Key_D:
459 on_actionSwitchDirection_triggered();
460 break;
461 case Qt::Key_G:
462 on_actionGoToPacket_triggered();
463 break;
464 case Qt::Key_S:
465 on_actionToggleSequenceNumbers_triggered();
466 break;
467 case Qt::Key_T:
468 on_actionToggleTimeOrigin_triggered();
469 break;
470 case Qt::Key_Z:
471 on_actionDragZoom_triggered();
472 break;
474 case Qt::Key_1:
475 on_actionRoundTripTime_triggered();
476 break;
477 case Qt::Key_2:
478 on_actionThroughput_triggered();
479 break;
480 case Qt::Key_3:
481 on_actionStevens_triggered();
482 break;
483 case Qt::Key_4:
484 on_actionTcptrace_triggered();
485 break;
486 case Qt::Key_5:
487 on_actionWindowScaling_triggered();
488 break;
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();
500 event->accept();
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));
565 if (!cap_file_) {
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());
571 sp->replot();
572 return;
575 ts_offset_ = 0;
576 seq_offset_ = 0;
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;
584 int pkts_fwd = 0;
585 int pkts_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)
592 bool insert = true;
593 if (!compareHeaders(seg)) {
594 bytes_rev += seg->th_seglen;
595 pkts_rev++;
596 // only insert reverse packets if SACK present
597 insert = (seg->num_sack_ranges != 0);
598 } else {
599 bytes_fwd += seg->th_seglen;
600 pkts_fwd++;
602 double ts = seg->rel_secs + seg->rel_usecs / 1000000.0;
603 if (ts_unset) {
604 ts_offset_ = ts;
605 ts_unset = false;
607 if (seq_unset) {
608 if (compareHeaders(seg)) {
609 if (seg->th_seq != seg->th_rawseq) {
610 seq_offset_ = seg->th_seq - seg->th_rawseq;
611 } else {
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;
616 } else {
617 seq_offset_ = seg->th_seq - 1;
620 seq_unset = false;
621 } else {
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;
628 } else {
629 seq_offset_ -= seg->th_ack - 1;
631 seq_unset = false;
635 if (insert) {
636 time_stamp_map_.insert(ts - ts_offset_, seg);
640 switch (graph_.type) {
641 case GRAPH_TSEQ_STEVENS:
642 fillStevens();
643 break;
644 case GRAPH_TSEQ_TCPTRACE:
645 fillTcptrace();
646 break;
647 case GRAPH_THROUGHPUT:
648 fillThroughput();
649 break;
650 case GRAPH_RTT:
651 fillRoundTripTime();
652 break;
653 case GRAPH_WSCALE:
654 fillWindowScale();
655 break;
656 default:
657 break;
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)));
668 mouseMoved(NULL);
669 if (reset_axes)
670 resetAxes();
671 else
672 sp->replot();
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.
678 if (set_focus)
679 sp->setFocus();
682 void TCPStreamDialog::showWidgetsForGraphType()
684 if (graph_.type == GRAPH_RTT) {
685 ui->bySeqNumberCheckBox->setVisible(true);
686 } else {
687 ui->bySeqNumberCheckBox->setVisible(false);
689 if (graph_.type == GRAPH_THROUGHPUT) {
690 #ifdef MA_1_SECOND
691 ui->maWindowSizeLabel->setVisible(true);
692 ui->maWindowSizeSpinBox->setVisible(true);
693 #else
694 ui->maWindowSizeLabel->setVisible(false);
695 ui->maWindowSizeSpinBox->setVisible(false);
696 #endif
697 ui->showSegLengthCheckBox->setVisible(true);
698 ui->showThroughputCheckBox->setVisible(true);
699 ui->showGoodputCheckBox->setVisible(true);
700 } else {
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);
710 } else {
711 ui->selectSACKsCheckBox->setVisible(false);
714 if (graph_.type == GRAPH_WSCALE) {
715 ui->showRcvWinCheckBox->setVisible(true);
716 ui->showBytesOutCheckBox->setVisible(true);
717 } else {
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);
729 if (!in) {
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());
736 sp->replot();
739 void TCPStreamDialog::zoomXAxis(bool in)
741 QCustomPlot *sp = ui->streamPlot;
742 double h_factor = sp->axisRect()->rangeZoomFactor(Qt::Horizontal);
744 if (!in) {
745 h_factor = pow(h_factor, -1);
748 sp->xAxis->scaleRange(h_factor, sp->xAxis->range().center());
749 sp->replot();
752 void TCPStreamDialog::zoomYAxis(bool in)
754 QCustomPlot *sp = ui->streamPlot;
755 double v_factor = sp->axisRect()->rangeZoomFactor(Qt::Vertical);
757 if (!in) {
758 v_factor = pow(v_factor, -1);
761 sp->yAxis->scaleRange(v_factor, sp->yAxis->range().center());
762 sp->replot();
765 void TCPStreamDialog::panAxes(int x_pixels, int y_pixels)
767 QCustomPlot *sp = ui->streamPlot;
768 double h_pan = 0.0;
769 double v_pan = 0.0;
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?
774 if (h_pan) {
775 sp->xAxis->moveRange(h_pan);
776 sp->replot();
778 if (v_pan) {
779 sp->yAxis->moveRange(v_pan);
780 sp->replot();
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);
796 // }
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());
812 sp->replot();
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)) {
830 continue;
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
880 pkt_time.append(ts);
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) {
886 sb_time.append(ts);
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);
897 } else {
898 // Reverse direction: ACK + RWIN
899 if (! (seg->th_flags & TH_ACK)) {
900 // SYNs and RSTs do not necessarily have ACKs
901 continue;
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];
907 half = half/2.0;
908 double center = seg->sack_left_edge[i] - seq_offset_ + half;
909 if (i == 0) {
910 sack_time.append(ts);
911 sack_center.append(center);
912 sack_span.append(half);
913 if (allow_sack_select) {
914 pkt_time.append(ts);
915 pkt_seqnums.append(center);
917 } else {
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);
932 ack.append(ackno);
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
957 // fully ACKed.
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
960 // or std::map.
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)
970 static void
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;
988 break;
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);
993 ++unacked;
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;
1012 break;
1014 ++next_new_idx;
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:
1041 // next_new:
1042 // [-------------)
1043 // next_old:
1044 // 1. [---)
1045 // 2. [-------)
1046 // 3. [----------------)
1047 // 4. [---------------------)
1048 // 5. [----------------------------)
1049 // 6. [--------)
1050 // 7. [-------------)
1051 // 8. [--------------------)
1052 // 9. [---)
1053 // 10. [--------)
1054 // 11. [---------------)
1055 // 12. [------)
1056 // 13. [--)
1058 // Case 1: end of next_old is before beginning of next_new
1059 // next_new:
1060 // [-------------) ... <end>
1061 // next_old:
1062 // 1. [---) ... <end>
1063 if (tcp_seq_before(next_old->second, next_new->first)) {
1064 // Actions:
1065 // advance to the next sack in old_sacks
1066 ++next_old;
1067 // retry from the top
1068 continue;
1071 // Case 13: end of next_new is before beginning of next_old
1072 // next_new:
1073 // [-------------) ... <end>
1074 // next_old:
1075 // 13. [--) ... <end>
1076 if (tcp_seq_before(next_new->second, next_old->first)) {
1077 // Actions:
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
1086 ++next_new_idx;
1087 // retry from the top
1088 continue;
1091 // Remaining possible configurations:
1092 // next_new:
1093 // [-------------)
1094 // next_old:
1095 // 2. [-------)
1096 // 3. [----------------)
1097 // 4. [---------------------)
1098 // 5. [----------------------------)
1099 // 6. [--------)
1100 // 7. [-------------)
1101 // 8. [--------------------)
1102 // 9. [---)
1103 // 10. [--------)
1104 // 11. [---------------)
1105 // 12. [------)
1107 // Cases 2,3,6,9: end of next_old is before end of next_new
1108 // next_new:
1109 // [-------------)
1110 // next_old:
1111 // 2. [-------)
1112 // 3. [----------------)
1113 // 6. [--------)
1114 // 9. [---)
1115 // Actions:
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
1129 else {
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
1141 // Leaving:
1143 // Remaining possible configurations:
1144 // next_new:
1145 // [-------------)
1146 // next_old:
1147 // 4. [---------------------)
1148 // 5. [----------------------------)
1149 // 7. [-------------)
1150 // 8. [--------------------)
1151 // 10. [--------)
1152 // 11. [---------------)
1153 // 12. [------)
1155 // Cases 10,11,12: start of next_new is before start of next_old
1156 // next_new:
1157 // [-------------)
1158 // next_old:
1159 // 10. [--------)
1160 // 11. [---------------)
1161 // 12. [------)
1162 if (tcp_seq_before(next_new->first, next_old->first)) {
1163 // Actions:
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
1172 // Leaving:
1174 // Remaining possible configurations:
1175 // next_new:
1176 // [-------------)
1177 // next_old:
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
1186 ++next_new_idx;
1188 // Conditions for leaving loop:
1189 // - we processed all remaining new_sacks - nothing left to do
1190 // (next_new_idx == num_sack_ranges)
1191 // OR
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);
1200 ++next_new_idx;
1203 #endif // USE_SACKS_IN_GOODPUT_CALC
1205 void TCPStreamDialog::fillThroughput()
1207 QString dlg_title = tr("Throughput") + streamDescription();
1208 #ifdef MA_1_SECOND
1209 dlg_title.append(tr(" (MA)"));
1210 #else
1211 dlg_title.append(tr(" (%1 Segment MA)").arg(moving_avg_period_));
1212 #endif
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());
1227 #ifdef MA_1_SECOND
1228 if (!graph_.segments) {
1229 #else
1230 if (!graph_.segments || !graph_.segments->next) {
1231 #endif
1232 dlg_title.append(tr(" [not enough data]"));
1233 return;
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
1247 // fully ACKed.
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;
1265 break;
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.
1276 #ifdef MA_1_SECOND
1277 // NOTE that for the time-based MA case, you certainly can start with the
1278 // first segment!
1279 for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
1280 #else
1281 for (struct segment *seg = graph_.segments->next; seg != NULL; seg = seg->next) {
1282 #endif
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;
1295 } else {
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,
1308 compare_sack);
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,
1314 old_sacks);
1315 #endif // USE_SACKS_IN_GOODPUT_CALC
1316 } else {
1317 seglen = 0;
1321 r_pkt_times.append(ts);
1322 r_lens.append(seglen);
1324 #ifdef MA_1_SECOND
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_);
1331 r_oldest++;
1333 #else
1334 if (r_lens.size() > moving_avg_period_) {
1335 r_sum -= r_lens[r_oldest];
1336 r_oldest++;
1338 #endif
1340 // av_Xput computes Xput, i.e.:
1341 // throughput for forward packets
1342 // goodput for reverse packets
1343 double av_Xput;
1344 r_sum += seglen;
1345 #ifdef MA_1_SECOND
1346 // for time-based MA, delta_t is constant
1347 av_Xput = r_sum * 8.0 / ma_window_size_;
1348 #else
1349 double dtime = 0.0;
1350 if (r_oldest > 0)
1351 dtime = ts - r_pkt_times[r_oldest-1];
1352 if (dtime > 0.0) {
1353 av_Xput = r_sum * 8.0 / dtime;
1354 } else {
1355 av_Xput = 0.0;
1357 #endif
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;
1364 } else {
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)
1381 // Assumptions:
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;
1394 // sanity check:
1395 if (tcp_seq_eq_or_after(left, right))
1396 return begin;
1397 // real work:
1398 for (cur = begin; cur != end; cur = next) {
1399 next = 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.
1405 continue;
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
1414 if (bySeqNumber) {
1415 x_vals.append(cur->seqno);
1416 } else {
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 ]
1423 if (cur == begin)
1424 begin = next;
1425 rtt_delete_unack_from_list(list, cur);
1426 continue;
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
1431 // ACK the left end
1432 if (bySeqNumber) {
1433 x_vals.append(cur->seqno);
1434 } else {
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
1439 cur->seqno = right;
1440 continue;
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
1446 if (bySeqNumber) {
1447 x_vals.append(left);
1448 } else {
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;
1454 continue;
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
1462 if (bySeqNumber) {
1463 x_vals.append(left);
1464 } else {
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;
1474 return begin;
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();
1486 if (bySeqNumber) {
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;
1510 break;
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);
1520 if (!u) {
1521 // make sure to free list before returning!
1522 rtt_destroy_unack_list(&unack_list);
1523 return;
1525 rtt_put_unack_on_list(&unack_list, u);
1527 } else {
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
1536 if (bySeqNumber) {
1537 x_vals.append(u->seqno);
1538 sequence_num_map_.insert(u->seqno, seg);
1539 } else {
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
1545 v = u->next;
1546 rtt_delete_unack_from_list(&unack_list, u);
1547 // no need to compare SACK blocks for fully ACKed seg
1548 continue;
1549 } else {
1550 // partial ack of GSO seg
1551 u->seqno = ack_no;
1552 // (keep going - still need to compare SACK blocks...)
1555 v = u->next;
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,
1564 &unack_list, u, v,
1565 left, right, rt_val);
1566 // if range is empty after selective ack, we can
1567 // skip the rest of the SACK blocks
1568 if (u == v) break;
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));
1619 } else {
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));
1668 return description;
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,
1677 COMPARE_CURR_DIR));
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);
1693 } else {
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_) {
1712 return zoom_ranges;
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());
1722 // QRects grow down
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());
1727 return zoom_ranges;
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
1740 sp->setFocus();
1742 if (mouse_drags_) {
1743 if (sp->axisRect()->rect().contains(event->pos())) {
1744 sp->setCursor(QCursor(Qt::ClosedHandCursor));
1746 on_actionGoToPacket_triggered();
1747 } else {
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:
1766 case GRAPH_WSCALE:
1767 case GRAPH_RTT:
1768 ts_origin_conn_ = ts_origin_conn_ ? false : true;
1769 fillGraph();
1770 break;
1771 default:
1772 break;
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;
1779 fillGraph();
1780 break;
1781 default:
1782 break;
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;
1794 if (event) {
1795 if (event->buttons().testFlag(Qt::LeftButton)) {
1796 if (mouse_drags_) {
1797 shape = Qt::ClosedHandCursor;
1798 } else {
1799 shape = Qt::CrossCursor;
1801 } else {
1802 if (sp->axisRect()->rect().contains(event->pos())) {
1803 if (mouse_drags_) {
1804 shape = Qt::OpenHandCursor;
1805 } else {
1806 shape = Qt::CrossCursor;
1811 sp->setCursor(QCursor(shape));
1813 QString hint = "<small><i>";
1814 if (mouse_drags_) {
1815 double tr_key = tracer_->position->key();
1816 struct segment *packet_seg = NULL;
1817 packet_num_ = 0;
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:
1827 case GRAPH_WSCALE:
1828 packet_seg = time_stamp_map_.value(tr_key, NULL);
1829 break;
1830 case GRAPH_RTT:
1831 if (ui->bySeqNumberCheckBox->isChecked())
1832 packet_seg = sequence_num_map_.value(tr_key, NULL);
1833 else
1834 packet_seg = time_stamp_map_.value(tr_key, NULL);
1835 default:
1836 break;
1840 if (!packet_seg) {
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);
1845 return;
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"))
1856 .arg(packet_num_)
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);
1864 } else {
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());
1874 } else {
1875 hint += tr("Unable to select range.");
1877 } else {
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());
1897 sp->replot();
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")
1928 .arg(pdf_filter)
1929 .arg(png_filter)
1930 .arg(bmp_filter)
1931 .arg(jpeg_filter);
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?
1948 if (save_ok) {
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()
1965 resetAxes();
1968 void TCPStreamDialog::setCaptureFile(capture_file *cf)
1970 if (!cf) { // We only want to know when the file closes.
1971 cap_file_ = NULL;
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()
1989 updateGraph();
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()
2002 updateGraph();
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)
2017 if (checked) {
2018 mouse_drags_ = true;
2019 if (rubber_band_ && rubber_band_->isVisible())
2020 rubber_band_->hide();
2021 ui->streamPlot->setInteractions(
2022 QCP::iRangeDrag |
2023 QCP::iRangeZoom
2028 void TCPStreamDialog::on_zoomRadioButton_toggled(bool checked)
2030 if (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()
2090 zoomAxes(true);
2093 void TCPStreamDialog::on_actionZoomInX_triggered()
2095 zoomXAxis(true);
2098 void TCPStreamDialog::on_actionZoomInY_triggered()
2100 zoomYAxis(true);
2103 void TCPStreamDialog::on_actionZoomOut_triggered()
2105 zoomAxes(false);
2108 void TCPStreamDialog::on_actionZoomOutX_triggered()
2110 zoomXAxis(false);
2113 void TCPStreamDialog::on_actionZoomOutY_triggered()
2115 zoomYAxis(false);
2118 void TCPStreamDialog::on_actionReset_triggered()
2120 on_resetButton_clicked();
2123 void TCPStreamDialog::on_actionMoveRight10_triggered()
2125 panAxes(10, 0);
2128 void TCPStreamDialog::on_actionMoveLeft10_triggered()
2130 panAxes(-10, 0);
2133 void TCPStreamDialog::on_actionMoveUp10_triggered()
2135 panAxes(0, 10);
2138 void TCPStreamDialog::on_actionMoveDown10_triggered()
2140 panAxes(0, -10);
2143 void TCPStreamDialog::on_actionMoveRight1_triggered()
2145 panAxes(1, 0);
2148 void TCPStreamDialog::on_actionMoveLeft1_triggered()
2150 panAxes(-1, 0);
2153 void TCPStreamDialog::on_actionMoveUp1_triggered()
2155 panAxes(0, 1);
2158 void TCPStreamDialog::on_actionMoveDown1_triggered()
2160 panAxes(0, -1);
2163 void TCPStreamDialog::on_actionNextStream_triggered()
2165 if (int(graph_.stream) < int(get_tcp_stream_count()) - 1) {
2166 ui->streamNumberSpinBox->setValue(graph_.stream + 1);
2167 updateGraph();
2171 void TCPStreamDialog::on_actionPreviousStream_triggered()
2173 if (graph_.stream > 0) {
2174 ui->streamNumberSpinBox->setValue(graph_.stream - 1);
2175 updateGraph();
2179 void TCPStreamDialog::on_actionSwitchDirection_triggered()
2181 address tmp_addr;
2182 uint16_t tmp_port;
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()
2206 if (mouse_drags_) {
2207 ui->zoomRadioButton->toggle();
2208 } else {
2209 ui->dragRadioButton->toggle();
2213 void TCPStreamDialog::on_actionToggleSequenceNumbers_triggered()
2215 seq_origin_zero_ = seq_origin_zero_ ? false : true;
2216 fillGraph();
2219 void TCPStreamDialog::on_actionToggleTimeOrigin_triggered()
2221 ts_origin_conn_ = ts_origin_conn_ ? false : true;
2222 fillGraph();
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);