1 /* tcp_stream_dialog.cpp 2 * 3 * Wireshark - Network traffic analyzer 4 * By Gerald Combs <gerald@wireshark.org> 5 * Copyright 1998 Gerald Combs 6 * 7 * SPDX-License-Identifier: GPL-2.0-or-later 8 */ 9 10 #include "tcp_stream_dialog.h" 11 #include <ui_tcp_stream_dialog.h> 12 13 #include <algorithm> // for std::sort 14 #include <utility> // for std::pair 15 #include <vector> 16 17 #include "epan/to_str.h" 18 19 #include "wsutil/str_util.h" 20 21 #include <wsutil/utf8_entities.h> 22 23 #include <ui/qt/utils/tango_colors.h> 24 #include <ui/qt/utils/qt_ui_utils.h> 25 #include "progress_frame.h" 26 #include "wireshark_application.h" 27 #include "ui/qt/widgets/wireshark_file_dialog.h" 28 29 #include <QCursor> 30 #include <QDir> 31 #include <QIcon> 32 #include <QPushButton> 33 34 #include <QDebug> 35 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 49 // The GTK+ version computes a 20 (or 21!) segment moving average. Comment 50 // out the line below to use that. By default we use a 1 second MA. 51 #define MA_1_SECOND 52 53 #ifndef MA_1_SECOND 54 const int moving_avg_period_ = 20; 55 #endif 56 57 const QRgb graph_color_1 = tango_sky_blue_5; 58 const QRgb graph_color_2 = tango_butter_6; 59 const QRgb graph_color_3 = tango_chameleon_5; 60 const QRgb graph_color_4 = tango_scarlet_red_4; 61 const QRgb graph_color_5 = tango_scarlet_red_6; 62 63 // Size of selectable packet points in the base graph 64 const double pkt_point_size_ = 3.0; 65 66 // Don't accidentally zoom into a 1x1 rect if you happen to click on the graph 67 // in zoom mode. 68 const int min_zoom_pixels_ = 20; 69 70 const QString average_throughput_label_ = QObject::tr("Average Throughput (bits/s)"); 71 const QString round_trip_time_ms_label_ = QObject::tr("Round Trip Time (ms)"); 72 const QString segment_length_label_ = QObject::tr("Segment Length (B)"); 73 const QString sequence_number_label_ = QObject::tr("Sequence Number (B)"); 74 const QString time_s_label_ = QObject::tr("Time (s)"); 75 const QString window_size_label_ = QObject::tr("Window Size (B)"); 76 77 QCPErrorBarsNotSelectable::QCPErrorBarsNotSelectable(QCPAxis *keyAxis, QCPAxis *valueAxis) : 78 QCPErrorBars(keyAxis, valueAxis) 79 { 80 } 81 82 QCPErrorBarsNotSelectable::~QCPErrorBarsNotSelectable() 83 { 84 } 85 86 double QCPErrorBarsNotSelectable::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const 87 { 88 Q_UNUSED(pos); 89 Q_UNUSED(onlySelectable); 90 Q_UNUSED(details); 91 return -1.0; 92 } 93 94 TCPStreamDialog::TCPStreamDialog(QWidget *parent, capture_file *cf, tcp_graph_type graph_type) : 95 GeometryStateDialog(parent), 96 ui(new Ui::TCPStreamDialog), 97 cap_file_(cf), 98 ts_offset_(0), 99 ts_origin_conn_(true), 100 seq_offset_(0), 101 seq_origin_zero_(true), 102 title_(nullptr), 103 base_graph_(nullptr), 104 tput_graph_(nullptr), 105 goodput_graph_(nullptr), 106 seg_graph_(nullptr), 107 seg_eb_(nullptr), 108 ack_graph_(nullptr), 109 sack_graph_(nullptr), 110 sack_eb_(nullptr), 111 sack2_graph_(nullptr), 112 sack2_eb_(nullptr), 113 rwin_graph_(nullptr), 114 dup_ack_graph_(nullptr), 115 zero_win_graph_(nullptr), 116 tracer_(nullptr), 117 packet_num_(0), 118 mouse_drags_(true), 119 rubber_band_(nullptr), 120 graph_updater_(this), 121 num_dsegs_(-1), 122 num_acks_(-1), 123 num_sack_ranges_(-1), 124 ma_window_size_(1.0) 125 { 126 int graph_idx = -1; 127 128 memset(&graph_, 0, sizeof(graph_)); 129 130 ui->setupUi(this); 131 if (parent) loadGeometry(parent->width() * 2 / 3, parent->height() * 4 / 5); 132 setAttribute(Qt::WA_DeleteOnClose, true); 133 134 guint32 th_stream = select_tcpip_session(cap_file_); 135 if (th_stream == G_MAXUINT32) { 136 done(QDialog::Rejected); 137 return; 138 } 139 140 QComboBox *gtcb = ui->graphTypeComboBox; 141 gtcb->setUpdatesEnabled(false); 142 gtcb->addItem(ui->actionRoundTripTime->text(), GRAPH_RTT); 143 if (graph_type == GRAPH_RTT) graph_idx = gtcb->count() - 1; 144 gtcb->addItem(ui->actionThroughput->text(), GRAPH_THROUGHPUT); 145 if (graph_type == GRAPH_THROUGHPUT) graph_idx = gtcb->count() - 1; 146 gtcb->addItem(ui->actionStevens->text(), GRAPH_TSEQ_STEVENS); 147 if (graph_type == GRAPH_TSEQ_STEVENS) graph_idx = gtcb->count() - 1; 148 gtcb->addItem(ui->actionTcptrace->text(), GRAPH_TSEQ_TCPTRACE); 149 if (graph_type == GRAPH_TSEQ_TCPTRACE) graph_idx = gtcb->count() - 1; 150 gtcb->addItem(ui->actionWindowScaling->text(), GRAPH_WSCALE); 151 if (graph_type == GRAPH_WSCALE) graph_idx = gtcb->count() - 1; 152 gtcb->setUpdatesEnabled(true); 153 154 ui->dragRadioButton->setChecked(mouse_drags_); 155 156 ctx_menu_.addAction(ui->actionZoomIn); 157 ctx_menu_.addAction(ui->actionZoomInX); 158 ctx_menu_.addAction(ui->actionZoomInY); 159 ctx_menu_.addAction(ui->actionZoomOut); 160 ctx_menu_.addAction(ui->actionZoomOutX); 161 ctx_menu_.addAction(ui->actionZoomOutY); 162 ctx_menu_.addAction(ui->actionReset); 163 ctx_menu_.addSeparator(); 164 ctx_menu_.addAction(ui->actionMoveRight10); 165 ctx_menu_.addAction(ui->actionMoveLeft10); 166 ctx_menu_.addAction(ui->actionMoveUp10); 167 ctx_menu_.addAction(ui->actionMoveDown10); 168 ctx_menu_.addAction(ui->actionMoveRight1); 169 ctx_menu_.addAction(ui->actionMoveLeft1); 170 ctx_menu_.addAction(ui->actionMoveUp1); 171 ctx_menu_.addAction(ui->actionMoveDown1); 172 ctx_menu_.addSeparator(); 173 ctx_menu_.addAction(ui->actionNextStream); 174 ctx_menu_.addAction(ui->actionPreviousStream); 175 ctx_menu_.addAction(ui->actionSwitchDirection); 176 ctx_menu_.addAction(ui->actionGoToPacket); 177 ctx_menu_.addSeparator(); 178 ctx_menu_.addAction(ui->actionDragZoom); 179 ctx_menu_.addAction(ui->actionToggleSequenceNumbers); 180 ctx_menu_.addAction(ui->actionToggleTimeOrigin); 181 ctx_menu_.addAction(ui->actionCrosshairs); 182 ctx_menu_.addSeparator(); 183 ctx_menu_.addAction(ui->actionRoundTripTime); 184 ctx_menu_.addAction(ui->actionThroughput); 185 ctx_menu_.addAction(ui->actionStevens); 186 ctx_menu_.addAction(ui->actionTcptrace); 187 ctx_menu_.addAction(ui->actionWindowScaling); 188 set_action_shortcuts_visible_in_context_menu(ctx_menu_.actions()); 189 190 graph_.type = graph_type; 191 graph_.stream = th_stream; 192 findStream(); 193 194 showWidgetsForGraphType(); 195 196 ui->streamNumberSpinBox->blockSignals(true); 197 ui->streamNumberSpinBox->setMaximum(get_tcp_stream_count() - 1); 198 ui->streamNumberSpinBox->setValue(graph_.stream); 199 ui->streamNumberSpinBox->blockSignals(false); 200 201 #ifdef MA_1_SECOND 202 ui->maWindowSizeSpinBox->blockSignals(true); 203 ui->maWindowSizeSpinBox->setDecimals(6); 204 ui->maWindowSizeSpinBox->setMinimum(0.000001); 205 ui->maWindowSizeSpinBox->setValue(ma_window_size_); 206 ui->maWindowSizeSpinBox->blockSignals(false); 207 #endif 208 209 // set which Throughput graphs are displayed by default 210 ui->showSegLengthCheckBox->blockSignals(true); 211 ui->showSegLengthCheckBox->setChecked(true); 212 ui->showSegLengthCheckBox->blockSignals(false); 213 214 ui->showThroughputCheckBox->blockSignals(true); 215 ui->showThroughputCheckBox->setChecked(true); 216 ui->showThroughputCheckBox->blockSignals(false); 217 218 // set which WScale graphs are displayed by default 219 ui->showRcvWinCheckBox->blockSignals(true); 220 ui->showRcvWinCheckBox->setChecked(true); 221 ui->showRcvWinCheckBox->blockSignals(false); 222 223 ui->showBytesOutCheckBox->blockSignals(true); 224 ui->showBytesOutCheckBox->setChecked(true); 225 ui->showBytesOutCheckBox->blockSignals(false); 226 227 QCustomPlot *sp = ui->streamPlot; 228 QCPTextElement *file_title = new QCPTextElement(sp, gchar_free_to_qstring(cf_get_display_name(cap_file_))); 229 file_title->setFont(sp->xAxis->labelFont()); 230 title_ = new QCPTextElement(sp); 231 sp->plotLayout()->insertRow(0); 232 sp->plotLayout()->addElement(0, 0, file_title); 233 sp->plotLayout()->insertRow(0); 234 sp->plotLayout()->addElement(0, 0, title_); 235 236 qreal pen_width = 0.5; 237 // Base Graph - enables selecting segments (both data and SACKs) 238 base_graph_ = sp->addGraph(); 239 base_graph_->setPen(QPen(QBrush(graph_color_1), pen_width)); 240 241 // Throughput Graph - rate of sent bytes 242 tput_graph_ = sp->addGraph(sp->xAxis, sp->yAxis2); 243 tput_graph_->setPen(QPen(QBrush(graph_color_2), pen_width)); 244 tput_graph_->setLineStyle(QCPGraph::lsStepLeft); 245 246 // Goodput Graph - rate of ACKed bytes 247 goodput_graph_ = sp->addGraph(sp->xAxis, sp->yAxis2); 248 goodput_graph_->setPen(QPen(QBrush(graph_color_3), pen_width)); 249 goodput_graph_->setLineStyle(QCPGraph::lsStepLeft); 250 251 // Seg Graph - displays forward data segments on tcptrace graph 252 seg_graph_ = sp->addGraph(); 253 seg_graph_->setLineStyle(QCPGraph::lsNone); 254 seg_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDot, Qt::transparent, 0)); 255 seg_eb_ = new QCPErrorBarsNotSelectable(sp->xAxis, sp->yAxis); 256 seg_eb_->setErrorType(QCPErrorBars::etValueError); 257 seg_eb_->setPen(QPen(QBrush(graph_color_1), pen_width)); 258 seg_eb_->setSymbolGap(0.0); // draw error spine as single line 259 seg_eb_->setWhiskerWidth(pkt_point_size_); 260 seg_eb_->removeFromLegend(); 261 seg_eb_->setDataPlottable(seg_graph_); 262 263 // Ack Graph - displays ack numbers from reverse packets 264 ack_graph_ = sp->addGraph(); 265 ack_graph_->setPen(QPen(QBrush(graph_color_2), pen_width)); 266 ack_graph_->setLineStyle(QCPGraph::lsStepLeft); 267 268 // Sack Graph - displays highest number (most recent) SACK block 269 sack_graph_ = sp->addGraph(); 270 sack_graph_->setLineStyle(QCPGraph::lsNone); 271 sack_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDot, Qt::transparent, 0)); 272 sack_eb_ = new QCPErrorBarsNotSelectable(sp->xAxis, sp->yAxis); 273 sack_eb_->setErrorType(QCPErrorBars::etValueError); 274 sack_eb_->setPen(QPen(QBrush(graph_color_4), pen_width)); 275 sack_eb_->setSymbolGap(0.0); // draw error spine as single line 276 sack_eb_->setWhiskerWidth(0.0); 277 sack_eb_->removeFromLegend(); 278 sack_eb_->setDataPlottable(sack_graph_); 279 280 // Sack Graph 2 - displays subsequent SACK blocks 281 sack2_graph_ = sp->addGraph(); 282 sack2_graph_->setLineStyle(QCPGraph::lsNone); 283 sack2_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDot, Qt::transparent, 0)); 284 sack2_eb_ = new QCPErrorBarsNotSelectable(sp->xAxis, sp->yAxis); 285 sack2_eb_->setErrorType(QCPErrorBars::etValueError); 286 sack2_eb_->setPen(QPen(QBrush(graph_color_5), pen_width)); 287 sack2_eb_->setSymbolGap(0.0); // draw error spine as single line 288 sack2_eb_->setWhiskerWidth(0.0); 289 sack2_eb_->removeFromLegend(); 290 sack2_eb_->setDataPlottable(sack2_graph_); 291 292 // RWin graph - displays upper extent of RWIN advertised on reverse packets 293 rwin_graph_ = sp->addGraph(); 294 rwin_graph_->setPen(QPen(QBrush(graph_color_3), pen_width)); 295 rwin_graph_->setLineStyle(QCPGraph::lsStepLeft); 296 297 // Duplicate ACK Graph - displays duplicate ack ticks 298 // QCustomPlot doesn't have QCPScatterStyle::ssTick so we have to make our own. 299 int tick_len = 3; 300 tick_len *= devicePixelRatio(); 301 QPixmap da_tick_pm = QPixmap(1, tick_len * 2); 302 da_tick_pm.fill(Qt::transparent); 303 QPainter painter(&da_tick_pm); 304 QPen da_tick_pen; 305 da_tick_pen.setColor(graph_color_2); 306 da_tick_pen.setWidthF(pen_width); 307 painter.setPen(da_tick_pen); 308 painter.drawLine(0, tick_len, 0, tick_len * 2); 309 dup_ack_graph_ = sp->addGraph(); 310 dup_ack_graph_->setLineStyle(QCPGraph::lsNone); 311 QCPScatterStyle da_ss = QCPScatterStyle(QCPScatterStyle::ssPixmap, graph_color_2, 0); 312 da_ss.setPixmap(da_tick_pm); 313 dup_ack_graph_->setScatterStyle(da_ss); 314 315 // Zero Window Graph - displays zero window crosses (x) 316 zero_win_graph_ = sp->addGraph(); 317 zero_win_graph_->setLineStyle(QCPGraph::lsNone); 318 zero_win_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCross, graph_color_1, 5)); 319 320 tracer_ = new QCPItemTracer(sp); 321 322 // Triggers fillGraph() [ UNLESS the index is already graph_idx!! ] 323 if (graph_idx != ui->graphTypeComboBox->currentIndex()) 324 // changing the current index will call fillGraph 325 ui->graphTypeComboBox->setCurrentIndex(graph_idx); 326 else 327 // the current index is what we want - so fillGraph() manually 328 fillGraph(); 329 330 sp->setMouseTracking(true); 331 332 sp->yAxis->setLabelColor(QColor(graph_color_1)); 333 sp->yAxis->setTickLabelColor(QColor(graph_color_1)); 334 335 tracer_->setVisible(false); 336 toggleTracerStyle(true); 337 338 QPushButton *save_bt = ui->buttonBox->button(QDialogButtonBox::Save); 339 save_bt->setText(tr("Save As…")); 340 341 QPushButton *close_bt = ui->buttonBox->button(QDialogButtonBox::Close); 342 if (close_bt) { 343 close_bt->setDefault(true); 344 } 345 346 ProgressFrame::addToButtonBox(ui->buttonBox, parent); 347 348 connect(sp, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(graphClicked(QMouseEvent*))); 349 connect(sp, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMoved(QMouseEvent*))); 350 connect(sp, SIGNAL(mouseRelease(QMouseEvent*)), this, SLOT(mouseReleased(QMouseEvent*))); 351 connect(sp, SIGNAL(axisClick(QCPAxis*,QCPAxis::SelectablePart,QMouseEvent*)), 352 this, SLOT(axisClicked(QCPAxis*,QCPAxis::SelectablePart,QMouseEvent*))); 353 connect(sp->yAxis, SIGNAL(rangeChanged(QCPRange)), this, SLOT(transformYRange(QCPRange))); 354 disconnect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept())); 355 this->setResult(QDialog::Accepted); 356 } 357 358 TCPStreamDialog::~TCPStreamDialog() 359 { 360 graph_segment_list_free(&graph_); 361 362 delete ui; 363 } 364 365 void TCPStreamDialog::showEvent(QShowEvent *) 366 { 367 resetAxes(); 368 } 369 370 void TCPStreamDialog::keyPressEvent(QKeyEvent *event) 371 { 372 int pan_pixels = event->modifiers() & Qt::ShiftModifier ? 1 : 10; 373 374 QWidget* focusWidget = QApplication::focusWidget(); 375 376 // Block propagation of "Enter" key when focus is not default (e.g. SpinBox) 377 // [ Note that if focus was on, e.g. Close button, event would never reach 378 // here ] 379 if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) && 380 focusWidget !=NULL && focusWidget != ui->streamPlot) { 381 382 // reset focus to default, and accept event 383 ui->streamPlot->setFocus(); 384 event->accept(); 385 return; 386 } 387 388 // XXX - This differs from the main window but matches other applications (e.g. Mozilla and Safari) 389 switch(event->key()) { 390 case Qt::Key_Minus: 391 case Qt::Key_Underscore: // Shifted minus on U.S. keyboards 392 case Qt::Key_O: // GTK+ 393 zoomAxes(false); 394 break; 395 case Qt::Key_Plus: 396 case Qt::Key_Equal: // Unshifted plus on U.S. keyboards 397 case Qt::Key_I: // GTK+ 398 zoomAxes(true); 399 break; 400 case Qt::Key_X: // Zoom X axis only 401 if (event->modifiers() & Qt::ShiftModifier) { 402 zoomXAxis(false); // upper case X -> Zoom out 403 } else { 404 zoomXAxis(true); // lower case x -> Zoom in 405 } 406 break; 407 case Qt::Key_Y: // Zoom Y axis only 408 if (event->modifiers() & Qt::ShiftModifier) { 409 zoomYAxis(false); // upper case Y -> Zoom out 410 } else { 411 zoomYAxis(true); // lower case y -> Zoom in 412 } 413 break; 414 case Qt::Key_Right: 415 case Qt::Key_L: 416 panAxes(pan_pixels, 0); 417 break; 418 case Qt::Key_Left: 419 case Qt::Key_H: 420 panAxes(-1 * pan_pixels, 0); 421 break; 422 case Qt::Key_Up: 423 case Qt::Key_K: 424 panAxes(0, pan_pixels); 425 break; 426 case Qt::Key_Down: 427 case Qt::Key_J: 428 panAxes(0, -1 * pan_pixels); 429 break; 430 431 case Qt::Key_Space: 432 toggleTracerStyle(); 433 break; 434 435 case Qt::Key_0: 436 case Qt::Key_ParenRight: // Shifted 0 on U.S. keyboards 437 case Qt::Key_R: 438 case Qt::Key_Home: 439 resetAxes(); 440 break; 441 442 case Qt::Key_PageUp: 443 on_actionNextStream_triggered(); 444 break; 445 case Qt::Key_PageDown: 446 on_actionPreviousStream_triggered(); 447 break; 448 449 case Qt::Key_D: 450 on_actionSwitchDirection_triggered(); 451 break; 452 case Qt::Key_G: 453 on_actionGoToPacket_triggered(); 454 break; 455 case Qt::Key_S: 456 on_actionToggleSequenceNumbers_triggered(); 457 break; 458 case Qt::Key_T: 459 on_actionToggleTimeOrigin_triggered(); 460 break; 461 case Qt::Key_Z: 462 on_actionDragZoom_triggered(); 463 break; 464 465 case Qt::Key_1: 466 on_actionRoundTripTime_triggered(); 467 break; 468 case Qt::Key_2: 469 on_actionThroughput_triggered(); 470 break; 471 case Qt::Key_3: 472 on_actionStevens_triggered(); 473 break; 474 case Qt::Key_4: 475 on_actionTcptrace_triggered(); 476 break; 477 case Qt::Key_5: 478 on_actionWindowScaling_triggered(); 479 break; 480 // Alas, there is no Blade Runner-style Qt::Key_Enhance 481 } 482 483 QDialog::keyPressEvent(event); 484 } 485 486 void TCPStreamDialog::mousePressEvent(QMouseEvent *event) 487 { 488 // if no-one else wants the event, then this is a click on blank space. 489 // Use this opportunity to set focus back to default, and accept event. 490 ui->streamPlot->setFocus(); 491 event->accept(); 492 } 493 494 void TCPStreamDialog::mouseReleaseEvent(QMouseEvent *event) 495 { 496 mouseReleased(event); 497 } 498 499 void TCPStreamDialog::findStream() 500 { 501 QCustomPlot *sp = ui->streamPlot; 502 503 disconnect(sp, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMoved(QMouseEvent*))); 504 // if streamNumberSpinBox has focus - 505 // first clear focus, then disable/enable, then restore focus 506 bool spin_box_focused = ui->streamNumberSpinBox->hasFocus(); 507 if (spin_box_focused) 508 ui->streamNumberSpinBox->clearFocus(); 509 ui->streamNumberSpinBox->setEnabled(false); 510 graph_segment_list_free(&graph_); 511 graph_segment_list_get(cap_file_, &graph_); 512 ui->streamNumberSpinBox->setEnabled(true); 513 if (spin_box_focused) 514 ui->streamNumberSpinBox->setFocus(); 515 516 connect(sp, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMoved(QMouseEvent*))); 517 } 518 519 void TCPStreamDialog::fillGraph(bool reset_axes, bool set_focus) 520 { 521 QCustomPlot *sp = ui->streamPlot; 522 523 if (sp->graphCount() < 1) return; 524 525 base_graph_->setLineStyle(QCPGraph::lsNone); 526 tracer_->setGraph(NULL); 527 528 // base_graph_ is always visible. 529 for (int i = 0; i < sp->graphCount(); i++) { 530 sp->graph(i)->data()->clear(); 531 sp->graph(i)->setVisible(i == 0 ? true : false); 532 } 533 // also clear and hide ErrorBars plottables 534 seg_eb_->setVisible(false); 535 seg_eb_->data()->clear(); 536 sack_eb_->setVisible(false); 537 sack_eb_->data()->clear(); 538 sack2_eb_->setVisible(false); 539 sack2_eb_->data()->clear(); 540 541 base_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, pkt_point_size_)); 542 543 sp->xAxis->setLabel(time_s_label_); 544 sp->xAxis->setNumberFormat("gb"); 545 // Use enough precision to mark microseconds 546 // when zooming in on a <100s capture 547 sp->xAxis->setNumberPrecision(8); 548 sp->yAxis->setNumberFormat("f"); 549 sp->yAxis->setNumberPrecision(0); 550 sp->yAxis2->setVisible(false); 551 sp->yAxis2->setLabel(QString()); 552 553 if (!cap_file_) { 554 QString dlg_title = QString(tr("No Capture Data")); 555 setWindowTitle(dlg_title); 556 title_->setText(dlg_title); 557 sp->setEnabled(false); 558 sp->yAxis->setLabel(QString()); 559 sp->replot(); 560 return; 561 } 562 563 ts_offset_ = 0; 564 seq_offset_ = 0; 565 bool first = true; 566 guint64 bytes_fwd = 0; 567 guint64 bytes_rev = 0; 568 int pkts_fwd = 0; 569 int pkts_rev = 0; 570 571 time_stamp_map_.clear(); 572 for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) { 573 // NOTE - adding both forward and reverse packets to time_stamp_map_ 574 // so that both data and acks are selectable 575 // (this is important especially in selecting particular SACK pkts) 576 bool insert = true; 577 if (!compareHeaders(seg)) { 578 bytes_rev += seg->th_seglen; 579 pkts_rev++; 580 // only insert reverse packets if SACK present 581 insert = (seg->num_sack_ranges != 0); 582 } else { 583 bytes_fwd += seg->th_seglen; 584 pkts_fwd++; 585 } 586 double ts = seg->rel_secs + seg->rel_usecs / 1000000.0; 587 if (first) { 588 if (ts_origin_conn_) ts_offset_ = ts; 589 if (seq_origin_zero_) { 590 if (compareHeaders(seg)) 591 seq_offset_ = seg->th_seq; 592 else 593 seq_offset_ = seg->th_ack; 594 } 595 first = false; 596 } 597 if (insert) { 598 time_stamp_map_.insert(ts - ts_offset_, seg); 599 } 600 } 601 602 switch (graph_.type) { 603 case GRAPH_TSEQ_STEVENS: 604 fillStevens(); 605 break; 606 case GRAPH_TSEQ_TCPTRACE: 607 fillTcptrace(); 608 break; 609 case GRAPH_THROUGHPUT: 610 fillThroughput(); 611 break; 612 case GRAPH_RTT: 613 fillRoundTripTime(); 614 break; 615 case GRAPH_WSCALE: 616 fillWindowScale(); 617 break; 618 default: 619 break; 620 } 621 sp->setEnabled(true); 622 623 stream_desc_ = tr("%1 %2 pkts, %3 %4 %5 pkts, %6 ") 624 .arg(UTF8_RIGHTWARDS_ARROW) 625 .arg(gchar_free_to_qstring(format_size(pkts_fwd, format_size_unit_none|format_size_prefix_si))) 626 .arg(gchar_free_to_qstring(format_size(bytes_fwd, format_size_unit_bytes|format_size_prefix_si))) 627 .arg(UTF8_LEFTWARDS_ARROW) 628 .arg(gchar_free_to_qstring(format_size(pkts_rev, format_size_unit_none|format_size_prefix_si))) 629 .arg(gchar_free_to_qstring(format_size(bytes_rev, format_size_unit_bytes|format_size_prefix_si))); 630 mouseMoved(NULL); 631 if (reset_axes) 632 resetAxes(); 633 else 634 sp->replot(); 635 // Throughput and Window Scale graphs can hide base_graph_ 636 if (base_graph_->visible()) 637 tracer_->setGraph(base_graph_); 638 639 // XXX QCustomPlot doesn't seem to draw any sort of focus indicator. 640 if (set_focus) 641 sp->setFocus(); 642 } 643 644 void TCPStreamDialog::showWidgetsForGraphType() 645 { 646 if (graph_.type == GRAPH_RTT) { 647 ui->bySeqNumberCheckBox->setVisible(true); 648 } else { 649 ui->bySeqNumberCheckBox->setVisible(false); 650 } 651 if (graph_.type == GRAPH_THROUGHPUT) { 652 #ifdef MA_1_SECOND 653 ui->maWindowSizeLabel->setVisible(true); 654 ui->maWindowSizeSpinBox->setVisible(true); 655 #else 656 ui->maWindowSizeLabel->setVisible(false); 657 ui->maWindowSizeSpinBox->setVisible(false); 658 #endif 659 ui->showSegLengthCheckBox->setVisible(true); 660 ui->showThroughputCheckBox->setVisible(true); 661 ui->showGoodputCheckBox->setVisible(true); 662 } else { 663 ui->maWindowSizeLabel->setVisible(false); 664 ui->maWindowSizeSpinBox->setVisible(false); 665 ui->showSegLengthCheckBox->setVisible(false); 666 ui->showThroughputCheckBox->setVisible(false); 667 ui->showGoodputCheckBox->setVisible(false); 668 } 669 670 if (graph_.type == GRAPH_TSEQ_TCPTRACE) { 671 ui->selectSACKsCheckBox->setVisible(true); 672 } else { 673 ui->selectSACKsCheckBox->setVisible(false); 674 } 675 676 if (graph_.type == GRAPH_WSCALE) { 677 ui->showRcvWinCheckBox->setVisible(true); 678 ui->showBytesOutCheckBox->setVisible(true); 679 } else { 680 ui->showRcvWinCheckBox->setVisible(false); 681 ui->showBytesOutCheckBox->setVisible(false); 682 } 683 } 684 685 void TCPStreamDialog::zoomAxes(bool in) 686 { 687 QCustomPlot *sp = ui->streamPlot; 688 double h_factor = sp->axisRect()->rangeZoomFactor(Qt::Horizontal); 689 double v_factor = sp->axisRect()->rangeZoomFactor(Qt::Vertical); 690 691 if (!in) { 692 h_factor = pow(h_factor, -1); 693 v_factor = pow(v_factor, -1); 694 } 695 696 sp->xAxis->scaleRange(h_factor, sp->xAxis->range().center()); 697 sp->yAxis->scaleRange(v_factor, sp->yAxis->range().center()); 698 sp->replot(); 699 } 700 701 void TCPStreamDialog::zoomXAxis(bool in) 702 { 703 QCustomPlot *sp = ui->streamPlot; 704 double h_factor = sp->axisRect()->rangeZoomFactor(Qt::Horizontal); 705 706 if (!in) { 707 h_factor = pow(h_factor, -1); 708 } 709 710 sp->xAxis->scaleRange(h_factor, sp->xAxis->range().center()); 711 sp->replot(); 712 } 713 714 void TCPStreamDialog::zoomYAxis(bool in) 715 { 716 QCustomPlot *sp = ui->streamPlot; 717 double v_factor = sp->axisRect()->rangeZoomFactor(Qt::Vertical); 718 719 if (!in) { 720 v_factor = pow(v_factor, -1); 721 } 722 723 sp->yAxis->scaleRange(v_factor, sp->yAxis->range().center()); 724 sp->replot(); 725 } 726 727 void TCPStreamDialog::panAxes(int x_pixels, int y_pixels) 728 { 729 QCustomPlot *sp = ui->streamPlot; 730 double h_pan = 0.0; 731 double v_pan = 0.0; 732 733 h_pan = sp->xAxis->range().size() * x_pixels / sp->xAxis->axisRect()->width(); 734 v_pan = sp->yAxis->range().size() * y_pixels / sp->yAxis->axisRect()->height(); 735 // The GTK+ version won't pan unless we're zoomed. Should we do the same here? 736 if (h_pan) { 737 sp->xAxis->moveRange(h_pan); 738 sp->replot(); 739 } 740 if (v_pan) { 741 sp->yAxis->moveRange(v_pan); 742 sp->replot(); 743 } 744 } 745 746 void TCPStreamDialog::resetAxes() 747 { 748 QCustomPlot *sp = ui->streamPlot; 749 750 y_axis_xfrm_.reset(); 751 double pixel_pad = 10.0; // per side 752 753 sp->rescaleAxes(true); 754 // tput_graph_->rescaleValueAxis(false, true); 755 // base_graph_->rescaleAxes(false, true); 756 // for (int i = 0; i < sp->graphCount(); i++) { 757 // sp->graph(i)->rescaleValueAxis(false, true); 758 // } 759 760 double axis_pixels = sp->xAxis->axisRect()->width(); 761 sp->xAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, sp->xAxis->range().center()); 762 763 if (sp->yAxis2->visible()) { 764 double ratio = sp->yAxis2->range().size() / sp->yAxis->range().size(); 765 y_axis_xfrm_.translate(0.0, sp->yAxis2->range().lower - (sp->yAxis->range().lower * ratio)); 766 y_axis_xfrm_.scale(1.0, ratio); 767 } 768 769 axis_pixels = sp->yAxis->axisRect()->height(); 770 sp->yAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, sp->yAxis->range().center()); 771 772 sp->replot(); 773 } 774 775 void TCPStreamDialog::fillStevens() 776 { 777 QString dlg_title = QString(tr("Sequence Numbers (Stevens)")) + streamDescription(); 778 setWindowTitle(dlg_title); 779 title_->setText(dlg_title); 780 781 QCustomPlot *sp = ui->streamPlot; 782 sp->yAxis->setLabel(sequence_number_label_); 783 784 // True Stevens-style graphs don't have lines but I like them - gcc 785 base_graph_->setLineStyle(QCPGraph::lsStepLeft); 786 787 QVector<double> rel_time, seq; 788 for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) { 789 if (!compareHeaders(seg)) { 790 continue; 791 } 792 793 double ts = seg->rel_secs + seg->rel_usecs / 1000000.0; 794 rel_time.append(ts - ts_offset_); 795 seq.append(seg->th_seq - seq_offset_); 796 } 797 base_graph_->setData(rel_time, seq); 798 } 799 800 void TCPStreamDialog::fillTcptrace() 801 { 802 QString dlg_title = QString(tr("Sequence Numbers (tcptrace)")) + streamDescription(); 803 setWindowTitle(dlg_title); 804 title_->setText(dlg_title); 805 806 bool allow_sack_select = ui->selectSACKsCheckBox->isChecked(); 807 808 QCustomPlot *sp = ui->streamPlot; 809 sp->yAxis->setLabel(sequence_number_label_); 810 811 base_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDot)); 812 813 seg_graph_->setVisible(true); 814 seg_eb_->setVisible(true); 815 ack_graph_->setVisible(true); 816 sack_graph_->setVisible(true); 817 sack_eb_->setVisible(true); 818 sack2_graph_->setVisible(true); 819 sack2_eb_->setVisible(true); 820 rwin_graph_->setVisible(true); 821 dup_ack_graph_->setVisible(true); 822 zero_win_graph_->setVisible(true); 823 824 QVector<double> pkt_time, pkt_seqnums; 825 QVector<double> sb_time, sb_center, sb_span; 826 QVector<double> ackrwin_time, ack, rwin; 827 QVector<double> sack_time, sack_center, sack_span; 828 QVector<double> sack2_time, sack2_center, sack2_span; 829 QVector<double> dup_ack_time, dup_ack; 830 QVector<double> zero_win_time, zero_win; 831 832 for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) { 833 double ts = (seg->rel_secs + seg->rel_usecs / 1000000.0) - ts_offset_; 834 if (compareHeaders(seg)) { 835 double half = seg->th_seglen / 2.0; 836 double center = seg->th_seq - seq_offset_ + half; 837 838 // Add forward direction to base_graph_ (to select data packets) 839 // Forward direction: seq + data 840 pkt_time.append(ts); 841 pkt_seqnums.append(center); 842 843 // QCP doesn't have a segment graph type. For now, fake 844 // it with error bars. 845 if (seg->th_seglen > 0) { 846 sb_time.append(ts); 847 sb_center.append(center); 848 sb_span.append(half); 849 } 850 851 // Look for zero window sizes. 852 // Should match the TCP_A_ZERO_WINDOW test in packet-tcp.c. 853 if (seg->th_win == 0 && (seg->th_flags & (TH_RST|TH_FIN|TH_SYN)) == 0) { 854 zero_win_time.append(ts); 855 zero_win.append(center); 856 } 857 } else { 858 // Reverse direction: ACK + RWIN 859 if (! (seg->th_flags & TH_ACK)) { 860 // SYNs and RSTs do not necessarily have ACKs 861 continue; 862 } 863 double ackno = seg->th_ack - seq_offset_; 864 // add SACK segments to sack, sack2, and selectable packet graph 865 for (int i = 0; i < seg->num_sack_ranges; ++i) { 866 double half = seg->sack_right_edge[i] - seg->sack_left_edge[i]; 867 half = half/2.0; 868 double center = seg->sack_left_edge[i] - seq_offset_ + half; 869 if (i == 0) { 870 sack_time.append(ts); 871 sack_center.append(center); 872 sack_span.append(half); 873 if (allow_sack_select) { 874 pkt_time.append(ts); 875 pkt_seqnums.append(center); 876 } 877 } else { 878 sack2_time.append(ts); 879 sack2_center.append(center); 880 sack2_span.append(half); 881 } 882 } 883 // If ackno is the same as our last one mark it as a duplicate. 884 // (but don't mark window updates as duplicate acks) 885 if (ack.size() > 0 && ack.last() == ackno 886 && rwin.last() == ackno + seg->th_win) { 887 dup_ack_time.append(ts); 888 dup_ack.append(ackno); 889 } 890 // Also add reverse packets to the ack_graph_ 891 ackrwin_time.append(ts); 892 ack.append(ackno); 893 rwin.append(ackno + seg->th_win); 894 } 895 } 896 base_graph_->setData(pkt_time, pkt_seqnums, true); 897 ack_graph_->setData(ackrwin_time, ack, true); 898 seg_graph_->setData(sb_time, sb_center, true); 899 seg_eb_->setData(sb_span); 900 sack_graph_->setData(sack_time, sack_center, true); 901 sack_eb_->setData(sack_span); 902 sack2_graph_->setData(sack2_time, sack2_center, true); 903 sack2_eb_->setData(sack2_span); 904 rwin_graph_->setData(ackrwin_time, rwin, true); 905 dup_ack_graph_->setData(dup_ack_time, dup_ack, true); 906 zero_win_graph_->setData(zero_win_time, zero_win, true); 907 } 908 909 // If the current implementation of incorporating SACKs in goodput calc 910 // is slow, comment out the following line to ignore SACKs in goodput calc. 911 #define USE_SACKS_IN_GOODPUT_CALC 912 913 #ifdef USE_SACKS_IN_GOODPUT_CALC 914 // to incorporate SACKED segments into goodput calculation, 915 // need to keep track of all the SACK blocks we haven't yet 916 // fully ACKed. 917 // I expect this to be _relatively_ small, so using vector to store 918 // them. If this performs badly, it can be refactored with std::list 919 // or std::map. 920 typedef std::pair<guint32, guint32> sack_t; 921 typedef std::vector<sack_t> sack_list_t; 922 static inline bool compare_sack(const sack_t& s1, const sack_t& s2) { 923 return tcp_seq_before(s1.first, s2.first); 924 } 925 926 // Helper function to adjust an acked seglen for goodput: 927 // - removes previously sacked ranges from seglen (and from old_sacks), 928 // - adds newly sacked ranges to seglen (and to old_sacks) 929 static void 930 goodput_adjust_for_sacks(guint32 *seglen, guint32 last_ack, 931 sack_list_t& new_sacks, guint8 num_sack_ranges, 932 sack_list_t& old_sacks) { 933 934 // Step 1 - For any old_sacks acked by last_ack, 935 // delete their acked length from seglen, 936 // and remove the sack block (or portion) 937 // from (sorted) old_sacks. 938 sack_list_t::iterator unacked = old_sacks.begin(); 939 while (unacked != old_sacks.end()) { 940 // break on first sack not fully acked 941 if (tcp_seq_before(last_ack, unacked->second)) { 942 if (tcp_seq_after(last_ack, unacked->first)) { 943 // partially acked - modify to remove acked part 944 *seglen -= (last_ack - unacked->first); 945 unacked->first = last_ack; 946 } 947 break; 948 } 949 // remove fully acked sacks from seglen and move on 950 // (we'll actually remove from the list when loop is done) 951 *seglen -= (unacked->second - unacked->first); 952 ++unacked; 953 } 954 // actually remove all fully acked sacks from old_sacks list 955 if (unacked != old_sacks.begin()) 956 old_sacks.erase(old_sacks.begin(), unacked); 957 958 // Step 2 - for any new_sacks that precede last_ack, 959 // ignore them. (These would generally be SACKed dup-acks of 960 // a retransmitted seg). 961 // [ in the unlikely case that any new SACK straddles last_ack, 962 // the sack block will be modified to remove the acked portion ] 963 int next_new_idx = 0; 964 while (next_new_idx < num_sack_ranges) { 965 if (tcp_seq_before(last_ack, new_sacks[next_new_idx].second)) { 966 // if a new SACK block is unacked by its own packet, then it's 967 // likely fully unacked, but let's check for partial ack anyway, 968 // and truncate the SACK so that it's fully unacked: 969 if (tcp_seq_before(new_sacks[next_new_idx].first, last_ack)) 970 new_sacks[next_new_idx].first = last_ack; 971 break; 972 } 973 ++next_new_idx; 974 } 975 976 // Step 3 - for any byte ranges in remaining new_sacks 977 // that don't already exist in old_sacks, add 978 // their length to seglen 979 // and add that range (by extension, if possible) to 980 // the list of old_sacks. 981 982 sack_list_t::iterator next_old = old_sacks.begin(); 983 984 while (next_new_idx < num_sack_ranges && 985 next_old != old_sacks.end()) { 986 sack_t* next_new = &new_sacks[next_new_idx]; 987 988 // Assumptions / Invariants: 989 // - new and old lists are sorted 990 // - span of leftmost to rightmost endpt. is less than half uint32 range 991 // [ensures transitivity - e.g. before(a,b) and before(b,c) ==> before(a,c)] 992 // - all SACKs are non-empty (sack.left before sack.right) 993 // - adjacent SACKs in list always have a gap between them 994 // (sack.right before next_sack.left) 995 996 // Given these assumptions, and noting that there are only three 997 // possible comparisons for a pair of points (before/equal/after), 998 // there are only a few possible relative configurations 999 // of next_old and next_new: 1000 // next_new: 1001 // [-------------) 1002 // next_old: 1003 // 1. [---) 1004 // 2. [-------) 1005 // 3. [----------------) 1006 // 4. [---------------------) 1007 // 5. [----------------------------) 1008 // 6. [--------) 1009 // 7. [-------------) 1010 // 8. [--------------------) 1011 // 9. [---) 1012 // 10. [--------) 1013 // 11. [---------------) 1014 // 12. [------) 1015 // 13. [--) 1016 1017 // Case 1: end of next_old is before beginning of next_new 1018 // next_new: 1019 // [-------------) ... <end> 1020 // next_old: 1021 // 1. [---) ... <end> 1022 if (tcp_seq_before(next_old->second, next_new->first)) { 1023 // Actions: 1024 // advance to the next sack in old_sacks 1025 ++next_old; 1026 // retry from the top 1027 continue; 1028 } 1029 1030 // Case 13: end of next_new is before beginning of next_old 1031 // next_new: 1032 // [-------------) ... <end> 1033 // next_old: 1034 // 13. [--) ... <end> 1035 if (tcp_seq_before(next_new->second, next_old->first)) { 1036 // Actions: 1037 // add then entire length of next_new into seglen 1038 *seglen += (next_new->second - next_new->first); 1039 // insert next_new before next_old in old_sacks 1040 // (be sure to save and restore next_old iterator around insert!) 1041 int next_old_idx = int(next_old - old_sacks.begin()); 1042 old_sacks.insert(next_old, *next_new); 1043 next_old = old_sacks.begin() + next_old_idx + 1; 1044 // advance to the next remaining sack in new_sacks 1045 ++next_new_idx; 1046 // retry from the top 1047 continue; 1048 } 1049 1050 // Remaining possible configurations: 1051 // next_new: 1052 // [-------------) 1053 // next_old: 1054 // 2. [-------) 1055 // 3. [----------------) 1056 // 4. [---------------------) 1057 // 5. [----------------------------) 1058 // 6. [--------) 1059 // 7. [-------------) 1060 // 8. [--------------------) 1061 // 9. [---) 1062 // 10. [--------) 1063 // 11. [---------------) 1064 // 12. [------) 1065 1066 // Cases 2,3,6,9: end of next_old is before end of next_new 1067 // next_new: 1068 // [-------------) 1069 // next_old: 1070 // 2. [-------) 1071 // 3. [----------------) 1072 // 6. [--------) 1073 // 9. [---) 1074 // Actions: 1075 // until end of next_old is equal or after end of next_new, 1076 // repeatedly extend next_old, coalescing with next_next_old 1077 // if necessary. (and add extended bytes to seglen) 1078 while (tcp_seq_before(next_old->second, next_new->second)) { 1079 // if end of next_new doesn't collide with start of next_next_old, 1080 if (((next_old+1) == old_sacks.end()) || 1081 tcp_seq_before(next_new->second, (next_old + 1)->first)) { 1082 // extend end of next_old up to end of next_new, 1083 // adding extended bytes to seglen 1084 *seglen += (next_new->second - next_old->second); 1085 next_old->second = next_new->second; 1086 } 1087 // otherwise, coalesce next_old with next_next_old 1088 else { 1089 // add bytes to close gap between sacks to seglen 1090 *seglen += ((next_old + 1)->first - next_old->second); 1091 // coalesce next_next_old into next_old 1092 next_old->second = (next_old + 1)->second; 1093 old_sacks.erase(next_old + 1); 1094 } 1095 } 1096 // This operation turns: 1097 // Cases 2 and 3 into Case 4 or 5 1098 // Case 6 into Case 7 1099 // Case 9 into Case 10 1100 // Leaving: 1101 1102 // Remaining possible configurations: 1103 // next_new: 1104 // [-------------) 1105 // next_old: 1106 // 4. [---------------------) 1107 // 5. [----------------------------) 1108 // 7. [-------------) 1109 // 8. [--------------------) 1110 // 10. [--------) 1111 // 11. [---------------) 1112 // 12. [------) 1113 1114 // Cases 10,11,12: start of next_new is before start of next_old 1115 // next_new: 1116 // [-------------) 1117 // next_old: 1118 // 10. [--------) 1119 // 11. [---------------) 1120 // 12. [------) 1121 if (tcp_seq_before(next_new->first, next_old->first)) { 1122 // Actions: 1123 // add the unaccounted bytes in next_new to seglen 1124 *seglen += (next_old->first - next_new->first); 1125 // then pull the start of next_old back to the start of next_new 1126 next_old->first = next_new->first; 1127 } 1128 // This operation turns: 1129 // Case 10 into Case 7 1130 // Cases 11 and 12 into Case 8 1131 // Leaving: 1132 1133 // Remaining possible configurations: 1134 // next_new: 1135 // [-------------) 1136 // next_old: 1137 // 4. [---------------------) 1138 // 5. [----------------------------) 1139 // 7. [-------------) 1140 // 8. [--------------------) 1141 1142 // In these cases, the bytes in next_new are fully accounted 1143 // by the bytes in next_old, so we can move on to look at 1144 // the next sack block in new_sacks 1145 ++next_new_idx; 1146 } 1147 // Conditions for leaving loop: 1148 // - we processed all remaining new_sacks - nothing left to do 1149 // (next_new_idx == num_sack_ranges) 1150 // OR 1151 // - all remaining new_sacks start at least one byte after 1152 // the rightmost edge of the last old_sack 1153 // (meaning we can just add the remaining new_sacks to old_sacks list, 1154 // and add them directly to the goodput seglen) 1155 while (next_new_idx < num_sack_ranges) { 1156 sack_t* next_new = &new_sacks[next_new_idx]; 1157 *seglen += (next_new->second - next_new->first); 1158 old_sacks.push_back(*next_new); 1159 ++next_new_idx; 1160 } 1161 } 1162 #endif // USE_SACKS_IN_GOODPUT_CALC 1163 1164 void TCPStreamDialog::fillThroughput() 1165 { 1166 QString dlg_title = QString(tr("Throughput")) + streamDescription(); 1167 #ifdef MA_1_SECOND 1168 dlg_title.append(tr(" (MA)")); 1169 #else 1170 dlg_title.append(QString(tr(" (%1 Segment MA)")).arg(moving_avg_period_)); 1171 #endif 1172 setWindowTitle(dlg_title); 1173 title_->setText(dlg_title); 1174 1175 QCustomPlot *sp = ui->streamPlot; 1176 sp->yAxis->setLabel(segment_length_label_); 1177 sp->yAxis2->setLabel(average_throughput_label_); 1178 sp->yAxis2->setLabelColor(QColor(graph_color_2)); 1179 sp->yAxis2->setTickLabelColor(QColor(graph_color_2)); 1180 sp->yAxis2->setVisible(true); 1181 1182 base_graph_->setVisible(ui->showSegLengthCheckBox->isChecked()); 1183 tput_graph_->setVisible(ui->showThroughputCheckBox->isChecked()); 1184 goodput_graph_->setVisible(ui->showGoodputCheckBox->isChecked()); 1185 1186 #ifdef MA_1_SECOND 1187 if (!graph_.segments) { 1188 #else 1189 if (!graph_.segments || !graph_.segments->next) { 1190 #endif 1191 dlg_title.append(tr(" [not enough data]")); 1192 return; 1193 } 1194 1195 QVector<double> seg_rel_times, ack_rel_times; 1196 QVector<double> seg_lens, ack_lens; 1197 QVector<double> tput_times, gput_times; 1198 QVector<double> tputs, gputs; 1199 int oldest_seg = 0, oldest_ack = 0; 1200 guint64 seg_sum = 0, ack_sum = 0; 1201 guint32 seglen = 0; 1202 1203 #ifdef USE_SACKS_IN_GOODPUT_CALC 1204 // to incorporate SACKED segments into goodput calculation, 1205 // need to keep track of all the SACK blocks we haven't yet 1206 // fully ACKed. 1207 sack_list_t old_sacks, new_sacks; 1208 new_sacks.reserve(MAX_TCP_SACK_RANGES); 1209 // statically allocate current_sacks vector 1210 // [ std::array might be better, but that is C++11 ] 1211 for (int i = 0; i < MAX_TCP_SACK_RANGES; ++i) { 1212 new_sacks.push_back(sack_t(0,0)); 1213 } 1214 old_sacks.reserve(2*MAX_TCP_SACK_RANGES); 1215 #endif // USE_SACKS_IN_GOODPUT_CALC 1216 1217 // need first acked sequence number to jump-start 1218 // computation of acked bytes per packet 1219 guint32 last_ack = 0; 1220 for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) { 1221 // first reverse packet with ACK flag tells us first acked sequence # 1222 if (!compareHeaders(seg) && (seg->th_flags & TH_ACK)) { 1223 last_ack = seg->th_ack; 1224 break; 1225 } 1226 } 1227 // Financial charts don't show MA data until a full period has elapsed. 1228 // [ NOTE - this is because they assume that there's old data that they 1229 // don't have access to - but in our case we know that there's NO 1230 // data prior to the first packet in the stream - so it's fine to 1231 // spit out the MA immediately... ] 1232 // The Rosetta Code MA examples start spitting out values immediately. 1233 // For now use not-really-correct initial values just to keep our vector 1234 // lengths the same. 1235 #ifdef MA_1_SECOND 1236 // NOTE that for the time-based MA case, you certainly can start with the 1237 // first segment! 1238 for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) { 1239 #else 1240 for (struct segment *seg = graph_.segments->next; seg != NULL; seg = seg->next) { 1241 #endif 1242 bool is_forward_seg = compareHeaders(seg); 1243 QVector<double>& r_pkt_times = is_forward_seg ? seg_rel_times : ack_rel_times; 1244 QVector<double>& r_lens = is_forward_seg ? seg_lens : ack_lens; 1245 QVector<double>& r_Xput_times = is_forward_seg ? tput_times : gput_times; 1246 QVector<double>& r_Xputs = is_forward_seg ? tputs : gputs; 1247 int& r_oldest = is_forward_seg ? oldest_seg : oldest_ack; 1248 guint64& r_sum = is_forward_seg ? seg_sum : ack_sum; 1249 1250 double ts = (seg->rel_secs + seg->rel_usecs / 1000000.0) - ts_offset_; 1251 1252 if (is_forward_seg) { 1253 seglen = seg->th_seglen; 1254 } else { 1255 if ((seg->th_flags & TH_ACK) && 1256 tcp_seq_eq_or_after(seg->th_ack, last_ack)) { 1257 seglen = seg->th_ack - last_ack; 1258 last_ack = seg->th_ack; 1259 #ifdef USE_SACKS_IN_GOODPUT_CALC 1260 // copy any sack_ranges into new_sacks, and sort. 1261 for (int i = 0; i < seg->num_sack_ranges; ++i) { 1262 new_sacks[i].first = seg->sack_left_edge[i]; 1263 new_sacks[i].second = seg->sack_right_edge[i]; 1264 } 1265 std::sort(new_sacks.begin(), 1266 new_sacks.begin() + seg->num_sack_ranges, 1267 compare_sack); 1268 1269 // adjust the seglen based on new and old sacks, 1270 // and update the old_sacks list 1271 goodput_adjust_for_sacks(&seglen, last_ack, 1272 new_sacks, seg->num_sack_ranges, 1273 old_sacks); 1274 #endif // USE_SACKS_IN_GOODPUT_CALC 1275 } else { 1276 seglen = 0; 1277 } 1278 } 1279 1280 r_pkt_times.append(ts); 1281 r_lens.append(seglen); 1282 1283 #ifdef MA_1_SECOND 1284 while (r_oldest < r_pkt_times.size() && ts - r_pkt_times[r_oldest] > ma_window_size_) { 1285 r_sum -= r_lens[r_oldest]; 1286 // append points where a packet LEAVES the MA window 1287 // (as well as, below, where they ENTER the MA window) 1288 r_Xputs.append(r_sum * 8.0 / ma_window_size_); 1289 r_Xput_times.append(r_pkt_times[r_oldest] + ma_window_size_); 1290 r_oldest++; 1291 } 1292 #else 1293 if (r_lens.size() > moving_avg_period_) { 1294 r_sum -= r_lens[r_oldest]; 1295 r_oldest++; 1296 } 1297 #endif 1298 1299 // av_Xput computes Xput, i.e.: 1300 // throughput for forward packets 1301 // goodput for reverse packets 1302 double av_Xput; 1303 r_sum += seglen; 1304 #ifdef MA_1_SECOND 1305 // for time-based MA, delta_t is constant 1306 av_Xput = r_sum * 8.0 / ma_window_size_; 1307 #else 1308 double dtime = 0.0; 1309 if (r_oldest > 0) 1310 dtime = ts - r_pkt_times[r_oldest-1]; 1311 if (dtime > 0.0) { 1312 av_Xput = r_sum * 8.0 / dtime; 1313 } else { 1314 av_Xput = 0.0; 1315 } 1316 #endif 1317 1318 // Add a data point only if our time window has advanced. Otherwise 1319 // update the most recent point. (We might want to show a warning 1320 // for out-of-order packets.) 1321 if (r_Xput_times.size() > 0 && ts <= r_Xput_times.last()) { 1322 r_Xputs[r_Xputs.size() - 1] = av_Xput; 1323 } else { 1324 r_Xputs.append(av_Xput); 1325 r_Xput_times.append(ts); 1326 } 1327 } 1328 base_graph_->setData(seg_rel_times, seg_lens); 1329 tput_graph_->setData(tput_times, tputs); 1330 goodput_graph_->setData(gput_times, gputs); 1331 } 1332 1333 // rtt_selectively_ack_range: 1334 // "Helper" function for fillRoundTripTime 1335 // given an rtt_unack list, two pointers to a range of segments in the list, 1336 // and the [left,right) edges of a SACK block, selectively ACKs the range 1337 // from "begin" to "end" - possibly splitting one segment in the range 1338 // into two (and relinking the new segment in order after the first) 1339 // 1340 // Assumptions: 1341 // "begin must be non-NULL 1342 // "begin" must precede "end" (or "end" must be NULL) 1343 // [ there are minor optimizations that could be added if 1344 // the range from "begin" to "end" are in sequence number order. 1345 // (this function would preserve that as an invariant). ] 1346 static struct rtt_unack * 1347 rtt_selectively_ack_range(QVector<double>& x_vals, bool bySeqNumber, 1348 QVector<double>& rtt, 1349 struct rtt_unack **list, 1350 struct rtt_unack *begin, struct rtt_unack *end, 1351 unsigned int left, unsigned int right, double rt_val) { 1352 struct rtt_unack *cur, *next; 1353 // sanity check: 1354 if (tcp_seq_eq_or_after(left, right)) 1355 return begin; 1356 // real work: 1357 for (cur = begin; cur != end; cur = next) { 1358 next = cur->next; 1359 // check #1: does [left,right) intersect current unack at all? 1360 // (if not, we can just move on to the next unack) 1361 if (tcp_seq_eq_or_after(cur->seqno, right) || 1362 tcp_seq_eq_or_after(left, cur->end_seqno)) { 1363 // no intersection - just skip this. 1364 continue; 1365 } 1366 // yes, we intersect! 1367 int left_end_acked = tcp_seq_eq_or_after(cur->seqno, left); 1368 int right_end_acked = tcp_seq_eq_or_after(right, cur->end_seqno); 1369 // check #2 - did we fully ack the current unack? 1370 // (if so, we can delete it and move on) 1371 if (left_end_acked && right_end_acked) { 1372 // ACK the whole segment 1373 if (bySeqNumber) { 1374 x_vals.append(cur->seqno); 1375 } else { 1376 x_vals.append(cur->time); 1377 } 1378 rtt.append((rt_val - cur->time) * 1000.0); 1379 // in this case, we will delete current unack 1380 // [ update "begin" if necessary - we will return it to the 1381 // caller to let them know we deleted it ] 1382 if (cur == begin) 1383 begin = next; 1384 rtt_delete_unack_from_list(list, cur); 1385 continue; 1386 } 1387 // check #3 - did we ACK the left-hand side of the current unack? 1388 // (if so, we can just modify it and move on) 1389 if (left_end_acked) { // and right_end_not_acked 1390 // ACK the left end 1391 if (bySeqNumber) { 1392 x_vals.append(cur->seqno); 1393 } else { 1394 x_vals.append(cur->time); 1395 } 1396 rtt.append((rt_val - cur->time) * 1000.0); 1397 // in this case, "right" marks the start of remaining bytes 1398 cur->seqno = right; 1399 continue; 1400 } 1401 // check #4 - did we ACK the right-hand side of the current unack? 1402 // (if so, we can just modify it and move on) 1403 if (right_end_acked) { // and left_end_not_acked 1404 // ACK the right end 1405 if (bySeqNumber) { 1406 x_vals.append(left); 1407 } else { 1408 x_vals.append(cur->time); 1409 } 1410 rtt.append((rt_val - cur->time) * 1000.0); 1411 // in this case, "left" is just beyond the remaining bytes 1412 cur->end_seqno = left; 1413 continue; 1414 } 1415 // at this point, we know: 1416 // - the SACK block does intersect this unack, but 1417 // - it does not intersect the left or right endpoints 1418 // Therefore, it must intersect the middle, so we must split the unack 1419 // into left and right unacked segments: 1420 // ACK the SACK block 1421 if (bySeqNumber) { 1422 x_vals.append(left); 1423 } else { 1424 x_vals.append(cur->time); 1425 } 1426 rtt.append((rt_val - cur->time) * 1000.0); 1427 // then split cur into two unacked segments 1428 // (linking the right-hand unack after the left) 1429 cur->next = rtt_get_new_unack(cur->time, right, cur->end_seqno - right); 1430 cur->next->next = next; 1431 cur->end_seqno = left; 1432 } 1433 return begin; 1434 } 1435 1436 void TCPStreamDialog::fillRoundTripTime() 1437 { 1438 QString dlg_title = QString(tr("Round Trip Time")) + streamDescription(); 1439 setWindowTitle(dlg_title); 1440 title_->setText(dlg_title); 1441 1442 QCustomPlot *sp = ui->streamPlot; 1443 bool bySeqNumber = ui->bySeqNumberCheckBox->isChecked(); 1444 1445 if (bySeqNumber) { 1446 sequence_num_map_.clear(); 1447 sp->xAxis->setLabel(sequence_number_label_); 1448 sp->xAxis->setNumberFormat("f"); 1449 sp->xAxis->setNumberPrecision(0); 1450 } 1451 sp->yAxis->setLabel(round_trip_time_ms_label_); 1452 sp->yAxis->setNumberFormat("gb"); 1453 sp->yAxis->setNumberPrecision(3); 1454 1455 base_graph_->setLineStyle(QCPGraph::lsLine); 1456 1457 QVector<double> x_vals, rtt; 1458 guint32 seq_base = 0; 1459 struct rtt_unack *unack_list = NULL, *u = NULL; 1460 for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) { 1461 if (compareHeaders(seg)) { 1462 seq_base = seg->th_seq; 1463 break; 1464 } 1465 } 1466 for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) { 1467 if (compareHeaders(seg)) { 1468 guint32 seqno = seg->th_seq - seq_base; 1469 if (seg->th_seglen && !rtt_is_retrans(unack_list, seqno)) { 1470 double rt_val = seg->rel_secs + seg->rel_usecs / 1000000.0; 1471 rt_val -= ts_offset_; 1472 u = rtt_get_new_unack(rt_val, seqno, seg->th_seglen); 1473 if (!u) { 1474 // make sure to free list before returning! 1475 rtt_destroy_unack_list(&unack_list); 1476 return; 1477 } 1478 rtt_put_unack_on_list(&unack_list, u); 1479 } 1480 } else { 1481 guint32 ack_no = seg->th_ack - seq_base; 1482 double rt_val = seg->rel_secs + seg->rel_usecs / 1000000.0; 1483 rt_val -= ts_offset_; 1484 struct rtt_unack *v; 1485 1486 for (u = unack_list; u; u = v) { 1487 if (tcp_seq_after(ack_no, u->seqno)) { 1488 // full or partial ack of seg by ack_no 1489 if (bySeqNumber) { 1490 x_vals.append(u->seqno); 1491 sequence_num_map_.insert(u->seqno, seg); 1492 } else { 1493 x_vals.append(u->time); 1494 } 1495 rtt.append((rt_val - u->time) * 1000.0); 1496 if (tcp_seq_eq_or_after(ack_no, u->end_seqno)) { 1497 // fully acked segment - nothing more to see here 1498 v = u->next; 1499 rtt_delete_unack_from_list(&unack_list, u); 1500 // no need to compare SACK blocks for fully ACKed seg 1501 continue; 1502 } else { 1503 // partial ack of GSO seg 1504 u->seqno = ack_no; 1505 // (keep going - still need to compare SACK blocks...) 1506 } 1507 } 1508 v = u->next; 1509 // selectively acking u more than once 1510 // can shatter it into multiple intervals. 1511 // If we link those back into the list between u and v, 1512 // then each subsequent SACK selectively ACKs that range. 1513 for (int i = 0; i < seg->num_sack_ranges; ++i) { 1514 guint32 left = seg->sack_left_edge[i] - seq_base; 1515 guint32 right = seg->sack_right_edge[i] - seq_base; 1516 u = rtt_selectively_ack_range(x_vals, bySeqNumber, rtt, 1517 &unack_list, u, v, 1518 left, right, rt_val); 1519 // if range is empty after selective ack, we can 1520 // skip the rest of the SACK blocks 1521 if (u == v) break; 1522 } 1523 } 1524 } 1525 } 1526 // it's possible there's still unacked segs - so be sure to free list! 1527 rtt_destroy_unack_list(&unack_list); 1528 base_graph_->setData(x_vals, rtt); 1529 } 1530 1531 void TCPStreamDialog::fillWindowScale() 1532 { 1533 QString dlg_title = QString(tr("Window Scaling")) + streamDescription(); 1534 setWindowTitle(dlg_title); 1535 title_->setText(dlg_title); 1536 1537 QCustomPlot *sp = ui->streamPlot; 1538 // use base_graph_ to represent unacked window size 1539 // (estimate of congestion window) 1540 base_graph_->setLineStyle(QCPGraph::lsStepLeft); 1541 // use rwin_graph_ here to show rwin window scale 1542 // (derived from ACK packets) 1543 base_graph_->setVisible(ui->showBytesOutCheckBox->isChecked()); 1544 rwin_graph_->setVisible(ui->showRcvWinCheckBox->isChecked()); 1545 1546 QVector<double> rel_time, win_size; 1547 QVector<double> cwnd_time, cwnd_size; 1548 guint32 last_ack = 0; 1549 bool found_first_ack = false; 1550 for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) { 1551 double ts = seg->rel_secs + seg->rel_usecs / 1000000.0; 1552 1553 // The receive window that applies to this flow comes 1554 // from packets in the opposite direction 1555 if (compareHeaders(seg)) { 1556 // compute bytes_in_flight for cwnd graph 1557 guint32 end_seq = seg->th_seq + seg->th_seglen; 1558 if (found_first_ack && 1559 tcp_seq_eq_or_after(end_seq, last_ack)) { 1560 cwnd_time.append(ts - ts_offset_); 1561 cwnd_size.append((double)(end_seq - last_ack)); 1562 } 1563 } else { 1564 // packet in opposite direction - has advertised rwin 1565 guint16 flags = seg->th_flags; 1566 1567 if ((flags & (TH_SYN|TH_RST)) == 0) { 1568 rel_time.append(ts - ts_offset_); 1569 win_size.append(seg->th_win); 1570 } 1571 if ((flags & (TH_ACK)) != 0) { 1572 // use this to update last_ack 1573 if (!found_first_ack || 1574 tcp_seq_eq_or_after(seg->th_ack, last_ack)) { 1575 last_ack = seg->th_ack; 1576 found_first_ack = true; 1577 } 1578 } 1579 } 1580 } 1581 base_graph_->setData(cwnd_time, cwnd_size); 1582 rwin_graph_->setData(rel_time, win_size); 1583 sp->yAxis->setLabel(window_size_label_); 1584 } 1585 1586 QString TCPStreamDialog::streamDescription() 1587 { 1588 QString description(tr(" for %1:%2 %3 %4:%5") 1589 .arg(address_to_qstring(&graph_.src_address)) 1590 .arg(graph_.src_port) 1591 .arg(UTF8_RIGHTWARDS_ARROW) 1592 .arg(address_to_qstring(&graph_.dst_address)) 1593 .arg(graph_.dst_port)); 1594 return description; 1595 } 1596 1597 bool TCPStreamDialog::compareHeaders(segment *seg) 1598 { 1599 return (compare_headers(&graph_.src_address, &graph_.dst_address, 1600 graph_.src_port, graph_.dst_port, 1601 &seg->ip_src, &seg->ip_dst, 1602 seg->th_sport, seg->th_dport, 1603 COMPARE_CURR_DIR)); 1604 } 1605 1606 void TCPStreamDialog::toggleTracerStyle(bool force_default) 1607 { 1608 if (!tracer_->visible() && !force_default) return; 1609 1610 QPen sp_pen = ui->streamPlot->graph(0)->pen(); 1611 QCPItemTracer::TracerStyle tstyle = QCPItemTracer::tsCrosshair; 1612 QPen tr_pen = QPen(tracer_->pen()); 1613 QColor tr_color = sp_pen.color(); 1614 1615 if (force_default || tracer_->style() != QCPItemTracer::tsCircle) { 1616 tstyle = QCPItemTracer::tsCircle; 1617 tr_color.setAlphaF(1.0); 1618 tr_pen.setWidthF(1.5); 1619 } else { 1620 tr_color.setAlphaF(0.5); 1621 tr_pen.setWidthF(1.0); 1622 } 1623 1624 tracer_->setStyle(tstyle); 1625 tr_pen.setColor(tr_color); 1626 tracer_->setPen(tr_pen); 1627 ui->streamPlot->replot(); 1628 } 1629 1630 QRectF TCPStreamDialog::getZoomRanges(QRect zoom_rect) 1631 { 1632 QRectF zoom_ranges = QRectF(); 1633 1634 QCustomPlot *sp = ui->streamPlot; 1635 QRect zr = zoom_rect.normalized(); 1636 1637 if (zr.width() < min_zoom_pixels_ && zr.height() < min_zoom_pixels_) { 1638 return zoom_ranges; 1639 } 1640 1641 QRect ar = sp->axisRect()->rect(); 1642 if (ar.intersects(zr)) { 1643 QRect zsr = ar.intersected(zr); 1644 zoom_ranges.setX(sp->xAxis->range().lower 1645 + sp->xAxis->range().size() * (zsr.left() - ar.left()) / ar.width()); 1646 zoom_ranges.setWidth(sp->xAxis->range().size() * zsr.width() / ar.width()); 1647 1648 // QRects grow down 1649 zoom_ranges.setY(sp->yAxis->range().lower 1650 + sp->yAxis->range().size() * (ar.bottom() - zsr.bottom()) / ar.height()); 1651 zoom_ranges.setHeight(sp->yAxis->range().size() * zsr.height() / ar.height()); 1652 } 1653 return zoom_ranges; 1654 } 1655 1656 void TCPStreamDialog::graphClicked(QMouseEvent *event) 1657 { 1658 QCustomPlot *sp = ui->streamPlot; 1659 1660 // mouse press on graph should reset focus to graph 1661 sp->setFocus(); 1662 1663 if (event->button() == Qt::RightButton) { 1664 // XXX We should find some way to get streamPlot to handle a 1665 // contextMenuEvent instead. 1666 ctx_menu_.exec(event->globalPos()); 1667 } else if (mouse_drags_) { 1668 if (sp->axisRect()->rect().contains(event->pos())) { 1669 sp->setCursor(QCursor(Qt::ClosedHandCursor)); 1670 } 1671 on_actionGoToPacket_triggered(); 1672 } else { 1673 if (!rubber_band_) { 1674 rubber_band_ = new QRubberBand(QRubberBand::Rectangle, sp); 1675 } 1676 rb_origin_ = event->pos(); 1677 rubber_band_->setGeometry(QRect(rb_origin_, QSize())); 1678 rubber_band_->show(); 1679 } 1680 } 1681 1682 void TCPStreamDialog::axisClicked(QCPAxis *axis, QCPAxis::SelectablePart, QMouseEvent *) 1683 { 1684 QCustomPlot *sp = ui->streamPlot; 1685 1686 if (axis == sp->xAxis) { 1687 switch (graph_.type) { 1688 case GRAPH_THROUGHPUT: 1689 case GRAPH_TSEQ_STEVENS: 1690 case GRAPH_TSEQ_TCPTRACE: 1691 case GRAPH_WSCALE: 1692 case GRAPH_RTT: 1693 ts_origin_conn_ = ts_origin_conn_ ? false : true; 1694 fillGraph(); 1695 break; 1696 default: 1697 break; 1698 } 1699 } else if (axis == sp->yAxis) { 1700 switch (graph_.type) { 1701 case GRAPH_TSEQ_STEVENS: 1702 case GRAPH_TSEQ_TCPTRACE: 1703 seq_origin_zero_ = seq_origin_zero_ ? false : true; 1704 fillGraph(); 1705 break; 1706 default: 1707 break; 1708 } 1709 } 1710 } 1711 1712 // Setting mouseTracking on our streamPlot may not be as reliable 1713 // as we need. If it's not we might want to poll the mouse position 1714 // using a QTimer instead. 1715 void TCPStreamDialog::mouseMoved(QMouseEvent *event) 1716 { 1717 QCustomPlot *sp = ui->streamPlot; 1718 Qt::CursorShape shape = Qt::ArrowCursor; 1719 if (event) { 1720 if (event->buttons().testFlag(Qt::LeftButton)) { 1721 if (mouse_drags_) { 1722 shape = Qt::ClosedHandCursor; 1723 } else { 1724 shape = Qt::CrossCursor; 1725 } 1726 } else { 1727 if (sp->axisRect()->rect().contains(event->pos())) { 1728 if (mouse_drags_) { 1729 shape = Qt::OpenHandCursor; 1730 } else { 1731 shape = Qt::CrossCursor; 1732 } 1733 } 1734 } 1735 } 1736 sp->setCursor(QCursor(shape)); 1737 1738 QString hint = "<small><i>"; 1739 if (mouse_drags_) { 1740 double tr_key = tracer_->position->key(); 1741 struct segment *packet_seg = NULL; 1742 packet_num_ = 0; 1743 1744 // XXX If we have multiple packets with the same timestamp tr_key 1745 // may not return the packet we want. It might be possible to fudge 1746 // unique keys using nextafter(). 1747 if (event && tracer_->graph() && tracer_->position->axisRect()->rect().contains(event->pos())) { 1748 switch (graph_.type) { 1749 case GRAPH_TSEQ_STEVENS: 1750 case GRAPH_TSEQ_TCPTRACE: 1751 case GRAPH_THROUGHPUT: 1752 case GRAPH_WSCALE: 1753 packet_seg = time_stamp_map_.value(tr_key, NULL); 1754 break; 1755 case GRAPH_RTT: 1756 if (ui->bySeqNumberCheckBox->isChecked()) 1757 packet_seg = sequence_num_map_.value(tr_key, NULL); 1758 else 1759 packet_seg = time_stamp_map_.value(tr_key, NULL); 1760 default: 1761 break; 1762 } 1763 } 1764 1765 if (!packet_seg) { 1766 tracer_->setVisible(false); 1767 hint += "Hover over the graph for details. " + stream_desc_ + "</i></small>"; 1768 ui->hintLabel->setText(hint); 1769 ui->streamPlot->replot(); 1770 return; 1771 } 1772 1773 tracer_->setVisible(true); 1774 packet_num_ = packet_seg->num; 1775 hint += tr("%1 %2 (%3s len %4 seq %5 ack %6 win %7)") 1776 .arg(cap_file_ ? tr("Click to select packet") : tr("Packet")) 1777 .arg(packet_num_) 1778 .arg(QString::number(packet_seg->rel_secs + packet_seg->rel_usecs / 1000000.0, 'g', 4)) 1779 .arg(packet_seg->th_seglen) 1780 .arg(packet_seg->th_seq) 1781 .arg(packet_seg->th_ack) 1782 .arg(packet_seg->th_win); 1783 tracer_->setGraphKey(ui->streamPlot->xAxis->pixelToCoord(event->pos().x())); 1784 sp->replot(); 1785 } else { 1786 if (rubber_band_ && rubber_band_->isVisible() && event) { 1787 rubber_band_->setGeometry(QRect(rb_origin_, event->pos()).normalized()); 1788 QRectF zoom_ranges = getZoomRanges(QRect(rb_origin_, event->pos())); 1789 if (zoom_ranges.width() > 0.0 && zoom_ranges.height() > 0.0) { 1790 hint += tr("Release to zoom, x = %1 to %2, y = %3 to %4") 1791 .arg(zoom_ranges.x()) 1792 .arg(zoom_ranges.x() + zoom_ranges.width()) 1793 .arg(zoom_ranges.y()) 1794 .arg(zoom_ranges.y() + zoom_ranges.height()); 1795 } else { 1796 hint += tr("Unable to select range."); 1797 } 1798 } else { 1799 hint += tr("Click to select a portion of the graph."); 1800 } 1801 } 1802 hint += " " + stream_desc_ + "</i></small>"; 1803 ui->hintLabel->setText(hint); 1804 } 1805 1806 void TCPStreamDialog::mouseReleased(QMouseEvent *event) 1807 { 1808 if (rubber_band_ && rubber_band_->isVisible()) { 1809 rubber_band_->hide(); 1810 if (!mouse_drags_) { 1811 QRectF zoom_ranges = getZoomRanges(QRect(rb_origin_, event->pos())); 1812 if (zoom_ranges.width() > 0.0 && zoom_ranges.height() > 0.0) { 1813 QCustomPlot *sp = ui->streamPlot; 1814 sp->xAxis->setRangeLower(zoom_ranges.x()); 1815 sp->xAxis->setRangeUpper(zoom_ranges.x() + zoom_ranges.width()); 1816 sp->yAxis->setRangeLower(zoom_ranges.y()); 1817 sp->yAxis->setRangeUpper(zoom_ranges.y() + zoom_ranges.height()); 1818 sp->replot(); 1819 } 1820 } 1821 } else if (ui->streamPlot->cursor().shape() == Qt::ClosedHandCursor) { 1822 ui->streamPlot->setCursor(QCursor(Qt::OpenHandCursor)); 1823 } 1824 } 1825 1826 void TCPStreamDialog::transformYRange(const QCPRange &y_range1) 1827 { 1828 if (y_axis_xfrm_.isIdentity()) return; 1829 1830 QCustomPlot *sp = ui->streamPlot; 1831 QLineF yp1 = QLineF(1.0, y_range1.lower, 1.0, y_range1.upper); 1832 QLineF yp2 = y_axis_xfrm_.map(yp1); 1833 1834 sp->yAxis2->setRangeUpper(yp2.y2()); 1835 sp->yAxis2->setRangeLower(yp2.y1()); 1836 } 1837 1838 // XXX - We have similar code in io_graph_dialog and packet_diagram. Should this be a common routine? 1839 void TCPStreamDialog::on_buttonBox_accepted() 1840 { 1841 QString file_name, extension; 1842 QDir path(wsApp->lastOpenDir()); 1843 QString pdf_filter = tr("Portable Document Format (*.pdf)"); 1844 QString png_filter = tr("Portable Network Graphics (*.png)"); 1845 QString bmp_filter = tr("Windows Bitmap (*.bmp)"); 1846 // Gaze upon my beautiful graph with lossy artifacts! 1847 QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)"); 1848 QString filter = QString("%1;;%2;;%3;;%4") 1849 .arg(pdf_filter) 1850 .arg(png_filter) 1851 .arg(bmp_filter) 1852 .arg(jpeg_filter); 1853 1854 file_name = WiresharkFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Graph As…")), 1855 path.canonicalPath(), filter, &extension); 1856 1857 if (file_name.length() > 0) { 1858 bool save_ok = false; 1859 if (extension.compare(pdf_filter) == 0) { 1860 save_ok = ui->streamPlot->savePdf(file_name); 1861 } else if (extension.compare(png_filter) == 0) { 1862 save_ok = ui->streamPlot->savePng(file_name); 1863 } else if (extension.compare(bmp_filter) == 0) { 1864 save_ok = ui->streamPlot->saveBmp(file_name); 1865 } else if (extension.compare(jpeg_filter) == 0) { 1866 save_ok = ui->streamPlot->saveJpg(file_name); 1867 } 1868 // else error dialog? 1869 if (save_ok) { 1870 wsApp->setLastOpenDirFromFilename(file_name); 1871 } 1872 } 1873 } 1874 1875 void TCPStreamDialog::on_graphTypeComboBox_currentIndexChanged(int index) 1876 { 1877 if (index < 0) return; 1878 graph_.type = static_cast<tcp_graph_type>(ui->graphTypeComboBox->itemData(index).toInt()); 1879 showWidgetsForGraphType(); 1880 1881 fillGraph(/*reset_axes=*/true, /*set_focus=*/false); 1882 } 1883 1884 void TCPStreamDialog::on_resetButton_clicked() 1885 { 1886 resetAxes(); 1887 } 1888 1889 void TCPStreamDialog::setCaptureFile(capture_file *cf) 1890 { 1891 if (!cf) { // We only want to know when the file closes. 1892 cap_file_ = NULL; 1893 } 1894 } 1895 1896 void TCPStreamDialog::updateGraph() 1897 { 1898 graph_updater_.doUpdate(); 1899 } 1900 1901 void TCPStreamDialog::on_streamNumberSpinBox_valueChanged(int new_stream) 1902 { 1903 if (new_stream >= 0 && new_stream < int(get_tcp_stream_count())) { 1904 graph_updater_.triggerUpdate(1000, /*reset_axes =*/true); 1905 } 1906 } 1907 1908 void TCPStreamDialog::on_streamNumberSpinBox_editingFinished() 1909 { 1910 updateGraph(); 1911 } 1912 1913 void TCPStreamDialog::on_maWindowSizeSpinBox_valueChanged(double new_ma_size) 1914 { 1915 if (new_ma_size > 0.0) { 1916 ma_window_size_ = new_ma_size; 1917 graph_updater_.triggerUpdate(1000, /*reset_axes =*/false); 1918 } 1919 } 1920 1921 void TCPStreamDialog::on_maWindowSizeSpinBox_editingFinished() 1922 { 1923 updateGraph(); 1924 } 1925 1926 void TCPStreamDialog::on_selectSACKsCheckBox_stateChanged(int /* state */) 1927 { 1928 fillGraph(/*reset_axes=*/false, /*set_focus=*/false); 1929 } 1930 1931 void TCPStreamDialog::on_otherDirectionButton_clicked() 1932 { 1933 on_actionSwitchDirection_triggered(); 1934 } 1935 1936 void TCPStreamDialog::on_dragRadioButton_toggled(bool checked) 1937 { 1938 if (checked) { 1939 mouse_drags_ = true; 1940 if (rubber_band_ && rubber_band_->isVisible()) 1941 rubber_band_->hide(); 1942 ui->streamPlot->setInteractions( 1943 QCP::iRangeDrag | 1944 QCP::iRangeZoom 1945 ); 1946 } 1947 } 1948 1949 void TCPStreamDialog::on_zoomRadioButton_toggled(bool checked) 1950 { 1951 if (checked) { 1952 mouse_drags_ = false; 1953 ui->streamPlot->setInteractions(QCP::Interactions()); 1954 } 1955 } 1956 1957 void TCPStreamDialog::on_bySeqNumberCheckBox_stateChanged(int /* state */) 1958 { 1959 fillGraph(/*reset_axes=*/true, /*set_focus=*/false); 1960 } 1961 1962 void TCPStreamDialog::on_showSegLengthCheckBox_stateChanged(int state) 1963 { 1964 bool visible = (state != 0); 1965 if (graph_.type == GRAPH_THROUGHPUT && base_graph_ != NULL) { 1966 base_graph_->setVisible(visible); 1967 tracer_->setGraph(visible ? base_graph_ : NULL); 1968 ui->streamPlot->replot(); 1969 } 1970 } 1971 1972 void TCPStreamDialog::on_showThroughputCheckBox_stateChanged(int state) 1973 { 1974 bool visible = (state != 0); 1975 if (graph_.type == GRAPH_THROUGHPUT && tput_graph_ != NULL) { 1976 tput_graph_->setVisible(visible); 1977 ui->streamPlot->replot(); 1978 } 1979 } 1980 1981 void TCPStreamDialog::on_showGoodputCheckBox_stateChanged(int state) 1982 { 1983 bool visible = (state != 0); 1984 if (graph_.type == GRAPH_THROUGHPUT && goodput_graph_ != NULL) { 1985 goodput_graph_->setVisible(visible); 1986 ui->streamPlot->replot(); 1987 } 1988 } 1989 1990 void TCPStreamDialog::on_showRcvWinCheckBox_stateChanged(int state) 1991 { 1992 bool visible = (state != 0); 1993 if (graph_.type == GRAPH_WSCALE && rwin_graph_ != NULL) { 1994 rwin_graph_->setVisible(visible); 1995 ui->streamPlot->replot(); 1996 } 1997 } 1998 1999 void TCPStreamDialog::on_showBytesOutCheckBox_stateChanged(int state) 2000 { 2001 bool visible = (state != 0); 2002 if (graph_.type == GRAPH_WSCALE && base_graph_ != NULL) { 2003 base_graph_->setVisible(visible); 2004 tracer_->setGraph(visible ? base_graph_ : NULL); 2005 ui->streamPlot->replot(); 2006 } 2007 } 2008 2009 void TCPStreamDialog::on_actionZoomIn_triggered() 2010 { 2011 zoomAxes(true); 2012 } 2013 2014 void TCPStreamDialog::on_actionZoomInX_triggered() 2015 { 2016 zoomXAxis(true); 2017 } 2018 2019 void TCPStreamDialog::on_actionZoomInY_triggered() 2020 { 2021 zoomYAxis(true); 2022 } 2023 2024 void TCPStreamDialog::on_actionZoomOut_triggered() 2025 { 2026 zoomAxes(false); 2027 } 2028 2029 void TCPStreamDialog::on_actionZoomOutX_triggered() 2030 { 2031 zoomXAxis(false); 2032 } 2033 2034 void TCPStreamDialog::on_actionZoomOutY_triggered() 2035 { 2036 zoomYAxis(false); 2037 } 2038 2039 void TCPStreamDialog::on_actionReset_triggered() 2040 { 2041 on_resetButton_clicked(); 2042 } 2043 2044 void TCPStreamDialog::on_actionMoveRight10_triggered() 2045 { 2046 panAxes(10, 0); 2047 } 2048 2049 void TCPStreamDialog::on_actionMoveLeft10_triggered() 2050 { 2051 panAxes(-10, 0); 2052 } 2053 2054 void TCPStreamDialog::on_actionMoveUp10_triggered() 2055 { 2056 panAxes(0, 10); 2057 } 2058 2059 void TCPStreamDialog::on_actionMoveDown10_triggered() 2060 { 2061 panAxes(0, -10); 2062 } 2063 2064 void TCPStreamDialog::on_actionMoveRight1_triggered() 2065 { 2066 panAxes(1, 0); 2067 } 2068 2069 void TCPStreamDialog::on_actionMoveLeft1_triggered() 2070 { 2071 panAxes(-1, 0); 2072 } 2073 2074 void TCPStreamDialog::on_actionMoveUp1_triggered() 2075 { 2076 panAxes(0, 1); 2077 } 2078 2079 void TCPStreamDialog::on_actionMoveDown1_triggered() 2080 { 2081 panAxes(0, -1); 2082 } 2083 2084 void TCPStreamDialog::on_actionNextStream_triggered() 2085 { 2086 if (int(graph_.stream) < int(get_tcp_stream_count()) - 1) { 2087 ui->streamNumberSpinBox->setValue(graph_.stream + 1); 2088 updateGraph(); 2089 } 2090 } 2091 2092 void TCPStreamDialog::on_actionPreviousStream_triggered() 2093 { 2094 if (graph_.stream > 0) { 2095 ui->streamNumberSpinBox->setValue(graph_.stream - 1); 2096 updateGraph(); 2097 } 2098 } 2099 2100 void TCPStreamDialog::on_actionSwitchDirection_triggered() 2101 { 2102 address tmp_addr; 2103 guint16 tmp_port; 2104 2105 copy_address(&tmp_addr, &graph_.src_address); 2106 tmp_port = graph_.src_port; 2107 free_address(&graph_.src_address); 2108 copy_address(&graph_.src_address, &graph_.dst_address); 2109 graph_.src_port = graph_.dst_port; 2110 free_address(&graph_.dst_address); 2111 copy_address(&graph_.dst_address, &tmp_addr); 2112 graph_.dst_port = tmp_port; 2113 free_address(&tmp_addr); 2114 2115 fillGraph(/*reset_axes=*/true, /*set_focus=*/false); 2116 } 2117 2118 void TCPStreamDialog::on_actionGoToPacket_triggered() 2119 { 2120 if (tracer_->visible() && cap_file_ && packet_num_ > 0) { 2121 emit goToPacket(packet_num_); 2122 } 2123 } 2124 2125 void TCPStreamDialog::on_actionDragZoom_triggered() 2126 { 2127 if (mouse_drags_) { 2128 ui->zoomRadioButton->toggle(); 2129 } else { 2130 ui->dragRadioButton->toggle(); 2131 } 2132 } 2133 2134 void TCPStreamDialog::on_actionToggleSequenceNumbers_triggered() 2135 { 2136 seq_origin_zero_ = seq_origin_zero_ ? false : true; 2137 fillGraph(); 2138 } 2139 2140 void TCPStreamDialog::on_actionToggleTimeOrigin_triggered() 2141 { 2142 ts_origin_conn_ = ts_origin_conn_ ? false : true; 2143 fillGraph(); 2144 } 2145 2146 void TCPStreamDialog::on_actionRoundTripTime_triggered() 2147 { 2148 ui->graphTypeComboBox->setCurrentIndex(ui->graphTypeComboBox->findData(GRAPH_RTT)); 2149 } 2150 2151 void TCPStreamDialog::on_actionThroughput_triggered() 2152 { 2153 ui->graphTypeComboBox->setCurrentIndex(ui->graphTypeComboBox->findData(GRAPH_THROUGHPUT)); 2154 } 2155 2156 void TCPStreamDialog::on_actionStevens_triggered() 2157 { 2158 ui->graphTypeComboBox->setCurrentIndex(ui->graphTypeComboBox->findData(GRAPH_TSEQ_STEVENS)); 2159 } 2160 2161 void TCPStreamDialog::on_actionTcptrace_triggered() 2162 { 2163 ui->graphTypeComboBox->setCurrentIndex(ui->graphTypeComboBox->findData(GRAPH_TSEQ_TCPTRACE)); 2164 } 2165 2166 void TCPStreamDialog::on_actionWindowScaling_triggered() 2167 { 2168 ui->graphTypeComboBox->setCurrentIndex(ui->graphTypeComboBox->findData(GRAPH_WSCALE)); 2169 } 2170 2171 void TCPStreamDialog::GraphUpdater::triggerUpdate(int timeout, bool reset_axes) 2172 { 2173 if (!hasPendingUpdate()) { 2174 graph_update_timer_ = new QTimer(dialog_); 2175 graph_update_timer_->setSingleShot(true); 2176 dialog_->connect(graph_update_timer_, SIGNAL(timeout()), dialog_, SLOT(updateGraph())); 2177 } 2178 reset_axes_ = (reset_axes_ || reset_axes); 2179 graph_update_timer_->start(timeout); 2180 } 2181 2182 void TCPStreamDialog::GraphUpdater::clearPendingUpdate() 2183 { 2184 if (hasPendingUpdate()) { 2185 if (graph_update_timer_->isActive()) 2186 graph_update_timer_->stop(); 2187 delete graph_update_timer_; 2188 graph_update_timer_ = NULL; 2189 reset_axes_ = false; 2190 } 2191 } 2192 2193 void TCPStreamDialog::GraphUpdater::doUpdate() 2194 { 2195 if (hasPendingUpdate()) { 2196 bool reset_axes = reset_axes_; 2197 clearPendingUpdate(); 2198 // if the stream has changed, update the data here 2199 int new_stream = dialog_->ui->streamNumberSpinBox->value(); 2200 if ((int(dialog_->graph_.stream) != new_stream) && 2201 (new_stream >= 0 && new_stream < int(get_tcp_stream_count()))) { 2202 dialog_->graph_.stream = new_stream; 2203 dialog_->findStream(); 2204 } 2205 dialog_->fillGraph(reset_axes, /*set_focus =*/false); 2206 } 2207 } 2208 2209 void TCPStreamDialog::on_buttonBox_helpRequested() 2210 { 2211 wsApp->helpTopicAction(HELP_STATS_TCP_STREAM_GRAPHS_DIALOG); 2212 } 2213