1 /* multicast_statistics_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 "multicast_statistics_dialog.h" 11 12 #include <QFormLayout> 13 #include <QLabel> 14 #include <QPushButton> 15 #include <QTreeWidget> 16 17 #include <ui/qt/utils/qt_ui_utils.h> 18 #include <ui/qt/widgets/syntax_line_edit.h> 19 #include "wireshark_application.h" 20 21 enum { 22 col_src_addr_, 23 col_src_port_, 24 col_dst_addr_, 25 col_dst_port_, 26 col_packets_, 27 col_packets_s_, 28 col_avg_bw_, 29 col_max_bw_, 30 col_max_burst_, 31 col_burst_alarms_, 32 col_max_buffers_, 33 col_buffer_alarms_ 34 }; 35 36 enum { 37 mcast_table_type_ = 1000 38 }; 39 40 class MulticastStatTreeWidgetItem : public QTreeWidgetItem 41 { 42 public: 43 MulticastStatTreeWidgetItem(QTreeWidget *parent) : 44 QTreeWidgetItem (parent, mcast_table_type_) 45 { 46 clear_address(&src_addr_); 47 clear_address(&dst_addr_); 48 src_port_ = 0; 49 dst_port_ = 0; 50 num_packets_ = 0; 51 avg_pps_ = 0; 52 avg_bw_ = 0; 53 max_bw_ = 0; 54 top_burst_size_ = 0; 55 num_bursts_ = 0; 56 top_buff_usage_ = 0; 57 num_buff_alarms_ = 0; 58 } 59 60 void updateStreamInfo(const mcast_stream_info_t *stream_info) { 61 copy_address(&src_addr_, &stream_info->src_addr); 62 src_port_ = stream_info->src_port; 63 copy_address(&dst_addr_, &stream_info->dest_addr); 64 dst_port_ = stream_info->dest_port; 65 num_packets_ = stream_info->npackets; 66 avg_pps_ = stream_info->apackets; 67 avg_bw_ = stream_info->average_bw; 68 max_bw_ = stream_info->element.maxbw; 69 top_burst_size_ = stream_info->element.topburstsize; 70 num_bursts_ = stream_info->element.numbursts; 71 top_buff_usage_ = stream_info->element.topbuffusage; 72 num_buff_alarms_ = stream_info->element.numbuffalarms; 73 74 draw(); 75 } 76 77 void draw() { 78 setText(col_src_addr_, address_to_qstring(&src_addr_)); 79 setText(col_src_port_, QString::number(src_port_)); 80 setText(col_dst_addr_, address_to_qstring(&dst_addr_)); 81 setText(col_dst_port_, QString::number(dst_port_)); 82 setText(col_packets_, QString::number(num_packets_)); 83 setText(col_packets_s_, QString::number(avg_pps_, 'f', 2)); 84 setText(col_avg_bw_, bits_s_to_qstring(avg_bw_)); 85 setText(col_max_bw_, bits_s_to_qstring(max_bw_)); 86 setText(col_max_burst_, QString("%1 / %2ms").arg(top_burst_size_).arg(mcast_stream_burstint)); 87 setText(col_burst_alarms_, QString::number(num_bursts_)); 88 setText(col_max_buffers_, bits_s_to_qstring(top_buff_usage_)); 89 setText(col_buffer_alarms_, QString::number(num_buff_alarms_)); 90 } 91 92 bool operator< (const QTreeWidgetItem &other) const 93 { 94 if (other.type() != mcast_table_type_) return QTreeWidgetItem::operator< (other); 95 const MulticastStatTreeWidgetItem *other_row = static_cast<const MulticastStatTreeWidgetItem *>(&other); 96 97 switch (treeWidget()->sortColumn()) { 98 case col_src_addr_: 99 return cmp_address(&src_addr_, &other_row->src_addr_) < 0; 100 case col_src_port_: 101 return src_port_ < other_row->src_port_; 102 case col_dst_addr_: 103 return cmp_address(&dst_addr_, &other_row->dst_addr_) < 0; 104 case col_dst_port_: 105 return dst_port_ < other_row->dst_port_; 106 case col_packets_: 107 return num_packets_ < other_row->num_packets_; 108 case col_packets_s_: 109 return avg_pps_ < other_row->avg_pps_; 110 case col_avg_bw_: 111 return avg_bw_ < other_row->avg_bw_; 112 case col_max_bw_: 113 return max_bw_ < other_row->max_bw_; 114 case col_max_burst_: 115 return top_burst_size_ < other_row->top_burst_size_; 116 case col_burst_alarms_: 117 return num_bursts_ < other_row->num_bursts_; 118 case col_max_buffers_: 119 return top_buff_usage_ < other_row->top_buff_usage_; 120 case col_buffer_alarms_: 121 return num_buff_alarms_ < other_row->num_buff_alarms_; 122 default: 123 break; 124 } 125 126 return QTreeWidgetItem::operator< (other); 127 } 128 QList<QVariant> rowData() { 129 return QList<QVariant>() 130 << address_to_qstring(&src_addr_) << src_port_ 131 << address_to_qstring(&dst_addr_) << dst_port_ 132 << num_packets_ << avg_pps_ 133 << avg_bw_ << max_bw_ 134 << top_burst_size_ << num_bursts_ 135 << top_buff_usage_ << num_buff_alarms_; 136 } 137 const QString filterExpression() { 138 QString ip_version; 139 140 if (src_addr_.type == AT_IPv6) ip_version = "v6"; 141 142 const QString filter_expr = QString("(ip%1.src==%2 && udp.srcport==%3 && ip%1.dst==%4 && udp.dstport==%5)") 143 .arg(ip_version) 144 .arg(address_to_qstring(&src_addr_)) 145 .arg(src_port_) 146 .arg(address_to_qstring(&dst_addr_)) 147 .arg(dst_port_); 148 return filter_expr; 149 } 150 151 private: 152 address src_addr_; 153 guint16 src_port_; 154 address dst_addr_; 155 guint16 dst_port_; 156 unsigned num_packets_; 157 double avg_pps_; 158 double avg_bw_; 159 double max_bw_; 160 int top_burst_size_; 161 int num_bursts_; 162 int top_buff_usage_; 163 int num_buff_alarms_; 164 }; 165 166 MulticastStatisticsDialog::MulticastStatisticsDialog(QWidget &parent, CaptureFile &cf, const char *filter) : 167 TapParameterDialog(parent, cf) 168 { 169 setWindowSubtitle(tr("UDP Multicast Streams")); 170 loadGeometry(parent.width() * 4 / 5, parent.height() * 3 / 4, "MulticastStatisticsDialog"); 171 172 tapinfo_ = new mcaststream_tapinfo_t(); 173 tapinfo_->user_data = this; 174 tapinfo_->tap_reset = tapReset; 175 tapinfo_->tap_draw = tapDraw; 176 177 QStringList header_names = QStringList() 178 << tr("Source Address") << tr("Source Port") 179 << tr("Destination Address") << tr("Destination Port") 180 << tr("Packets") << tr("Packets/s") 181 << tr("Avg BW (bps)") << tr("Max BW (bps)") 182 << tr("Max Burst") << tr("Burst Alarms") 183 << tr("Max Buffers (B)") << tr("Buffer Alarms"); 184 185 statsTreeWidget()->setHeaderLabels(header_names); 186 187 for (int col = 0; col < statsTreeWidget()->columnCount(); col++) { 188 if (col == col_src_addr_ || col == col_dst_addr_) continue; 189 statsTreeWidget()->headerItem()->setTextAlignment(col, Qt::AlignRight); 190 } 191 192 burst_measurement_interval_le_ = new SyntaxLineEdit(this); 193 burst_alarm_threshold_le_ = new SyntaxLineEdit(this); 194 buffer_alarm_threshold_le_ = new SyntaxLineEdit(this); 195 stream_empty_speed_le_ = new SyntaxLineEdit(this); 196 total_empty_speed_le_ = new SyntaxLineEdit(this); 197 198 int filter_layout_idx = verticalLayout()->indexOf(filterLayout()->widget()); 199 QGridLayout *param_grid = new QGridLayout(); 200 int one_em = fontMetrics().height(); 201 verticalLayout()->insertLayout(filter_layout_idx, param_grid); 202 203 // Label | LineEdit | | Label | LineEdit | | Label | LineEdit 204 // 0 1 2 3 4 5 6 7 205 param_grid->setColumnMinimumWidth(2, one_em * 2); 206 param_grid->setColumnStretch(2, 1); 207 param_grid->setColumnMinimumWidth(5, one_em * 2); 208 param_grid->setColumnStretch(5, 1); 209 param_grid->addWidget(new QLabel(tr("Burst measurement interval (ms):")), 0, 0, Qt::AlignRight); 210 param_grid->addWidget(burst_measurement_interval_le_, 0, 1); 211 param_grid->addWidget(new QLabel(tr("Burst alarm threshold (packets):")), 0, 3, Qt::AlignRight); 212 param_grid->addWidget(burst_alarm_threshold_le_, 0, 4); 213 param_grid->addWidget(new QLabel(tr("Buffer alarm threshold (B):")), 0, 6, Qt::AlignRight); 214 param_grid->addWidget(buffer_alarm_threshold_le_, 0, 7); 215 216 param_grid->addWidget(new QLabel(tr("Stream empty speed (Kb/s):")), 1, 0, Qt::AlignRight); 217 param_grid->addWidget(stream_empty_speed_le_, 1, 1); 218 param_grid->addWidget(new QLabel(tr("Total empty speed (Kb/s):")), 1, 3, Qt::AlignRight); 219 param_grid->addWidget(total_empty_speed_le_, 1, 4); 220 221 burst_measurement_interval_le_->setText(QString::number(mcast_stream_burstint)); 222 burst_alarm_threshold_le_->setText(QString::number(mcast_stream_trigger)); 223 buffer_alarm_threshold_le_->setText(QString::number(mcast_stream_bufferalarm)); 224 stream_empty_speed_le_->setText(QString::number(mcast_stream_emptyspeed)); 225 total_empty_speed_le_->setText(QString::number(mcast_stream_cumulemptyspeed)); 226 227 line_edits_ = QList<QWidget *>() 228 << burst_measurement_interval_le_ << burst_alarm_threshold_le_ 229 << buffer_alarm_threshold_le_ << stream_empty_speed_le_ 230 << total_empty_speed_le_; 231 232 foreach (QWidget *line_edit, line_edits_) { 233 line_edit->setMinimumWidth(one_em * 5); 234 connect(line_edit, SIGNAL(textEdited(QString)), this, SLOT(updateWidgets())); 235 } 236 237 addFilterActions(); 238 239 if (filter) { 240 setDisplayFilter(filter); 241 } 242 243 connect(this, SIGNAL(updateFilter(QString)), 244 this, SLOT(updateMulticastParameters())); 245 246 /* Register the tap listener */ 247 register_tap_listener_mcast_stream(tapinfo_); 248 249 updateWidgets(); 250 } 251 252 MulticastStatisticsDialog::~MulticastStatisticsDialog() 253 { 254 /* Remove the stream tap listener */ 255 remove_tap_listener_mcast_stream(tapinfo_); 256 257 /* Clean up memory used by stream tap */ 258 mcaststream_reset(tapinfo_); 259 260 delete tapinfo_; 261 } 262 263 void MulticastStatisticsDialog::tapReset(mcaststream_tapinfo_t *tapinfo) 264 { 265 MulticastStatisticsDialog *ms_dlg = dynamic_cast<MulticastStatisticsDialog *>((MulticastStatisticsDialog*)tapinfo->user_data); 266 if (!ms_dlg || !ms_dlg->statsTreeWidget()) return; 267 268 ms_dlg->statsTreeWidget()->clear(); 269 } 270 271 void MulticastStatisticsDialog::tapDraw(mcaststream_tapinfo_t *tapinfo) 272 { 273 MulticastStatisticsDialog *ms_dlg = dynamic_cast<MulticastStatisticsDialog *>((MulticastStatisticsDialog*)tapinfo->user_data); 274 if (!ms_dlg || !ms_dlg->statsTreeWidget()) return; 275 276 //Clear the tree because the list always starts from the beginning 277 ms_dlg->statsTreeWidget()->clear(); 278 279 // Add missing rows and update stats 280 int cur_row = 0; 281 for (GList *cur = g_list_first(tapinfo->strinfo_list); cur; cur = gxx_list_next(cur)) { 282 mcast_stream_info_t *stream_info = gxx_list_data(mcast_stream_info_t *, cur); 283 if (!stream_info) continue; 284 285 MulticastStatTreeWidgetItem *ms_ti; 286 QTreeWidgetItem *ti = ms_dlg->statsTreeWidget()->topLevelItem(cur_row); 287 if (!ti) { 288 ms_ti = new MulticastStatTreeWidgetItem(ms_dlg->statsTreeWidget()); 289 for (int col = 0; col < ms_dlg->statsTreeWidget()->columnCount(); col++) { 290 if (col == col_src_addr_ || col == col_dst_addr_) continue; 291 ms_ti->setTextAlignment(col, Qt::AlignRight); 292 } 293 } else { 294 ms_ti = static_cast<MulticastStatTreeWidgetItem *>(ti); 295 } 296 297 ms_ti->updateStreamInfo(stream_info); 298 cur_row++; 299 } 300 } 301 302 QList<QVariant> MulticastStatisticsDialog::treeItemData(QTreeWidgetItem *ti) const 303 { 304 MulticastStatTreeWidgetItem *ms_ti = dynamic_cast<MulticastStatTreeWidgetItem*>(ti); 305 if (ms_ti) { 306 return ms_ti->rowData(); 307 } 308 else { 309 return QList<QVariant>(); 310 } 311 } 312 313 const QString MulticastStatisticsDialog::filterExpression() 314 { 315 QString filter_expr; 316 if (statsTreeWidget()->selectedItems().count() > 0) { 317 QTreeWidgetItem *ti = statsTreeWidget()->selectedItems()[0]; 318 319 MulticastStatTreeWidgetItem *ms_ti = static_cast<MulticastStatTreeWidgetItem *>(ti); 320 filter_expr = ms_ti->filterExpression(); 321 } 322 return filter_expr; 323 } 324 325 void MulticastStatisticsDialog::updateWidgets() 326 { 327 QString hint; 328 bool enable_apply = true; 329 bool enable_edits = cap_file_.isValid(); 330 bool ok = false; 331 int param; 332 333 param = burst_measurement_interval_le_->text().toUInt(&ok); 334 if (!ok || param < 1 || param > 1000) { 335 hint += tr("The burst interval must be between 1 and 1000. "); 336 enable_apply = false; 337 burst_measurement_interval_le_->setSyntaxState(SyntaxLineEdit::Invalid); 338 } else { 339 burst_measurement_interval_le_->setSyntaxState(SyntaxLineEdit::Valid); 340 } 341 342 param = burst_alarm_threshold_le_->text().toInt(&ok); 343 if (!ok || param < 1) { 344 hint += tr("The burst alarm threshold isn't valid. "); 345 enable_apply = false; 346 burst_alarm_threshold_le_->setSyntaxState(SyntaxLineEdit::Invalid); 347 } else { 348 burst_alarm_threshold_le_->setSyntaxState(SyntaxLineEdit::Valid); 349 } 350 351 param = buffer_alarm_threshold_le_->text().toInt(&ok); 352 if (!ok || param < 1) { 353 hint += tr("The buffer alarm threshold isn't valid. "); 354 enable_apply = false; 355 buffer_alarm_threshold_le_->setSyntaxState(SyntaxLineEdit::Invalid); 356 } else { 357 buffer_alarm_threshold_le_->setSyntaxState(SyntaxLineEdit::Valid); 358 } 359 360 param = stream_empty_speed_le_->text().toInt(&ok); 361 if (!ok || param < 1 || param > 10000000) { 362 hint += tr("The stream empty speed should be between 1 and 10000000. "); 363 enable_apply = false; 364 stream_empty_speed_le_->setSyntaxState(SyntaxLineEdit::Invalid); 365 } else { 366 stream_empty_speed_le_->setSyntaxState(SyntaxLineEdit::Valid); 367 } 368 369 param = total_empty_speed_le_->text().toInt(&ok); 370 if (!ok || param < 1 || param > 10000000) { 371 hint += tr("The total empty speed should be between 1 and 10000000. "); 372 enable_apply = false; 373 total_empty_speed_le_->setSyntaxState(SyntaxLineEdit::Invalid); 374 } else { 375 total_empty_speed_le_->setSyntaxState(SyntaxLineEdit::Valid); 376 } 377 378 foreach (QWidget *line_edit, line_edits_) { 379 line_edit->setEnabled(enable_edits); 380 } 381 382 applyFilterButton()->setEnabled(enable_apply); 383 384 if (hint.isEmpty() && tapinfo_->allstreams) { 385 const QString stats = tr("%1 streams, avg bw: %2bps, max bw: %3bps, max burst: %4 / %5ms, max buffer: %6B") 386 .arg(statsTreeWidget()->topLevelItemCount()) 387 .arg(bits_s_to_qstring(tapinfo_->allstreams->average_bw)) 388 .arg(bits_s_to_qstring(tapinfo_->allstreams->element.maxbw)) 389 .arg(tapinfo_->allstreams->element.topburstsize) 390 .arg(mcast_stream_burstint) 391 .arg(bits_s_to_qstring(tapinfo_->allstreams->element.topbuffusage)); 392 hint.append(stats); 393 } 394 hint.prepend("<small><i>"); 395 hint.append("</i></small>"); 396 setHint(hint); 397 TapParameterDialog::updateWidgets(); 398 } 399 400 void MulticastStatisticsDialog::updateMulticastParameters() 401 { 402 bool ok = false; 403 int param; 404 405 param = burst_measurement_interval_le_->text().toUInt(&ok); 406 if (ok && param > 0 && param <= 1000) { 407 mcast_stream_burstint = (guint16) param; 408 } 409 410 param = burst_alarm_threshold_le_->text().toInt(&ok); 411 if (ok) { 412 mcast_stream_trigger = param; 413 } 414 415 param = buffer_alarm_threshold_le_->text().toInt(&ok); 416 if (ok && param > 0) { 417 mcast_stream_bufferalarm = param; 418 } 419 420 param = stream_empty_speed_le_->text().toInt(&ok); 421 if (ok && param > 0 && param <= 10000000) { 422 mcast_stream_emptyspeed = param; 423 } 424 425 param = total_empty_speed_le_->text().toInt(&ok); 426 if (ok && param > 0 && param <= 10000000) { 427 mcast_stream_cumulemptyspeed = param; 428 } 429 } 430 431 void MulticastStatisticsDialog::fillTree() 432 { 433 QList<QWidget *> disable_widgets = QList<QWidget *>() 434 << line_edits_ << displayFilterLineEdit() << applyFilterButton(); 435 436 foreach (QWidget *w, disable_widgets) w->setEnabled(false); 437 438 /* Scan for Mcast streams (redissect all packets) */ 439 mcaststream_scan(tapinfo_, cap_file_.capFile()); 440 tapDraw(tapinfo_); 441 442 foreach (QWidget *w, disable_widgets) w->setEnabled(true); 443 for (int col = 0; col < statsTreeWidget()->columnCount() - 1; col++) { 444 statsTreeWidget()->resizeColumnToContents(col); 445 } 446 updateWidgets(); 447 } 448 449 void MulticastStatisticsDialog::captureFileClosing() 450 { 451 /* Remove the stream tap listener */ 452 remove_tap_listener_mcast_stream(tapinfo_); 453 454 WiresharkDialog::captureFileClosing(); 455 } 456 457 // Stat command + args 458 459 static void 460 multicast_statistics_init(const char *args, void*) { 461 QStringList args_l = QString(args).split(','); 462 QByteArray filter; 463 if (args_l.length() > 2) { 464 filter = QStringList(args_l.mid(2)).join(",").toUtf8(); 465 } 466 wsApp->emitStatCommandSignal("MulticastStatistics", filter.constData(), NULL); 467 } 468 469 static stat_tap_ui multicast_statistics_ui = { 470 REGISTER_STAT_GROUP_GENERIC, 471 NULL, 472 "multicast,stat", 473 multicast_statistics_init, 474 0, 475 NULL 476 }; 477 478 extern "C" { 479 480 void register_tap_listener_qt_multicast_statistics(void); 481 482 void 483 register_tap_listener_qt_multicast_statistics(void) 484 { 485 register_stat_tap_ui(&multicast_statistics_ui, NULL); 486 } 487 488 } 489