1 /* protocol_hierarchy_dialog.cpp
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later
10 #include "protocol_hierarchy_dialog.h"
11 #include <ui_protocol_hierarchy_dialog.h>
15 #include "ui/proto_hier_stats.h"
17 #include <ui/qt/utils/variant_pointer.h>
19 #include <wsutil/utf8_entities.h>
21 #include <ui/qt/utils/qt_ui_utils.h>
22 #include "main_application.h"
24 #include <epan/proto.h>
25 #include <epan/disabled_protos.h>
28 #include <QPushButton>
29 #include <QTextStream>
30 #include <QTreeWidgetItemIterator>
33 * @file Protocol Hierarchy Statistics dialog
35 * Displays tree of protocols with various statistics
36 * Allows filtering on tree items
40 // - Make "Copy as YAML" output a tree?
41 // - Add time series data to ph_stats_node_t and draw sparklines.
43 const int protocol_col_
= 0;
44 const int pct_packets_col_
= 1;
45 const int packets_col_
= 2;
46 const int pct_bytes_col_
= 3;
47 const int bytes_col_
= 4;
48 const int bandwidth_col_
= 5;
49 const int end_packets_col_
= 6;
50 const int end_bytes_col_
= 7;
51 const int end_bandwidth_col_
= 8;
52 const int pdus_col_
= 9;
54 struct addTreeNodeData
{
55 QSet
<QString
> *protos
;
56 QTreeWidgetItem
*widget
;
59 class ProtocolHierarchyTreeWidgetItem
: public QTreeWidgetItem
62 ProtocolHierarchyTreeWidgetItem(QTreeWidgetItem
*parent
, ph_stats_node_t
& ph_stats_node
) :
63 QTreeWidgetItem(parent
),
64 total_packets_(ph_stats_node
.num_pkts_total
),
65 total_pdus_(ph_stats_node
.num_pdus_total
),
66 last_packets_(ph_stats_node
.num_pkts_last
),
67 total_bytes_(ph_stats_node
.num_bytes_total
),
68 last_bytes_(ph_stats_node
.num_bytes_last
),
74 filter_name_
= ph_stats_node
.hfinfo
->abbrev
;
77 ph_stats_t
*ph_stats
= VariantPointer
<ph_stats_t
>::asPtr(parent
->treeWidget()->invisibleRootItem()->data(0, Qt::UserRole
));
79 if (!ph_stats
|| ph_stats
->tot_packets
< 1) return;
80 percent_packets_
= total_packets_
* 100.0 / ph_stats
->tot_packets
;
81 percent_bytes_
= total_bytes_
* 100.0 / ph_stats
->tot_bytes
;
83 double seconds
= ph_stats
->last_time
- ph_stats
->first_time
;
86 bits_s_
= total_bytes_
* 8.0 / seconds
;
87 end_bits_s_
= last_bytes_
* 8.0 / seconds
;
90 setText(protocol_col_
, ph_stats_node
.hfinfo
->name
);
91 setToolTip(protocol_col_
, QStringLiteral("%1").arg(ph_stats_node
.hfinfo
->abbrev
));
92 setData(pct_packets_col_
, Qt::UserRole
, percent_packets_
);
93 setText(packets_col_
, QString::number(total_packets_
));
94 setData(pct_bytes_col_
, Qt::UserRole
, percent_bytes_
);
95 setText(bytes_col_
, QString::number(total_bytes_
));
96 setText(bandwidth_col_
, seconds
> 0.0 ? bits_s_to_qstring(bits_s_
) : UTF8_EM_DASH
);
97 setText(end_packets_col_
, QString::number(last_packets_
));
98 setText(end_bytes_col_
, QString::number(last_bytes_
));
99 setText(end_bandwidth_col_
, seconds
> 0.0 ? bits_s_to_qstring(end_bits_s_
) : UTF8_EM_DASH
);
100 setText(pdus_col_
, QString::number(total_pdus_
));
103 // Return a QString, int, double, or invalid QVariant representing the raw column data.
104 QVariant
colData(int col
) const {
108 case (pct_packets_col_
):
109 return percent_packets_
;
111 return total_packets_
;
112 case (pct_bytes_col_
):
113 return percent_bytes_
;
116 case (bandwidth_col_
):
118 case (end_packets_col_
):
119 return last_packets_
;
120 case (end_bytes_col_
):
122 case (end_bandwidth_col_
):
132 bool operator< (const QTreeWidgetItem
&other
) const
134 const ProtocolHierarchyTreeWidgetItem
&other_phtwi
= dynamic_cast<const ProtocolHierarchyTreeWidgetItem
&>(other
);
136 switch (treeWidget()->sortColumn()) {
137 case pct_packets_col_
:
138 return percent_packets_
< other_phtwi
.percent_packets_
;
140 return total_packets_
< other_phtwi
.total_packets_
;
142 return percent_packets_
< other_phtwi
.percent_packets_
;
144 return total_bytes_
< other_phtwi
.total_bytes_
;
146 return bits_s_
< other_phtwi
.bits_s_
;
147 case end_packets_col_
:
148 return last_packets_
< other_phtwi
.last_packets_
;
150 return last_bytes_
< other_phtwi
.last_bytes_
;
151 case end_bandwidth_col_
:
152 return end_bits_s_
< other_phtwi
.end_bits_s_
;
154 return total_pdus_
< other_phtwi
.total_pdus_
;
159 // Fall back to string comparison
160 return QTreeWidgetItem::operator <(other
);
163 const QString
filterName(void) { return filter_name_
; }
166 QString filter_name_
;
167 unsigned total_packets_
;
168 unsigned total_pdus_
;
169 unsigned last_packets_
;
170 unsigned total_bytes_
;
171 unsigned last_bytes_
;
173 double percent_packets_
;
174 double percent_bytes_
;
179 ProtocolHierarchyDialog::ProtocolHierarchyDialog(QWidget
&parent
, CaptureFile
&cf
) :
180 WiresharkDialog(parent
, cf
),
181 ui(new Ui::ProtocolHierarchyDialog
)
184 loadGeometry(parent
.width() * 4 / 5, parent
.height() * 4 / 5);
185 setWindowSubtitle(tr("Protocol Hierarchy Statistics"));
187 ui
->hierStatsTreeWidget
->setItemDelegateForColumn(pct_packets_col_
, &percent_bar_delegate_
);
188 ui
->hierStatsTreeWidget
->setItemDelegateForColumn(pct_bytes_col_
, &percent_bar_delegate_
);
189 ph_stats_t
*ph_stats
= ph_stats_new(cap_file_
.capFile());
191 ui
->hierStatsTreeWidget
->invisibleRootItem()->setData(0, Qt::UserRole
, VariantPointer
<ph_stats_t
>::asQVariant(ph_stats
));
192 addTreeNodeData atnd
{ &used_protos_
, ui
->hierStatsTreeWidget
->invisibleRootItem() };
193 g_node_children_foreach(ph_stats
->stats_tree
, G_TRAVERSE_ALL
, addTreeNode
, (void *)&atnd
);
194 ph_stats_free(ph_stats
);
197 ui
->hierStatsTreeWidget
->setContextMenuPolicy(Qt::CustomContextMenu
);
198 connect(ui
->hierStatsTreeWidget
, SIGNAL(customContextMenuRequested(QPoint
)),
199 SLOT(showProtoHierMenu(QPoint
)));
201 ui
->hierStatsTreeWidget
->setSortingEnabled(true);
202 ui
->hierStatsTreeWidget
->expandAll();
204 for (int i
= 0; i
< ui
->hierStatsTreeWidget
->columnCount(); i
++) {
205 ui
->hierStatsTreeWidget
->resizeColumnToContents(i
);
210 FilterAction::Action cur_action
= FilterAction::ActionApply
;
211 submenu
= ctx_menu_
.addMenu(FilterAction::actionName(cur_action
));
212 foreach (FilterAction::ActionType at
, FilterAction::actionTypes()) {
213 FilterAction
*fa
= new FilterAction(submenu
, cur_action
, at
);
214 submenu
->addAction(fa
);
215 connect(fa
, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
218 cur_action
= FilterAction::ActionPrepare
;
219 submenu
= ctx_menu_
.addMenu(FilterAction::actionName(cur_action
));
220 foreach (FilterAction::ActionType at
, FilterAction::actionTypes()) {
221 FilterAction
*fa
= new FilterAction(submenu
, cur_action
, at
);
222 submenu
->addAction(fa
);
223 connect(fa
, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
226 FilterAction
*fa
= new FilterAction(&ctx_menu_
, FilterAction::ActionFind
);
227 ctx_menu_
.addAction(fa
);
228 connect(fa
, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
230 fa
= new FilterAction(&ctx_menu_
, FilterAction::ActionColorize
);
231 ctx_menu_
.addAction(fa
);
232 connect(fa
, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
234 ctx_menu_
.addSeparator();
235 ctx_menu_
.addAction(ui
->actionCopyAsCsv
);
236 ctx_menu_
.addAction(ui
->actionCopyAsYaml
);
238 QPushButton
*copy_button
= ui
->buttonBox
->addButton(tr("Copy"), QDialogButtonBox::ApplyRole
);
240 QMenu
*copy_menu
= new QMenu(copy_button
);
242 ca
= copy_menu
->addAction(tr("as CSV"));
243 ca
->setToolTip(ui
->actionCopyAsCsv
->toolTip());
244 connect(ca
, &QAction::triggered
, this, &ProtocolHierarchyDialog::on_actionCopyAsCsv_triggered
);
245 ca
= copy_menu
->addAction(tr("as YAML"));
246 ca
->setToolTip(ui
->actionCopyAsYaml
->toolTip());
247 connect(ca
, &QAction::triggered
, this, &ProtocolHierarchyDialog::on_actionCopyAsYaml_triggered
);
248 copy_button
->setMenu(copy_menu
);
249 connect(ca
, SIGNAL(triggered()), this, SLOT(on_actionCopyAsYaml_triggered()));
250 ca
= copy_menu
->addAction(tr("protocol short names"));
251 ca
->setToolTip(ui
->actionCopyProtoList
->toolTip());
252 connect(ca
, SIGNAL(triggered()), this, SLOT(on_actionCopyProtoList_triggered()));
253 copy_button
->setMenu(copy_menu
);
255 QPushButton
*protos_button
= ui
->buttonBox
->addButton(tr("Protocols"), QDialogButtonBox::ApplyRole
);
256 QMenu
*protos_menu
= new QMenu(protos_button
);
257 proto_disable_
= protos_menu
->addAction(tr("Disable unused"));
258 proto_disable_
->setToolTip(ui
->actionDisableProtos
->toolTip());
259 connect(proto_disable_
, SIGNAL(triggered()), this, SLOT(on_actionDisableProtos_triggered()));
260 proto_revert_
= protos_menu
->addAction(tr("Revert changes"));
261 proto_revert_
->setToolTip(ui
->actionRevertProtos
->toolTip());
262 connect(proto_revert_
, SIGNAL(triggered()), this, SLOT(on_actionRevertProtos_triggered()));
263 protos_button
->setMenu(protos_menu
);
265 QPushButton
*close_bt
= ui
->buttonBox
->button(QDialogButtonBox::Close
);
267 close_bt
->setDefault(true);
270 display_filter_
= cap_file_
.capFile()->dfilter
;
274 ProtocolHierarchyDialog::~ProtocolHierarchyDialog()
279 void ProtocolHierarchyDialog::showProtoHierMenu(QPoint pos
)
281 bool enable
= ui
->hierStatsTreeWidget
->currentItem() != NULL
&& !file_closed_
? true : false;
283 foreach (QMenu
*submenu
, ctx_menu_
.findChildren
<QMenu
*>()) {
284 submenu
->setEnabled(enable
);
286 foreach (QAction
*action
, ctx_menu_
.actions()) {
287 if (action
!= ui
->actionCopyAsCsv
&& action
!= ui
->actionCopyAsYaml
) {
288 action
->setEnabled(enable
);
292 ctx_menu_
.popup(ui
->hierStatsTreeWidget
->viewport()->mapToGlobal(pos
));
295 void ProtocolHierarchyDialog::filterActionTriggered()
297 ProtocolHierarchyTreeWidgetItem
*phti
= static_cast<ProtocolHierarchyTreeWidgetItem
*>(ui
->hierStatsTreeWidget
->currentItem());
298 FilterAction
*fa
= qobject_cast
<FilterAction
*>(QObject::sender());
303 QString
filter_name(phti
->filterName());
305 emit
filterAction(filter_name
, fa
->action(), fa
->actionType());
308 void ProtocolHierarchyDialog::addTreeNode(GNode
*node
, void *data
)
310 ph_stats_node_t
*stats
= (ph_stats_node_t
*)node
->data
;
313 addTreeNodeData
*atndp
= (addTreeNodeData
*)data
;
314 QTreeWidgetItem
*parent_ti
= atndp
->widget
;
315 if (!parent_ti
) return;
317 atndp
->protos
->insert(QString(stats
->hfinfo
->abbrev
));
319 ProtocolHierarchyTreeWidgetItem
*phti
= new ProtocolHierarchyTreeWidgetItem(parent_ti
, *stats
);
320 addTreeNodeData atnd
{ atndp
->protos
, phti
};
322 g_node_children_foreach(node
, G_TRAVERSE_ALL
, addTreeNode
, (void *)&atnd
);
326 void ProtocolHierarchyDialog::updateWidgets()
328 QString hint
= "<small><i>";
329 if (display_filter_
.isEmpty()) {
330 hint
+= tr("No display filter.");
332 hint
+= tr("Display filter: %1").arg(display_filter_
);
334 hint
+= "</i></small>";
335 ui
->hintLabel
->setText(hint
);
337 proto_revert_
->setEnabled(enabled_protos_unsaved_changes());
339 WiresharkDialog::updateWidgets();
342 QList
<QVariant
> ProtocolHierarchyDialog::protoHierRowData(QTreeWidgetItem
*item
) const
344 QList
<QVariant
> row_data
;
346 for (int col
= 0; col
< ui
->hierStatsTreeWidget
->columnCount(); col
++) {
348 row_data
<< ui
->hierStatsTreeWidget
->headerItem()->text(col
);
350 ProtocolHierarchyTreeWidgetItem
*phti
= static_cast<ProtocolHierarchyTreeWidgetItem
*>(item
);
352 row_data
<< phti
->colData(col
);
359 void ProtocolHierarchyDialog::on_actionCopyAsCsv_triggered()
362 QTextStream
stream(&csv
, QIODevice::Text
);
363 QTreeWidgetItemIterator
iter(ui
->hierStatsTreeWidget
);
367 QStringList separated_value
;
368 QTreeWidgetItem
*item
= first
? NULL
: (*iter
);
370 foreach (QVariant v
, protoHierRowData(item
)) {
372 separated_value
<< "\"\"";
373 } else if (v
.userType() == QMetaType::QString
) {
374 separated_value
<< QStringLiteral("\"%1\"").arg(v
.toString());
376 separated_value
<< v
.toString();
379 stream
<< separated_value
.join(",") << '\n';
384 mainApp
->clipboard()->setText(stream
.readAll());
387 void ProtocolHierarchyDialog::on_actionCopyAsYaml_triggered()
390 QTextStream
stream(&yaml
, QIODevice::Text
);
391 QTreeWidgetItemIterator
iter(ui
->hierStatsTreeWidget
);
394 stream
<< "---" << '\n';
396 QTreeWidgetItem
*item
= first
? NULL
: (*iter
);
398 stream
<< "-" << '\n';
399 foreach (QVariant v
, protoHierRowData(item
)) {
400 stream
<< " - " << v
.toString() << '\n';
405 mainApp
->clipboard()->setText(stream
.readAll());
408 void ProtocolHierarchyDialog::on_actionCopyProtoList_triggered()
411 QTextStream
stream(&plist
, QIODevice::Text
);
413 QSetIterator
<QString
> iter(used_protos_
);
414 while (iter
.hasNext()) {
415 if (!first
) stream
<< ',';
416 stream
<< iter
.next();
419 mainApp
->clipboard()->setText(stream
.readAll());
422 void ProtocolHierarchyDialog::on_actionDisableProtos_triggered()
426 QSetIterator
<QString
> iter(used_protos_
);
427 while (iter
.hasNext()) {
428 proto_enable_proto_by_name(iter
.next().toStdString().c_str());
430 /* Note that we aren't saving the changes here; they only apply
431 * to the current dissection.
432 * (Though if the user goes to the Enabled Protocols dialog and
433 * makes changes, these changes as well as the user's will be saved.)
435 proto_revert_
->setEnabled(enabled_protos_unsaved_changes());
437 QString hint
= "<small><i>"
438 + tr("Unused protocols have been disabled.")
440 ui
->hintLabel
->setText(hint
);
442 // If we've done everything right, nothing should change.
443 //wsApp->emitAppSignal(WiresharkApplication::PacketDissectionChanged);
446 void ProtocolHierarchyDialog::on_actionRevertProtos_triggered()
448 proto_reenable_all();
449 read_enabled_and_disabled_lists();
451 proto_revert_
->setEnabled(enabled_protos_unsaved_changes());
452 QString hint
= "<small><i>"
453 + tr("Protocol changes have been reverted.")
455 ui
->hintLabel
->setText(hint
);
457 // If we've done everything right, nothing should change.
458 //wsApp->emitAppSignal(WiresharkApplication::PacketDissectionChanged);
461 void ProtocolHierarchyDialog::on_buttonBox_helpRequested()
463 mainApp
->helpTopicAction(HELP_STATS_PROTO_HIERARCHY_DIALOG
);