1 /* io_graph_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 "io_graph_dialog.h"
11 #include <ui_io_graph_dialog.h>
12
13 #include "file.h"
14
15 #include <epan/stat_tap_ui.h>
16 #include "epan/stats_tree_priv.h"
17 #include "epan/uat-int.h"
18
19 #include <wsutil/utf8_entities.h>
20 #include <wsutil/ws_assert.h>
21
22 #include <ui/qt/utils/qt_ui_utils.h>
23
24 #include <ui/qt/utils/variant_pointer.h>
25
26 #include <ui/qt/utils/color_utils.h>
27 #include <ui/qt/widgets/qcustomplot.h>
28 #include "progress_frame.h"
29 #include "wireshark_application.h"
30 #include <wsutil/report_message.h>
31
32 #include <ui/qt/utils/tango_colors.h> //provides some default colors
33 #include <ui/qt/widgets/copy_from_profile_button.h>
34 #include "ui/qt/widgets/wireshark_file_dialog.h"
35
36 #include <QClipboard>
37 #include <QFontMetrics>
38 #include <QFrame>
39 #include <QHBoxLayout>
40 #include <QLineEdit>
41 #include <QMessageBox>
42 #include <QPushButton>
43 #include <QRubberBand>
44 #include <QSpacerItem>
45 #include <QTimer>
46 #include <QVariant>
47
48 // Bugs and uncertainties:
49 // - Regular (non-stacked) bar graphs are drawn on top of each other on the Z axis.
50 // The QCP forum suggests drawing them side by side:
51 // https://www.qcustomplot.com/index.php/support/forum/62
52 // - We retap and redraw more than we should.
53 // - Smoothing doesn't seem to match GTK+
54 // - Closing the color picker on macOS sends the dialog to the background.
55 // - The color picker triggers https://bugreports.qt.io/browse/QTBUG-58699.
56
57 // To do:
58 // - Use scroll bars?
59 // - Scroll during live captures
60 // - Set ticks per pixel (e.g. pressing "2" sets 2 tpp).
61 // - Explicitly handle missing values, e.g. via NAN.
62 // - Add a "show missing" or "show zero" option to the UAT?
63 // It would add yet another graph configuration column.
64
65 const qreal graph_line_width_ = 1.0;
66
67 const int DEFAULT_MOVING_AVERAGE = 0;
68 const int DEFAULT_Y_AXIS_FACTOR = 1;
69
70 // Don't accidentally zoom into a 1x1 rect if you happen to click on the graph
71 // in zoom mode.
72 const int min_zoom_pixels_ = 20;
73
74 const int stat_update_interval_ = 200; // ms
75
76 // Saved graph settings
77 typedef struct _io_graph_settings_t {
78 gboolean enabled;
79 char* name;
80 char* dfilter;
81 guint color;
82 guint32 style;
83 guint32 yaxis;
84 char* yfield;
85 guint32 sma_period;
86 guint32 y_axis_factor;
87 } io_graph_settings_t;
88
89 static const value_string graph_style_vs[] = {
90 { IOGraph::psLine, "Line" },
91 { IOGraph::psImpulse, "Impulse" },
92 { IOGraph::psBar, "Bar" },
93 { IOGraph::psStackedBar, "Stacked Bar" },
94 { IOGraph::psDot, "Dot" },
95 { IOGraph::psSquare, "Square" },
96 { IOGraph::psDiamond, "Diamond" },
97 { IOGraph::psCross, "Cross" },
98 { IOGraph::psCircle, "Circle" },
99 { IOGraph::psPlus, "Plus" },
100 { 0, NULL }
101 };
102
103 static const value_string y_axis_vs[] = {
104 { IOG_ITEM_UNIT_PACKETS, "Packets" },
105 { IOG_ITEM_UNIT_BYTES, "Bytes" },
106 { IOG_ITEM_UNIT_BITS, "Bits" },
107 { IOG_ITEM_UNIT_CALC_SUM, "SUM(Y Field)" },
108 { IOG_ITEM_UNIT_CALC_FRAMES, "COUNT FRAMES(Y Field)" },
109 { IOG_ITEM_UNIT_CALC_FIELDS, "COUNT FIELDS(Y Field)" },
110 { IOG_ITEM_UNIT_CALC_MAX, "MAX(Y Field)" },
111 { IOG_ITEM_UNIT_CALC_MIN, "MIN(Y Field)" },
112 { IOG_ITEM_UNIT_CALC_AVERAGE, "AVG(Y Field)" },
113 { IOG_ITEM_UNIT_CALC_LOAD, "LOAD(Y Field)" },
114 { 0, NULL }
115 };
116
117 static const value_string moving_avg_vs[] = {
118 { 0, "None" },
119 { 10, "10 interval SMA" },
120 { 20, "20 interval SMA" },
121 { 50, "50 interval SMA" },
122 { 100, "100 interval SMA" },
123 { 200, "200 interval SMA" },
124 { 500, "500 interval SMA" },
125 { 1000, "1000 interval SMA" },
126 { 0, NULL }
127 };
128
129 static io_graph_settings_t *iog_settings_ = NULL;
130 static guint num_io_graphs_ = 0;
131 static uat_t *iog_uat_ = NULL;
132
133 // y_axis_factor was added in 3.6. Provide backward compatibility.
134 static const char *iog_uat_defaults_[] = {
135 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "1"
136 };
137
138 extern "C" {
139
140 //Allow the enable/disable field to be a checkbox, but for backwards compatibility,
141 //the strings have to be "Enabled"/"Disabled", not "TRUE"/"FALSE"
142 #define UAT_BOOL_ENABLE_CB_DEF(basename,field_name,rec_t) \
143 static void basename ## _ ## field_name ## _set_cb(void* rec, const char* buf, guint len, const void* UNUSED_PARAMETER(u1), const void* UNUSED_PARAMETER(u2)) {\
144 char* tmp_str = g_strndup(buf,len); \
145 if ((g_strcmp0(tmp_str, "Enabled") == 0) || \
146 (g_strcmp0(tmp_str, "TRUE") == 0)) \
147 ((rec_t*)rec)->field_name = 1; \
148 else \
149 ((rec_t*)rec)->field_name = 0; \
150 g_free(tmp_str); } \
151 static void basename ## _ ## field_name ## _tostr_cb(void* rec, char** out_ptr, unsigned* out_len, const void* UNUSED_PARAMETER(u1), const void* UNUSED_PARAMETER(u2)) {\
152 *out_ptr = g_strdup_printf("%s",((rec_t*)rec)->field_name ? "Enabled" : "Disabled"); \
153 *out_len = (unsigned)strlen(*out_ptr); }
154
uat_fld_chk_enable(void * u1 _U_,const char * strptr,guint len,const void * u2 _U_,const void * u3 _U_,char ** err)155 static gboolean uat_fld_chk_enable(void* u1 _U_, const char* strptr, guint len, const void* u2 _U_, const void* u3 _U_, char** err)
156 {
157 char* str = g_strndup(strptr,len);
158
159 if ((g_strcmp0(str, "Enabled") == 0) ||
160 (g_strcmp0(str, "Disabled") == 0) ||
161 (g_strcmp0(str, "TRUE") == 0) || //just for UAT functionality
162 (g_strcmp0(str, "FALSE") == 0)) {
163 *err = NULL;
164 g_free(str);
165 return TRUE;
166 }
167
168 //User should never see this unless they are manually modifying UAT
169 *err = g_strdup_printf("invalid value: %s (must be Enabled or Disabled)", str);
170 g_free(str);
171 return FALSE;
172 }
173
174 #define UAT_FLD_BOOL_ENABLE(basename,field_name,title,desc) \
175 {#field_name, title, PT_TXTMOD_BOOL,{uat_fld_chk_enable,basename ## _ ## field_name ## _set_cb,basename ## _ ## field_name ## _tostr_cb},{0,0,0},0,desc,FLDFILL}
176
177 //"Custom" handler for sma_period enumeration for backwards compatibility
io_graph_sma_period_set_cb(void * rec,const char * buf,guint len,const void * vs,const void * u2 _U_)178 static void io_graph_sma_period_set_cb(void* rec, const char* buf, guint len, const void* vs, const void* u2 _U_)
179 {
180 guint i;
181 char* str = g_strndup(buf,len);
182 const char* cstr;
183 ((io_graph_settings_t*)rec)->sma_period = 0;
184
185 //Original UAT had just raw numbers and not enumerated values with "interval SMA"
186 if (strstr(str, "interval SMA") == NULL) {
187 if (strcmp(str, "None") == 0) { //Valid enumerated value
188 } else if (strcmp(str, "0") == 0) {
189 g_free(str);
190 str = g_strdup("None");
191 } else {
192 char *str2 = g_strdup_printf("%s interval SMA", str);
193 g_free(str);
194 str = str2;
195 }
196 }
197
198 for (i=0; (cstr = ((const value_string*)vs)[i].strptr) ;i++) {
199 if (g_str_equal(cstr,str)) {
200 ((io_graph_settings_t*)rec)->sma_period = (guint32)((const value_string*)vs)[i].value;
201 g_free(str);
202 return;
203 }
204 }
205 g_free(str);
206 }
207 //Duplicated because macro covers both functions
io_graph_sma_period_tostr_cb(void * rec,char ** out_ptr,unsigned * out_len,const void * vs,const void * u2 _U_)208 static void io_graph_sma_period_tostr_cb(void* rec, char** out_ptr, unsigned* out_len, const void* vs, const void* u2 _U_)
209 {
210 guint i;
211 for (i=0;((const value_string*)vs)[i].strptr;i++) {
212 if (((const value_string*)vs)[i].value == ((io_graph_settings_t*)rec)->sma_period) {
213 *out_ptr = g_strdup(((const value_string*)vs)[i].strptr);
214 *out_len = (unsigned)strlen(*out_ptr);
215 return;
216 }
217 }
218 *out_ptr = g_strdup("None");
219 *out_len = (unsigned)strlen("None");
220 }
221
sma_period_chk_enum(void * u1 _U_,const char * strptr,guint len,const void * v,const void * u3 _U_,char ** err)222 static gboolean sma_period_chk_enum(void* u1 _U_, const char* strptr, guint len, const void* v, const void* u3 _U_, char** err) {
223 char *str = g_strndup(strptr,len);
224 guint i;
225 const value_string* vs = (const value_string *)v;
226
227 //Original UAT had just raw numbers and not enumerated values with "interval SMA"
228 if (strstr(str, "interval SMA") == NULL) {
229 if (strcmp(str, "None") == 0) { //Valid enumerated value
230 } else if (strcmp(str, "0") == 0) {
231 g_free(str);
232 str = g_strdup("None");
233 } else {
234 char *str2 = g_strdup_printf("%s interval SMA", str);
235 g_free(str);
236 str = str2;
237 }
238 }
239
240 for (i=0;vs[i].strptr;i++) {
241 if (g_strcmp0(vs[i].strptr,str) == 0) {
242 *err = NULL;
243 g_free(str);
244 return TRUE;
245 }
246 }
247
248 *err = g_strdup_printf("invalid value: %s",str);
249 g_free(str);
250 return FALSE;
251 }
252
253 #define UAT_FLD_SMA_PERIOD(basename,field_name,title,enum,desc) \
254 {#field_name, title, PT_TXTMOD_ENUM,{sma_period_chk_enum,basename ## _ ## field_name ## _set_cb,basename ## _ ## field_name ## _tostr_cb},{&(enum),&(enum),&(enum)},&(enum),desc,FLDFILL}
255
256
257 UAT_BOOL_ENABLE_CB_DEF(io_graph, enabled, io_graph_settings_t)
258 UAT_CSTRING_CB_DEF(io_graph, name, io_graph_settings_t)
259 UAT_DISPLAY_FILTER_CB_DEF(io_graph, dfilter, io_graph_settings_t)
260 UAT_COLOR_CB_DEF(io_graph, color, io_graph_settings_t)
261 UAT_VS_DEF(io_graph, style, io_graph_settings_t, guint32, 0, "Line")
262 UAT_VS_DEF(io_graph, yaxis, io_graph_settings_t, guint32, 0, "Packets")
263 UAT_PROTO_FIELD_CB_DEF(io_graph, yfield, io_graph_settings_t)
264 UAT_DEC_CB_DEF(io_graph, y_axis_factor, io_graph_settings_t)
265
266 static uat_field_t io_graph_fields[] = {
267 UAT_FLD_BOOL_ENABLE(io_graph, enabled, "Enabled", "Graph visibility"),
268 UAT_FLD_CSTRING(io_graph, name, "Graph Name", "The name of the graph"),
269 UAT_FLD_DISPLAY_FILTER(io_graph, dfilter, "Display Filter", "Graph packets matching this display filter"),
270 UAT_FLD_COLOR(io_graph, color, "Color", "Graph color (#RRGGBB)"),
271 UAT_FLD_VS(io_graph, style, "Style", graph_style_vs, "Graph style (Line, Bars, etc.)"),
272 UAT_FLD_VS(io_graph, yaxis, "Y Axis", y_axis_vs, "Y Axis units"),
273 UAT_FLD_PROTO_FIELD(io_graph, yfield, "Y Field", "Apply calculations to this field"),
274 UAT_FLD_SMA_PERIOD(io_graph, sma_period, "SMA Period", moving_avg_vs, "Simple moving average period"),
275 UAT_FLD_DEC(io_graph, y_axis_factor, "Y Axis Factor", "Y Axis Factor"),
276
277 UAT_END_FIELDS
278 };
279
io_graph_copy_cb(void * dst_ptr,const void * src_ptr,size_t)280 static void* io_graph_copy_cb(void* dst_ptr, const void* src_ptr, size_t) {
281 io_graph_settings_t* dst = (io_graph_settings_t *)dst_ptr;
282 const io_graph_settings_t* src = (const io_graph_settings_t *)src_ptr;
283
284 dst->enabled = src->enabled;
285 dst->name = g_strdup(src->name);
286 dst->dfilter = g_strdup(src->dfilter);
287 dst->color = src->color;
288 dst->style = src->style;
289 dst->yaxis = src->yaxis;
290 dst->yfield = g_strdup(src->yfield);
291 dst->sma_period = src->sma_period;
292 dst->y_axis_factor = src->y_axis_factor;
293
294 return dst;
295 }
296
io_graph_free_cb(void * p)297 static void io_graph_free_cb(void* p) {
298 io_graph_settings_t *iogs = (io_graph_settings_t *)p;
299 g_free(iogs->name);
300 g_free(iogs->dfilter);
301 g_free(iogs->yfield);
302 }
303
304 } // extern "C"
305
IOGraphDialog(QWidget & parent,CaptureFile & cf,QString displayFilter)306 IOGraphDialog::IOGraphDialog(QWidget &parent, CaptureFile &cf, QString displayFilter) :
307 WiresharkDialog(parent, cf),
308 ui(new Ui::IOGraphDialog),
309 uat_model_(nullptr),
310 uat_delegate_(nullptr),
311 base_graph_(nullptr),
312 tracer_(nullptr),
313 start_time_(0.0),
314 mouse_drags_(true),
315 rubber_band_(nullptr),
316 stat_timer_(nullptr),
317 need_replot_(false),
318 need_retap_(false),
319 auto_axes_(true),
320 number_ticker_(new QCPAxisTicker),
321 datetime_ticker_(new QCPAxisTickerDateTime)
322 {
323 ui->setupUi(this);
324 loadGeometry();
325
326 setWindowSubtitle(tr("I/O Graphs"));
327 setAttribute(Qt::WA_DeleteOnClose, true);
328 QCustomPlot *iop = ui->ioPlot;
329
330 ui->newToolButton->setStockIcon("list-add");
331 ui->deleteToolButton->setStockIcon("list-remove");
332 ui->copyToolButton->setStockIcon("list-copy");
333 ui->clearToolButton->setStockIcon("list-clear");
334
335 #ifdef Q_OS_MAC
336 ui->newToolButton->setAttribute(Qt::WA_MacSmallSize, true);
337 ui->deleteToolButton->setAttribute(Qt::WA_MacSmallSize, true);
338 ui->copyToolButton->setAttribute(Qt::WA_MacSmallSize, true);
339 ui->clearToolButton->setAttribute(Qt::WA_MacSmallSize, true);
340 #endif
341
342 QPushButton *save_bt = ui->buttonBox->button(QDialogButtonBox::Save);
343 save_bt->setText(tr("Save As…"));
344
345 QPushButton *copy_bt = ui->buttonBox->addButton(tr("Copy"), QDialogButtonBox::ActionRole);
346 connect (copy_bt, SIGNAL(clicked()), this, SLOT(copyAsCsvClicked()));
347
348 CopyFromProfileButton * copy_button = new CopyFromProfileButton(this, "io_graphs", tr("Copy graphs from another profile."));
349 ui->buttonBox->addButton(copy_button, QDialogButtonBox::ActionRole);
350 connect(copy_button, &CopyFromProfileButton::copyProfile, this, &IOGraphDialog::copyFromProfile);
351
352 QPushButton *close_bt = ui->buttonBox->button(QDialogButtonBox::Close);
353 if (close_bt) {
354 close_bt->setDefault(true);
355 }
356
357 ui->automaticUpdateCheckBox->setChecked(prefs.gui_io_graph_automatic_update ? true : false);
358
359 stat_timer_ = new QTimer(this);
360 connect(stat_timer_, SIGNAL(timeout()), this, SLOT(updateStatistics()));
361 stat_timer_->start(stat_update_interval_);
362
363 // Intervals (ms)
364 ui->intervalComboBox->addItem(tr("1 ms"), 1);
365 ui->intervalComboBox->addItem(tr("2 ms"), 2);
366 ui->intervalComboBox->addItem(tr("5 ms"), 5);
367 ui->intervalComboBox->addItem(tr("10 ms"), 10);
368 ui->intervalComboBox->addItem(tr("20 ms"), 20);
369 ui->intervalComboBox->addItem(tr("50 ms"), 50);
370 ui->intervalComboBox->addItem(tr("100 ms"), 100);
371 ui->intervalComboBox->addItem(tr("200 ms"), 200);
372 ui->intervalComboBox->addItem(tr("500 ms"), 500);
373 ui->intervalComboBox->addItem(tr("1 sec"), 1000);
374 ui->intervalComboBox->addItem(tr("2 sec"), 2000);
375 ui->intervalComboBox->addItem(tr("5 sec"), 5000);
376 ui->intervalComboBox->addItem(tr("10 sec"), 10000);
377 ui->intervalComboBox->addItem(tr("1 min"), 60000);
378 ui->intervalComboBox->addItem(tr("10 min"), 600000);
379 ui->intervalComboBox->setCurrentIndex(9);
380
381 ui->todCheckBox->setChecked(false);
382 iop->xAxis->setTicker(number_ticker_);
383
384 ui->dragRadioButton->setChecked(mouse_drags_);
385
386 ctx_menu_.addAction(ui->actionZoomIn);
387 ctx_menu_.addAction(ui->actionZoomInX);
388 ctx_menu_.addAction(ui->actionZoomInY);
389 ctx_menu_.addAction(ui->actionZoomOut);
390 ctx_menu_.addAction(ui->actionZoomOutX);
391 ctx_menu_.addAction(ui->actionZoomOutY);
392 ctx_menu_.addAction(ui->actionReset);
393 ctx_menu_.addSeparator();
394 ctx_menu_.addAction(ui->actionMoveRight10);
395 ctx_menu_.addAction(ui->actionMoveLeft10);
396 ctx_menu_.addAction(ui->actionMoveUp10);
397 ctx_menu_.addAction(ui->actionMoveDown10);
398 ctx_menu_.addAction(ui->actionMoveRight1);
399 ctx_menu_.addAction(ui->actionMoveLeft1);
400 ctx_menu_.addAction(ui->actionMoveUp1);
401 ctx_menu_.addAction(ui->actionMoveDown1);
402 ctx_menu_.addSeparator();
403 ctx_menu_.addAction(ui->actionGoToPacket);
404 ctx_menu_.addSeparator();
405 ctx_menu_.addAction(ui->actionDragZoom);
406 ctx_menu_.addAction(ui->actionToggleTimeOrigin);
407 ctx_menu_.addAction(ui->actionCrosshairs);
408 set_action_shortcuts_visible_in_context_menu(ctx_menu_.actions());
409
410 iop->xAxis->setLabel(tr("Time (s)"));
411
412 iop->setMouseTracking(true);
413 iop->setEnabled(true);
414
415 QCPTextElement *title = new QCPTextElement(iop);
416 iop->plotLayout()->insertRow(0);
417 iop->plotLayout()->addElement(0, 0, title);
418 title->setText(tr("Wireshark I/O Graphs: %1").arg(cap_file_.fileDisplayName()));
419
420 tracer_ = new QCPItemTracer(iop);
421
422 loadProfileGraphs();
423 bool filterExists = false;
424 if (uat_model_->rowCount() > 0) {
425 for (int i = 0; i < uat_model_->rowCount(); i++) {
426 createIOGraph(i);
427 if (ioGraphs_.at(i)->filter().compare(displayFilter) == 0)
428 filterExists = true;
429 }
430 if (! filterExists && displayFilter.length() > 0)
431 addGraph(true, tr("Filtered packets"), displayFilter, ColorUtils::graphColor(uat_model_->rowCount()),
432 IOGraph::psLine, IOG_ITEM_UNIT_PACKETS, QString(), DEFAULT_MOVING_AVERAGE, DEFAULT_Y_AXIS_FACTOR);
433 } else {
434 addDefaultGraph(true, 0);
435 addDefaultGraph(true, 1);
436 if (displayFilter.length() > 0)
437 addGraph(true, tr("Filtered packets"), displayFilter, ColorUtils::graphColor(uat_model_->rowCount()),
438 IOGraph::psLine, IOG_ITEM_UNIT_PACKETS, QString(), DEFAULT_MOVING_AVERAGE, DEFAULT_Y_AXIS_FACTOR);
439 }
440
441 toggleTracerStyle(true);
442 iop->setFocus();
443
444 iop->rescaleAxes();
445
446 ui->clearToolButton->setEnabled(uat_model_->rowCount() != 0);
447
448 ui->splitter->setStretchFactor(0, 95);
449 ui->splitter->setStretchFactor(1, 5);
450
451 //XXX - resize columns?
452
453 ProgressFrame::addToButtonBox(ui->buttonBox, &parent);
454
455 connect(iop, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(graphClicked(QMouseEvent*)));
456 connect(iop, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMoved(QMouseEvent*)));
457 connect(iop, SIGNAL(mouseRelease(QMouseEvent*)), this, SLOT(mouseReleased(QMouseEvent*)));
458 disconnect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
459 }
460
~IOGraphDialog()461 IOGraphDialog::~IOGraphDialog()
462 {
463 cap_file_.stopLoading();
464 foreach(IOGraph* iog, ioGraphs_) {
465 delete iog;
466 }
467 delete ui;
468 ui = NULL;
469 }
470
copyFromProfile(QString filename)471 void IOGraphDialog::copyFromProfile(QString filename)
472 {
473 guint orig_data_len = iog_uat_->raw_data->len;
474
475 gchar *err = NULL;
476 if (uat_load(iog_uat_, filename.toUtf8().constData(), &err)) {
477 iog_uat_->changed = TRUE;
478 uat_model_->reloadUat();
479 for (guint i = orig_data_len; i < iog_uat_->raw_data->len; i++) {
480 createIOGraph(i);
481 }
482 } else {
483 report_failure("Error while loading %s: %s", iog_uat_->name, err);
484 g_free(err);
485 }
486 }
487
addGraph(bool checked,QString name,QString dfilter,QRgb color_idx,IOGraph::PlotStyles style,io_graph_item_unit_t value_units,QString yfield,int moving_average,int y_axis_factor)488 void IOGraphDialog::addGraph(bool checked, QString name, QString dfilter, QRgb color_idx, IOGraph::PlotStyles style, io_graph_item_unit_t value_units, QString yfield, int moving_average, int y_axis_factor)
489 {
490
491 QVariantList newRowData;
492 newRowData.append(checked ? Qt::Checked : Qt::Unchecked);
493 newRowData.append(name);
494 newRowData.append(dfilter);
495 newRowData.append(QColor(color_idx));
496 newRowData.append(val_to_str_const(style, graph_style_vs, "None"));
497 newRowData.append(val_to_str_const(value_units, y_axis_vs, "Packets"));
498 newRowData.append(yfield);
499 newRowData.append(val_to_str_const((guint32) moving_average, moving_avg_vs, "None"));
500 newRowData.append(y_axis_factor);
501
502 QModelIndex newIndex = uat_model_->appendEntry(newRowData);
503 if ( !newIndex.isValid() )
504 {
505 qDebug() << "Failed to add a new record";
506 return;
507 }
508 ui->graphUat->setCurrentIndex(newIndex);
509 createIOGraph(newIndex.row());
510 }
511
addGraph(bool copy_from_current)512 void IOGraphDialog::addGraph(bool copy_from_current)
513 {
514 const QModelIndex ¤t = ui->graphUat->currentIndex();
515 if (copy_from_current && !current.isValid())
516 return;
517
518 QModelIndex copyIdx;
519
520 if (copy_from_current) {
521 copyIdx = uat_model_->copyRow(current);
522 if (!copyIdx.isValid())
523 {
524 qDebug() << "Failed to add a new record";
525 return;
526 }
527 createIOGraph(copyIdx.row());
528
529 ui->graphUat->setCurrentIndex(copyIdx);
530 } else {
531 addDefaultGraph(false);
532 copyIdx = uat_model_->index(uat_model_->rowCount() - 1, 0);
533 }
534
535 ui->graphUat->setCurrentIndex(copyIdx);
536 }
537
createIOGraph(int currentRow)538 void IOGraphDialog::createIOGraph(int currentRow)
539 {
540 // XXX - Should IOGraph have it's own list that has to sync with UAT?
541 ioGraphs_.append(new IOGraph(ui->ioPlot));
542 IOGraph* iog = ioGraphs_[currentRow];
543
544 connect(this, SIGNAL(recalcGraphData(capture_file *, bool)), iog, SLOT(recalcGraphData(capture_file *, bool)));
545 connect(this, SIGNAL(reloadValueUnitFields()), iog, SLOT(reloadValueUnitField()));
546 connect(&cap_file_, SIGNAL(captureEvent(CaptureEvent)),
547 iog, SLOT(captureEvent(CaptureEvent)));
548 connect(iog, SIGNAL(requestRetap()), this, SLOT(scheduleRetap()));
549 connect(iog, SIGNAL(requestRecalc()), this, SLOT(scheduleRecalc()));
550 connect(iog, SIGNAL(requestReplot()), this, SLOT(scheduleReplot()));
551
552 syncGraphSettings(currentRow);
553 if (iog->visible()) {
554 scheduleRetap();
555 }
556 }
557
addDefaultGraph(bool enabled,int idx)558 void IOGraphDialog::addDefaultGraph(bool enabled, int idx)
559 {
560 switch (idx % 2) {
561 case 0:
562 addGraph(enabled, tr("All Packets"), QString(), ColorUtils::graphColor(idx),
563 IOGraph::psLine, IOG_ITEM_UNIT_PACKETS, QString(), DEFAULT_MOVING_AVERAGE, DEFAULT_Y_AXIS_FACTOR);
564 break;
565 default:
566 addGraph(enabled, tr("TCP Errors"), "tcp.analysis.flags", ColorUtils::graphColor(4), // 4 = red
567 IOGraph::psBar, IOG_ITEM_UNIT_PACKETS, QString(), DEFAULT_MOVING_AVERAGE, DEFAULT_Y_AXIS_FACTOR);
568 break;
569 }
570 }
571
572 // Sync the settings from UAT model to its IOGraph.
573 // Disables the graph if any errors are found.
574 //
575 // NOTE: Setting dfilter, yaxis and yfield here will all end up in setFilter() and this
576 // has a chicken-and-egg problem because setFilter() depends on previous assigned
577 // values for filter_, val_units_ and vu_field_. Setting values in wrong order
578 // may give unpredicted results because setFilter() does not always set filter_
579 // on errors.
580 // TODO: The issues in the above note should be fixed and setFilter() should not be
581 // called so frequently.
582
syncGraphSettings(int row)583 void IOGraphDialog::syncGraphSettings(int row)
584 {
585 IOGraph *iog = ioGraphs_.value(row, Q_NULLPTR);
586
587 if (!uat_model_->index(row, colEnabled).isValid() || !iog)
588 return;
589
590 bool visible = graphIsEnabled(row);
591 bool retap = !iog->visible() && visible;
592 QString data_str;
593
594 iog->setName(uat_model_->data(uat_model_->index(row, colName)).toString());
595 iog->setFilter(uat_model_->data(uat_model_->index(row, colDFilter)).toString());
596
597 /* plot style depend on the value unit, so set it first. */
598 data_str = uat_model_->data(uat_model_->index(row, colYAxis)).toString();
599 iog->setValueUnits((int) str_to_val(qUtf8Printable(data_str), y_axis_vs, IOG_ITEM_UNIT_PACKETS));
600 iog->setValueUnitField(uat_model_->data(uat_model_->index(row, colYField)).toString());
601
602 iog->setColor(uat_model_->data(uat_model_->index(row, colColor), Qt::DecorationRole).value<QColor>().rgb());
603 data_str = uat_model_->data(uat_model_->index(row, colStyle)).toString();
604 iog->setPlotStyle((int) str_to_val(qUtf8Printable(data_str), graph_style_vs, 0));
605
606 data_str = uat_model_->data(uat_model_->index(row, colSMAPeriod)).toString();
607 iog->moving_avg_period_ = str_to_val(qUtf8Printable(data_str), moving_avg_vs, 0);
608
609 iog->y_axis_factor_ = uat_model_->data(uat_model_->index(row, colYAxisFactor)).toInt();
610
611 iog->setInterval(ui->intervalComboBox->itemData(ui->intervalComboBox->currentIndex()).toInt());
612
613 if (!iog->configError().isEmpty()) {
614 hint_err_ = iog->configError();
615 visible = false;
616 retap = false;
617 }
618
619 iog->setVisible(visible);
620
621 getGraphInfo();
622 mouseMoved(NULL); // Update hint
623 updateLegend();
624
625 if (visible) {
626 if (retap) {
627 scheduleRetap();
628 } else {
629 scheduleReplot();
630 }
631 }
632 }
633
updateWidgets()634 void IOGraphDialog::updateWidgets()
635 {
636 WiresharkDialog::updateWidgets();
637 }
638
scheduleReplot(bool now)639 void IOGraphDialog::scheduleReplot(bool now)
640 {
641 need_replot_ = true;
642 if (now) updateStatistics();
643 // A plot finished, force an update of the legend now in case a time unit
644 // was involved (which might append "(ms)" to the label).
645 updateLegend();
646 }
647
scheduleRecalc(bool now)648 void IOGraphDialog::scheduleRecalc(bool now)
649 {
650 need_recalc_ = true;
651 if (now) updateStatistics();
652 }
653
scheduleRetap(bool now)654 void IOGraphDialog::scheduleRetap(bool now)
655 {
656 need_retap_ = true;
657 if (now) updateStatistics();
658 }
659
reloadFields()660 void IOGraphDialog::reloadFields()
661 {
662 emit reloadValueUnitFields();
663 }
664
keyPressEvent(QKeyEvent * event)665 void IOGraphDialog::keyPressEvent(QKeyEvent *event)
666 {
667 int pan_pixels = event->modifiers() & Qt::ShiftModifier ? 1 : 10;
668
669 switch(event->key()) {
670 case Qt::Key_Minus:
671 case Qt::Key_Underscore: // Shifted minus on U.S. keyboards
672 case Qt::Key_O: // GTK+
673 case Qt::Key_R:
674 zoomAxes(false);
675 break;
676 case Qt::Key_Plus:
677 case Qt::Key_Equal: // Unshifted plus on U.S. keyboards
678 case Qt::Key_I: // GTK+
679 zoomAxes(true);
680 break;
681 case Qt::Key_X: // Zoom X axis only
682 if (event->modifiers() & Qt::ShiftModifier) {
683 zoomXAxis(false); // upper case X -> Zoom out
684 } else {
685 zoomXAxis(true); // lower case x -> Zoom in
686 }
687 break;
688 case Qt::Key_Y: // Zoom Y axis only
689 if (event->modifiers() & Qt::ShiftModifier) {
690 zoomYAxis(false); // upper case Y -> Zoom out
691 } else {
692 zoomYAxis(true); // lower case y -> Zoom in
693 }
694 break;
695 case Qt::Key_Right:
696 case Qt::Key_L:
697 panAxes(pan_pixels, 0);
698 break;
699 case Qt::Key_Left:
700 case Qt::Key_H:
701 panAxes(-1 * pan_pixels, 0);
702 break;
703 case Qt::Key_Up:
704 case Qt::Key_K:
705 panAxes(0, pan_pixels);
706 break;
707 case Qt::Key_Down:
708 case Qt::Key_J:
709 panAxes(0, -1 * pan_pixels);
710 break;
711
712 case Qt::Key_Space:
713 toggleTracerStyle();
714 break;
715
716 case Qt::Key_0:
717 case Qt::Key_ParenRight: // Shifted 0 on U.S. keyboards
718 case Qt::Key_Home:
719 resetAxes();
720 break;
721
722 case Qt::Key_G:
723 on_actionGoToPacket_triggered();
724 break;
725 case Qt::Key_T:
726 on_actionToggleTimeOrigin_triggered();
727 break;
728 case Qt::Key_Z:
729 on_actionDragZoom_triggered();
730 break;
731 }
732
733 QDialog::keyPressEvent(event);
734 }
735
reject()736 void IOGraphDialog::reject()
737 {
738 if (!uat_model_)
739 return;
740
741 // Changes to the I/O Graphs settings are always saved,
742 // there is no possibility for "rejection".
743 QString error;
744 if (uat_model_->applyChanges(error)) {
745 if (!error.isEmpty()) {
746 report_failure("%s", qPrintable(error));
747 }
748 }
749
750 QDialog::reject();
751 }
752
zoomAxes(bool in)753 void IOGraphDialog::zoomAxes(bool in)
754 {
755 QCustomPlot *iop = ui->ioPlot;
756 double h_factor = iop->axisRect()->rangeZoomFactor(Qt::Horizontal);
757 double v_factor = iop->axisRect()->rangeZoomFactor(Qt::Vertical);
758
759 auto_axes_ = false;
760
761 if (!in) {
762 h_factor = pow(h_factor, -1);
763 v_factor = pow(v_factor, -1);
764 }
765
766 iop->xAxis->scaleRange(h_factor, iop->xAxis->range().center());
767 iop->yAxis->scaleRange(v_factor, iop->yAxis->range().center());
768 iop->replot();
769 }
770
zoomXAxis(bool in)771 void IOGraphDialog::zoomXAxis(bool in)
772 {
773 QCustomPlot *iop = ui->ioPlot;
774 double h_factor = iop->axisRect()->rangeZoomFactor(Qt::Horizontal);
775
776 auto_axes_ = false;
777
778 if (!in) {
779 h_factor = pow(h_factor, -1);
780 }
781
782 iop->xAxis->scaleRange(h_factor, iop->xAxis->range().center());
783 iop->replot();
784 }
785
zoomYAxis(bool in)786 void IOGraphDialog::zoomYAxis(bool in)
787 {
788 QCustomPlot *iop = ui->ioPlot;
789 double v_factor = iop->axisRect()->rangeZoomFactor(Qt::Vertical);
790
791 auto_axes_ = false;
792
793 if (!in) {
794 v_factor = pow(v_factor, -1);
795 }
796
797 iop->yAxis->scaleRange(v_factor, iop->yAxis->range().center());
798 iop->replot();
799 }
800
panAxes(int x_pixels,int y_pixels)801 void IOGraphDialog::panAxes(int x_pixels, int y_pixels)
802 {
803 QCustomPlot *iop = ui->ioPlot;
804 double h_pan = 0.0;
805 double v_pan = 0.0;
806
807 auto_axes_ = false;
808
809 h_pan = iop->xAxis->range().size() * x_pixels / iop->xAxis->axisRect()->width();
810 v_pan = iop->yAxis->range().size() * y_pixels / iop->yAxis->axisRect()->height();
811 // The GTK+ version won't pan unless we're zoomed. Should we do the same here?
812 if (h_pan) {
813 iop->xAxis->moveRange(h_pan);
814 iop->replot();
815 }
816 if (v_pan) {
817 iop->yAxis->moveRange(v_pan);
818 iop->replot();
819 }
820 }
821
822
toggleTracerStyle(bool force_default)823 void IOGraphDialog::toggleTracerStyle(bool force_default)
824 {
825 if (!tracer_->visible() && !force_default) return;
826 if (!ui->ioPlot->graph(0)) return;
827
828 QPen sp_pen = ui->ioPlot->graph(0)->pen();
829 QCPItemTracer::TracerStyle tstyle = QCPItemTracer::tsCrosshair;
830 QPen tr_pen = QPen(tracer_->pen());
831 QColor tr_color = sp_pen.color();
832
833 if (force_default || tracer_->style() != QCPItemTracer::tsCircle) {
834 tstyle = QCPItemTracer::tsCircle;
835 tr_color.setAlphaF(1.0);
836 tr_pen.setWidthF(1.5);
837 } else {
838 tr_color.setAlphaF(0.5);
839 tr_pen.setWidthF(1.0);
840 }
841
842 tracer_->setStyle(tstyle);
843 tr_pen.setColor(tr_color);
844 tracer_->setPen(tr_pen);
845 ui->ioPlot->replot();
846 }
847
848 // Returns the IOGraph which is most likely to be used by the user. This is the
849 // currently selected, visible graph or the first visible graph otherwise.
currentActiveGraph() const850 IOGraph *IOGraphDialog::currentActiveGraph() const
851 {
852 QModelIndex index = ui->graphUat->currentIndex();
853 if (index.isValid()) {
854 return ioGraphs_.value(index.row(), NULL);
855 }
856
857 //if no currently selected item, go with first item enabled
858 for (int row = 0; row < uat_model_->rowCount(); row++)
859 {
860 if (graphIsEnabled(row)) {
861 return ioGraphs_.value(row, NULL);
862 }
863 }
864
865 return NULL;
866 }
867
graphIsEnabled(int row) const868 bool IOGraphDialog::graphIsEnabled(int row) const
869 {
870 Qt::CheckState state = static_cast<Qt::CheckState>(uat_model_->data(uat_model_->index(row, colEnabled), Qt::CheckStateRole).toInt());
871 return state == Qt::Checked;
872 }
873
874 // Scan through our graphs and gather information.
875 // QCPItemTracers can only be associated with QCPGraphs. Find the first one
876 // and associate it with our tracer. Set bar stacking order while we're here.
getGraphInfo()877 void IOGraphDialog::getGraphInfo()
878 {
879 base_graph_ = NULL;
880 QCPBars *prev_bars = NULL;
881 start_time_ = 0.0;
882
883 tracer_->setGraph(NULL);
884 IOGraph *selectedGraph = currentActiveGraph();
885
886 if (uat_model_ != NULL) {
887 //all graphs may not be created yet, so bounds check the graph array
888 for (int row = 0; row < uat_model_->rowCount(); row++) {
889 IOGraph* iog = ioGraphs_.value(row, Q_NULLPTR);
890 if (iog && graphIsEnabled(row)) {
891 QCPGraph *graph = iog->graph();
892 QCPBars *bars = iog->bars();
893 if (graph && (!base_graph_ || iog == selectedGraph)) {
894 base_graph_ = graph;
895 } else if (bars &&
896 (uat_model_->data(uat_model_->index(row, colStyle), Qt::DisplayRole).toString().compare(graph_style_vs[IOGraph::psStackedBar].strptr) == 0) &&
897 iog->visible()) {
898 bars->moveBelow(NULL); // Remove from existing stack
899 bars->moveBelow(prev_bars);
900 prev_bars = bars;
901 }
902 if (iog->visible() && iog->maxInterval() >= 0) {
903 double iog_start = iog->startOffset();
904 if (start_time_ == 0.0 || iog_start < start_time_) {
905 start_time_ = iog_start;
906 }
907 }
908
909 }
910 }
911 }
912 if (base_graph_ && base_graph_->data()->size() > 0) {
913 tracer_->setGraph(base_graph_);
914 tracer_->setVisible(true);
915 }
916 }
917
updateLegend()918 void IOGraphDialog::updateLegend()
919 {
920 QCustomPlot *iop = ui->ioPlot;
921 QSet<QString> vu_label_set;
922 QString intervalText = ui->intervalComboBox->itemText(ui->intervalComboBox->currentIndex());
923
924 iop->legend->setVisible(false);
925 iop->yAxis->setLabel(QString());
926
927 // Find unique labels
928 if (uat_model_ != NULL) {
929 for (int row = 0; row < uat_model_->rowCount(); row++) {
930 IOGraph *iog = ioGraphs_.value(row, Q_NULLPTR);
931 if (graphIsEnabled(row) && iog) {
932 QString label(iog->valueUnitLabel());
933 if (!iog->scaledValueUnit().isEmpty()) {
934 label += " (" + iog->scaledValueUnit() + ")";
935 }
936 vu_label_set.insert(label);
937 }
938 }
939 }
940
941 // Nothing.
942 if (vu_label_set.size() < 1) {
943 return;
944 }
945
946 // All the same. Use the Y Axis label.
947 if (vu_label_set.size() == 1) {
948 iop->yAxis->setLabel(vu_label_set.values()[0] + "/" + intervalText);
949 return;
950 }
951
952 // Differing labels. Create a legend with a Title label at top.
953 // Legend Title thanks to: https://www.qcustomplot.com/index.php/support/forum/443
954 QCPTextElement* legendTitle = qobject_cast<QCPTextElement*>(iop->legend->elementAt(0));
955 if (legendTitle == NULL) {
956 legendTitle = new QCPTextElement(iop, QString(""));
957 iop->legend->insertRow(0);
958 iop->legend->addElement(0, 0, legendTitle);
959 }
960 legendTitle->setText(QString(intervalText + " Intervals "));
961
962 if (uat_model_ != NULL) {
963 for (int row = 0; row < uat_model_->rowCount(); row++) {
964 IOGraph *iog = ioGraphs_.value(row, Q_NULLPTR);
965 if (iog) {
966 if (graphIsEnabled(row)) {
967 iog->addToLegend();
968 } else {
969 iog->removeFromLegend();
970 }
971 }
972 }
973 }
974 iop->legend->setVisible(true);
975 }
976
getZoomRanges(QRect zoom_rect)977 QRectF IOGraphDialog::getZoomRanges(QRect zoom_rect)
978 {
979 QRectF zoom_ranges = QRectF();
980
981 if (zoom_rect.width() < min_zoom_pixels_ && zoom_rect.height() < min_zoom_pixels_) {
982 return zoom_ranges;
983 }
984
985 QCustomPlot *iop = ui->ioPlot;
986 QRect zr = zoom_rect.normalized();
987 QRect ar = iop->axisRect()->rect();
988 if (ar.intersects(zr)) {
989 QRect zsr = ar.intersected(zr);
990 zoom_ranges.setX(iop->xAxis->range().lower
991 + iop->xAxis->range().size() * (zsr.left() - ar.left()) / ar.width());
992 zoom_ranges.setWidth(iop->xAxis->range().size() * zsr.width() / ar.width());
993
994 // QRects grow down
995 zoom_ranges.setY(iop->yAxis->range().lower
996 + iop->yAxis->range().size() * (ar.bottom() - zsr.bottom()) / ar.height());
997 zoom_ranges.setHeight(iop->yAxis->range().size() * zsr.height() / ar.height());
998 }
999 return zoom_ranges;
1000 }
1001
graphClicked(QMouseEvent * event)1002 void IOGraphDialog::graphClicked(QMouseEvent *event)
1003 {
1004 QCustomPlot *iop = ui->ioPlot;
1005
1006 if (event->button() == Qt::RightButton) {
1007 // XXX We should find some way to get ioPlot to handle a
1008 // contextMenuEvent instead.
1009 ctx_menu_.exec(event->globalPos());
1010 } else if (mouse_drags_) {
1011 if (iop->axisRect()->rect().contains(event->pos())) {
1012 iop->setCursor(QCursor(Qt::ClosedHandCursor));
1013 }
1014 on_actionGoToPacket_triggered();
1015 } else {
1016 if (!rubber_band_) {
1017 rubber_band_ = new QRubberBand(QRubberBand::Rectangle, iop);
1018 }
1019 rb_origin_ = event->pos();
1020 rubber_band_->setGeometry(QRect(rb_origin_, QSize()));
1021 rubber_band_->show();
1022 }
1023 iop->setFocus();
1024 }
1025
mouseMoved(QMouseEvent * event)1026 void IOGraphDialog::mouseMoved(QMouseEvent *event)
1027 {
1028 QCustomPlot *iop = ui->ioPlot;
1029 QString hint;
1030 Qt::CursorShape shape = Qt::ArrowCursor;
1031
1032 if (!hint_err_.isEmpty()) {
1033 hint += QString("<b>%1</b> ").arg(hint_err_);
1034 }
1035 if (event) {
1036 if (event->buttons().testFlag(Qt::LeftButton)) {
1037 if (mouse_drags_) {
1038 shape = Qt::ClosedHandCursor;
1039 } else {
1040 shape = Qt::CrossCursor;
1041 }
1042 } else if (iop->axisRect()->rect().contains(event->pos())) {
1043 if (mouse_drags_) {
1044 shape = Qt::OpenHandCursor;
1045 } else {
1046 shape = Qt::CrossCursor;
1047 }
1048 }
1049 iop->setCursor(QCursor(shape));
1050 }
1051
1052 if (mouse_drags_) {
1053 double ts = 0;
1054 packet_num_ = 0;
1055 int interval_packet = -1;
1056
1057 if (event && tracer_->graph()) {
1058 tracer_->setGraphKey(iop->xAxis->pixelToCoord(event->pos().x()));
1059 ts = tracer_->position->key();
1060 if (IOGraph *iog = currentActiveGraph()) {
1061 interval_packet = iog->packetFromTime(ts);
1062 }
1063 }
1064
1065 if (interval_packet < 0) {
1066 hint += tr("Hover over the graph for details.");
1067 } else {
1068 QString msg = tr("No packets in interval");
1069 QString val;
1070 if (interval_packet > 0) {
1071 packet_num_ = (guint32) interval_packet;
1072 msg = QString("%1 %2")
1073 .arg(!file_closed_ ? tr("Click to select packet") : tr("Packet"))
1074 .arg(packet_num_);
1075 val = " = " + QString::number(tracer_->position->value(), 'g', 4);
1076 }
1077 hint += tr("%1 (%2s%3).")
1078 .arg(msg)
1079 .arg(QString::number(ts, 'g', 4))
1080 .arg(val);
1081 }
1082 iop->replot();
1083 } else {
1084 if (event && rubber_band_ && rubber_band_->isVisible()) {
1085 rubber_band_->setGeometry(QRect(rb_origin_, event->pos()).normalized());
1086 QRectF zoom_ranges = getZoomRanges(QRect(rb_origin_, event->pos()));
1087 if (zoom_ranges.width() > 0.0 && zoom_ranges.height() > 0.0) {
1088 hint += tr("Release to zoom, x = %1 to %2, y = %3 to %4")
1089 .arg(zoom_ranges.x())
1090 .arg(zoom_ranges.x() + zoom_ranges.width())
1091 .arg(zoom_ranges.y())
1092 .arg(zoom_ranges.y() + zoom_ranges.height());
1093 } else {
1094 hint += tr("Unable to select range.");
1095 }
1096 } else {
1097 hint += tr("Click to select a portion of the graph.");
1098 }
1099 }
1100
1101 hint.prepend("<small><i>");
1102 hint.append("</i></small>");
1103 ui->hintLabel->setText(hint);
1104 }
1105
mouseReleased(QMouseEvent * event)1106 void IOGraphDialog::mouseReleased(QMouseEvent *event)
1107 {
1108 QCustomPlot *iop = ui->ioPlot;
1109 auto_axes_ = false;
1110 if (rubber_band_) {
1111 rubber_band_->hide();
1112 if (!mouse_drags_) {
1113 QRectF zoom_ranges = getZoomRanges(QRect(rb_origin_, event->pos()));
1114 if (zoom_ranges.width() > 0.0 && zoom_ranges.height() > 0.0) {
1115 iop->xAxis->setRangeLower(zoom_ranges.x());
1116 iop->xAxis->setRangeUpper(zoom_ranges.x() + zoom_ranges.width());
1117 iop->yAxis->setRangeLower(zoom_ranges.y());
1118 iop->yAxis->setRangeUpper(zoom_ranges.y() + zoom_ranges.height());
1119 iop->replot();
1120 }
1121 }
1122 } else if (iop->cursor().shape() == Qt::ClosedHandCursor) {
1123 iop->setCursor(QCursor(Qt::OpenHandCursor));
1124 }
1125 }
1126
resetAxes()1127 void IOGraphDialog::resetAxes()
1128 {
1129 QCustomPlot *iop = ui->ioPlot;
1130 QCPRange x_range = iop->xAxis->scaleType() == QCPAxis::stLogarithmic ?
1131 iop->xAxis->range().sanitizedForLogScale() : iop->xAxis->range();
1132
1133 double pixel_pad = 10.0; // per side
1134
1135 iop->rescaleAxes(true);
1136
1137 double axis_pixels = iop->xAxis->axisRect()->width();
1138 iop->xAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, x_range.center());
1139
1140 axis_pixels = iop->yAxis->axisRect()->height();
1141 iop->yAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, iop->yAxis->range().center());
1142
1143 auto_axes_ = true;
1144 iop->replot();
1145 }
1146
updateStatistics()1147 void IOGraphDialog::updateStatistics()
1148 {
1149 if (!isVisible()) return;
1150
1151 if (need_retap_ && !file_closed_ && prefs.gui_io_graph_automatic_update) {
1152 need_retap_ = false;
1153 cap_file_.retapPackets();
1154 // The user might have closed the window while tapping, which means
1155 // we might no longer exist.
1156 } else {
1157 if (need_recalc_ && !file_closed_ && prefs.gui_io_graph_automatic_update) {
1158 need_recalc_ = false;
1159 need_replot_ = true;
1160 int enabled_graphs = 0;
1161
1162 if (uat_model_ != NULL) {
1163 for (int row = 0; row < uat_model_->rowCount(); row++) {
1164 if (graphIsEnabled(row)) {
1165 ++enabled_graphs;
1166 }
1167 }
1168 }
1169 // With multiple visible graphs, disable Y scaling to avoid
1170 // multiple, distinct units.
1171 emit recalcGraphData(cap_file_.capFile(), enabled_graphs == 1);
1172 if (!tracer_->graph()) {
1173 if (base_graph_ && base_graph_->data()->size() > 0) {
1174 tracer_->setGraph(base_graph_);
1175 tracer_->setVisible(true);
1176 } else {
1177 tracer_->setVisible(false);
1178 }
1179 }
1180 }
1181 if (need_replot_) {
1182 need_replot_ = false;
1183 if (auto_axes_) {
1184 resetAxes();
1185 }
1186 ui->ioPlot->replot();
1187 }
1188 }
1189 }
1190
loadProfileGraphs()1191 void IOGraphDialog::loadProfileGraphs()
1192 {
1193 if (iog_uat_ == NULL) {
1194
1195 iog_uat_ = uat_new("I/O Graphs",
1196 sizeof(io_graph_settings_t),
1197 "io_graphs",
1198 TRUE,
1199 &iog_settings_,
1200 &num_io_graphs_,
1201 0, /* doesn't affect anything that requires a GUI update */
1202 "ChStatIOGraphs",
1203 io_graph_copy_cb,
1204 NULL,
1205 io_graph_free_cb,
1206 NULL,
1207 NULL,
1208 io_graph_fields);
1209
1210 uat_set_default_values(iog_uat_, iog_uat_defaults_);
1211
1212 char* err = NULL;
1213 if (!uat_load(iog_uat_, NULL, &err)) {
1214 report_failure("Error while loading %s: %s. Default graph values will be used", iog_uat_->name, err);
1215 g_free(err);
1216 uat_clear(iog_uat_);
1217 }
1218 }
1219
1220 uat_model_ = new UatModel(NULL, iog_uat_);
1221 uat_delegate_ = new UatDelegate;
1222 ui->graphUat->setModel(uat_model_);
1223 ui->graphUat->setItemDelegate(uat_delegate_);
1224
1225 connect(uat_model_, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
1226 this, SLOT(modelDataChanged(QModelIndex)));
1227 connect(uat_model_, SIGNAL(modelReset()), this, SLOT(modelRowsReset()));
1228 }
1229
1230 // Slots
1231
on_intervalComboBox_currentIndexChanged(int)1232 void IOGraphDialog::on_intervalComboBox_currentIndexChanged(int)
1233 {
1234 int interval = ui->intervalComboBox->itemData(ui->intervalComboBox->currentIndex()).toInt();
1235 bool need_retap = false;
1236
1237 if (uat_model_ != NULL) {
1238 for (int row = 0; row < uat_model_->rowCount(); row++) {
1239 IOGraph *iog = ioGraphs_.value(row, NULL);
1240 if (iog) {
1241 iog->setInterval(interval);
1242 if (iog->visible()) {
1243 need_retap = true;
1244 }
1245 }
1246 }
1247 }
1248
1249 if (need_retap) {
1250 scheduleRetap(true);
1251 }
1252
1253 updateLegend();
1254 }
1255
on_todCheckBox_toggled(bool checked)1256 void IOGraphDialog::on_todCheckBox_toggled(bool checked)
1257 {
1258 double orig_start = start_time_;
1259 bool orig_auto = auto_axes_;
1260
1261 if (checked) {
1262 ui->ioPlot->xAxis->setTicker(datetime_ticker_);
1263 } else {
1264 ui->ioPlot->xAxis->setTicker(number_ticker_);
1265 }
1266 auto_axes_ = false;
1267 scheduleRecalc(true);
1268 auto_axes_ = orig_auto;
1269 getGraphInfo();
1270 ui->ioPlot->xAxis->moveRange(start_time_ - orig_start);
1271 mouseMoved(NULL); // Update hint
1272 }
1273
modelRowsReset()1274 void IOGraphDialog::modelRowsReset()
1275 {
1276 ui->deleteToolButton->setEnabled(false);
1277 ui->copyToolButton->setEnabled(false);
1278 ui->clearToolButton->setEnabled(uat_model_->rowCount() != 0);
1279 }
1280
on_graphUat_currentItemChanged(const QModelIndex & current,const QModelIndex &)1281 void IOGraphDialog::on_graphUat_currentItemChanged(const QModelIndex ¤t, const QModelIndex&)
1282 {
1283 if (current.isValid()) {
1284 ui->deleteToolButton->setEnabled(true);
1285 ui->copyToolButton->setEnabled(true);
1286 ui->clearToolButton->setEnabled(true);
1287 } else {
1288 ui->deleteToolButton->setEnabled(false);
1289 ui->copyToolButton->setEnabled(false);
1290 ui->clearToolButton->setEnabled(false);
1291 }
1292 }
1293
modelDataChanged(const QModelIndex & index)1294 void IOGraphDialog::modelDataChanged(const QModelIndex &index)
1295 {
1296 bool recalc = false;
1297
1298 switch (index.column())
1299 {
1300 case colYAxis:
1301 case colSMAPeriod:
1302 recalc = true;
1303 }
1304
1305 syncGraphSettings(index.row());
1306
1307 if (recalc) {
1308 scheduleRecalc(true);
1309 } else {
1310 scheduleReplot(true);
1311 }
1312 }
1313
on_resetButton_clicked()1314 void IOGraphDialog::on_resetButton_clicked()
1315 {
1316 resetAxes();
1317 }
1318
on_newToolButton_clicked()1319 void IOGraphDialog::on_newToolButton_clicked()
1320 {
1321 addGraph();
1322 }
1323
on_deleteToolButton_clicked()1324 void IOGraphDialog::on_deleteToolButton_clicked()
1325 {
1326 const QModelIndex ¤t = ui->graphUat->currentIndex();
1327 if (uat_model_ && current.isValid()) {
1328 delete ioGraphs_[current.row()];
1329 ioGraphs_.remove(current.row());
1330
1331 if (!uat_model_->removeRows(current.row(), 1)) {
1332 qDebug() << "Failed to remove row";
1333 }
1334 }
1335
1336 // We should probably be smarter about this.
1337 hint_err_.clear();
1338 mouseMoved(NULL);
1339 }
1340
on_copyToolButton_clicked()1341 void IOGraphDialog::on_copyToolButton_clicked()
1342 {
1343 addGraph(true);
1344 }
1345
on_clearToolButton_clicked()1346 void IOGraphDialog::on_clearToolButton_clicked()
1347 {
1348 if (uat_model_) {
1349 foreach(IOGraph* iog, ioGraphs_) {
1350 delete iog;
1351 }
1352 ioGraphs_.clear();
1353 uat_model_->clearAll();
1354 }
1355
1356 hint_err_.clear();
1357 mouseMoved(NULL);
1358 }
1359
on_dragRadioButton_toggled(bool checked)1360 void IOGraphDialog::on_dragRadioButton_toggled(bool checked)
1361 {
1362 if (checked) mouse_drags_ = true;
1363 ui->ioPlot->setInteractions(
1364 QCP::iRangeDrag |
1365 QCP::iRangeZoom
1366 );
1367 }
1368
on_zoomRadioButton_toggled(bool checked)1369 void IOGraphDialog::on_zoomRadioButton_toggled(bool checked)
1370 {
1371 if (checked) mouse_drags_ = false;
1372 ui->ioPlot->setInteractions(QCP::Interactions());
1373 }
1374
on_logCheckBox_toggled(bool checked)1375 void IOGraphDialog::on_logCheckBox_toggled(bool checked)
1376 {
1377 QCustomPlot *iop = ui->ioPlot;
1378
1379 iop->yAxis->setScaleType(checked ? QCPAxis::stLogarithmic : QCPAxis::stLinear);
1380 iop->replot();
1381 }
1382
on_automaticUpdateCheckBox_toggled(bool checked)1383 void IOGraphDialog::on_automaticUpdateCheckBox_toggled(bool checked)
1384 {
1385 prefs.gui_io_graph_automatic_update = checked ? TRUE : FALSE;
1386
1387 prefs_main_write();
1388
1389 if(prefs.gui_io_graph_automatic_update)
1390 {
1391 updateStatistics();
1392 }
1393 }
1394
on_actionReset_triggered()1395 void IOGraphDialog::on_actionReset_triggered()
1396 {
1397 on_resetButton_clicked();
1398 }
1399
on_actionZoomIn_triggered()1400 void IOGraphDialog::on_actionZoomIn_triggered()
1401 {
1402 zoomAxes(true);
1403 }
1404
on_actionZoomInX_triggered()1405 void IOGraphDialog::on_actionZoomInX_triggered()
1406 {
1407 zoomXAxis(true);
1408 }
1409
on_actionZoomInY_triggered()1410 void IOGraphDialog::on_actionZoomInY_triggered()
1411 {
1412 zoomYAxis(true);
1413 }
1414
on_actionZoomOut_triggered()1415 void IOGraphDialog::on_actionZoomOut_triggered()
1416 {
1417 zoomAxes(false);
1418 }
1419
on_actionZoomOutX_triggered()1420 void IOGraphDialog::on_actionZoomOutX_triggered()
1421 {
1422 zoomXAxis(false);
1423 }
1424
on_actionZoomOutY_triggered()1425 void IOGraphDialog::on_actionZoomOutY_triggered()
1426 {
1427 zoomYAxis(false);
1428 }
1429
on_actionMoveUp10_triggered()1430 void IOGraphDialog::on_actionMoveUp10_triggered()
1431 {
1432 panAxes(0, 10);
1433 }
1434
on_actionMoveLeft10_triggered()1435 void IOGraphDialog::on_actionMoveLeft10_triggered()
1436 {
1437 panAxes(-10, 0);
1438 }
1439
on_actionMoveRight10_triggered()1440 void IOGraphDialog::on_actionMoveRight10_triggered()
1441 {
1442 panAxes(10, 0);
1443 }
1444
on_actionMoveDown10_triggered()1445 void IOGraphDialog::on_actionMoveDown10_triggered()
1446 {
1447 panAxes(0, -10);
1448 }
1449
on_actionMoveUp1_triggered()1450 void IOGraphDialog::on_actionMoveUp1_triggered()
1451 {
1452 panAxes(0, 1);
1453 }
1454
on_actionMoveLeft1_triggered()1455 void IOGraphDialog::on_actionMoveLeft1_triggered()
1456 {
1457 panAxes(-1, 0);
1458 }
1459
on_actionMoveRight1_triggered()1460 void IOGraphDialog::on_actionMoveRight1_triggered()
1461 {
1462 panAxes(1, 0);
1463 }
1464
on_actionMoveDown1_triggered()1465 void IOGraphDialog::on_actionMoveDown1_triggered()
1466 {
1467 panAxes(0, -1);
1468 }
1469
on_actionGoToPacket_triggered()1470 void IOGraphDialog::on_actionGoToPacket_triggered()
1471 {
1472 if (tracer_->visible() && !file_closed_ && packet_num_ > 0) {
1473 emit goToPacket(packet_num_);
1474 }
1475 }
1476
on_actionDragZoom_triggered()1477 void IOGraphDialog::on_actionDragZoom_triggered()
1478 {
1479 if (mouse_drags_) {
1480 ui->zoomRadioButton->toggle();
1481 } else {
1482 ui->dragRadioButton->toggle();
1483 }
1484 }
1485
on_actionToggleTimeOrigin_triggered()1486 void IOGraphDialog::on_actionToggleTimeOrigin_triggered()
1487 {
1488
1489 }
1490
on_actionCrosshairs_triggered()1491 void IOGraphDialog::on_actionCrosshairs_triggered()
1492 {
1493
1494 }
1495
on_buttonBox_helpRequested()1496 void IOGraphDialog::on_buttonBox_helpRequested()
1497 {
1498 wsApp->helpTopicAction(HELP_STATS_IO_GRAPH_DIALOG);
1499 }
1500
1501 // XXX - We have similar code in tcp_stream_dialog and packet_diagram. Should this be a common routine?
on_buttonBox_accepted()1502 void IOGraphDialog::on_buttonBox_accepted()
1503 {
1504 QString file_name, extension;
1505 QDir path(wsApp->lastOpenDir());
1506 QString pdf_filter = tr("Portable Document Format (*.pdf)");
1507 QString png_filter = tr("Portable Network Graphics (*.png)");
1508 QString bmp_filter = tr("Windows Bitmap (*.bmp)");
1509 // Gaze upon my beautiful graph with lossy artifacts!
1510 QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)");
1511 QString csv_filter = tr("Comma Separated Values (*.csv)");
1512 QString filter = QString("%1;;%2;;%3;;%4;;%5")
1513 .arg(pdf_filter)
1514 .arg(png_filter)
1515 .arg(bmp_filter)
1516 .arg(jpeg_filter)
1517 .arg(csv_filter);
1518
1519 QString save_file = path.canonicalPath();
1520 if (!file_closed_) {
1521 save_file += QString("/%1").arg(cap_file_.fileBaseName());
1522 }
1523 file_name = WiresharkFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Graph As…")),
1524 save_file, filter, &extension);
1525
1526 if (file_name.length() > 0) {
1527 bool save_ok = false;
1528 if (extension.compare(pdf_filter) == 0) {
1529 save_ok = ui->ioPlot->savePdf(file_name);
1530 } else if (extension.compare(png_filter) == 0) {
1531 save_ok = ui->ioPlot->savePng(file_name);
1532 } else if (extension.compare(bmp_filter) == 0) {
1533 save_ok = ui->ioPlot->saveBmp(file_name);
1534 } else if (extension.compare(jpeg_filter) == 0) {
1535 save_ok = ui->ioPlot->saveJpg(file_name);
1536 } else if (extension.compare(csv_filter) == 0) {
1537 save_ok = saveCsv(file_name);
1538 }
1539 // else error dialog?
1540 if (save_ok) {
1541 wsApp->setLastOpenDirFromFilename(file_name);
1542 }
1543 }
1544 }
1545
makeCsv(QTextStream & stream) const1546 void IOGraphDialog::makeCsv(QTextStream &stream) const
1547 {
1548 QList<IOGraph *> activeGraphs;
1549
1550 int ui_interval = ui->intervalComboBox->itemData(ui->intervalComboBox->currentIndex()).toInt();
1551 int max_interval = 0;
1552
1553 stream << "\"Interval start\"";
1554 if (uat_model_ != NULL) {
1555 for (int row = 0; row < uat_model_->rowCount(); row++) {
1556 if (graphIsEnabled(row) && ioGraphs_[row] != NULL) {
1557 activeGraphs.append(ioGraphs_[row]);
1558 if (max_interval < ioGraphs_[row]->maxInterval()) {
1559 max_interval = ioGraphs_[row]->maxInterval();
1560 }
1561 QString name = ioGraphs_[row]->name().toUtf8();
1562 name = QString("\"%1\"").arg(name.replace("\"", "\"\"")); // RFC 4180
1563 stream << "," << name;
1564 }
1565 }
1566 }
1567
1568 stream << '\n';
1569
1570 for (int interval = 0; interval <= max_interval; interval++) {
1571 double interval_start = (double)interval * ((double)ui_interval / 1000.0);
1572 stream << interval_start;
1573 foreach (IOGraph *iog, activeGraphs) {
1574 double value = 0.0;
1575 if (interval <= iog->maxInterval()) {
1576 value = iog->getItemValue(interval, cap_file_.capFile());
1577 }
1578 stream << "," << value;
1579 }
1580 stream << '\n';
1581 }
1582 }
1583
copyAsCsvClicked()1584 void IOGraphDialog::copyAsCsvClicked()
1585 {
1586 QString csv;
1587 QTextStream stream(&csv, QIODevice::Text);
1588 makeCsv(stream);
1589 wsApp->clipboard()->setText(stream.readAll());
1590 }
1591
saveCsv(const QString & file_name) const1592 bool IOGraphDialog::saveCsv(const QString &file_name) const
1593 {
1594 QFile save_file(file_name);
1595 save_file.open(QFile::WriteOnly | QFile::Text);
1596 QTextStream out(&save_file);
1597 makeCsv(out);
1598
1599 return true;
1600 }
1601
1602 // IOGraph
1603
IOGraph(QCustomPlot * parent)1604 IOGraph::IOGraph(QCustomPlot *parent) :
1605 parent_(parent),
1606 visible_(false),
1607 graph_(NULL),
1608 bars_(NULL),
1609 val_units_(IOG_ITEM_UNIT_FIRST),
1610 hf_index_(-1),
1611 cur_idx_(-1)
1612 {
1613 Q_ASSERT(parent_ != NULL);
1614 graph_ = parent_->addGraph(parent_->xAxis, parent_->yAxis);
1615 Q_ASSERT(graph_ != NULL);
1616
1617 GString *error_string;
1618 error_string = register_tap_listener("frame",
1619 this,
1620 "",
1621 TL_REQUIRES_PROTO_TREE,
1622 tapReset,
1623 tapPacket,
1624 tapDraw,
1625 NULL);
1626 if (error_string) {
1627 // QMessageBox::critical(this, tr("%1 failed to register tap listener").arg(name_),
1628 // error_string->str);
1629 // config_err_ = error_string->str;
1630 g_string_free(error_string, TRUE);
1631 }
1632 }
1633
~IOGraph()1634 IOGraph::~IOGraph() {
1635 remove_tap_listener(this);
1636 if (graph_) {
1637 parent_->removeGraph(graph_);
1638 }
1639 if (bars_) {
1640 parent_->removePlottable(bars_);
1641 }
1642 }
1643
1644 // Construct a full filter string from the display filter and value unit / Y axis.
1645 // Check for errors and sets config_err_ if any are found.
setFilter(const QString & filter)1646 void IOGraph::setFilter(const QString &filter)
1647 {
1648 GString *error_string;
1649 QString full_filter(filter.trimmed());
1650
1651 config_err_.clear();
1652
1653 // Make sure we have a good display filter
1654 if (!full_filter.isEmpty()) {
1655 dfilter_t *dfilter;
1656 bool status;
1657 gchar *err_msg;
1658 status = dfilter_compile(full_filter.toUtf8().constData(), &dfilter, &err_msg);
1659 dfilter_free(dfilter);
1660 if (!status) {
1661 config_err_ = QString::fromUtf8(err_msg);
1662 g_free(err_msg);
1663 filter_ = full_filter;
1664 return;
1665 }
1666 }
1667
1668 // Check our value unit + field combo.
1669 error_string = check_field_unit(vu_field_.toUtf8().constData(), NULL, val_units_);
1670 if (error_string) {
1671 config_err_ = error_string->str;
1672 g_string_free(error_string, TRUE);
1673 return;
1674 }
1675
1676 // Make sure vu_field_ survives edt tree pruning by adding it to our filter
1677 // expression.
1678 if (val_units_ >= IOG_ITEM_UNIT_CALC_SUM && !vu_field_.isEmpty() && hf_index_ >= 0) {
1679 if (full_filter.isEmpty()) {
1680 full_filter = vu_field_;
1681 } else {
1682 full_filter += QString(" && (%1)").arg(vu_field_);
1683 }
1684 }
1685
1686 error_string = set_tap_dfilter(this, full_filter.toUtf8().constData());
1687 if (error_string) {
1688 config_err_ = error_string->str;
1689 g_string_free(error_string, TRUE);
1690 return;
1691 } else {
1692 if (filter_.compare(filter) && visible_) {
1693 emit requestRetap();
1694 }
1695 filter_ = filter;
1696 }
1697 }
1698
applyCurrentColor()1699 void IOGraph::applyCurrentColor()
1700 {
1701 if (graph_) {
1702 graph_->setPen(QPen(color_, graph_line_width_));
1703 } else if (bars_) {
1704 bars_->setPen(QPen(QBrush(ColorUtils::graphColor(0)), graph_line_width_)); // ...or omit it altogether?
1705 bars_->setBrush(color_);
1706 }
1707 }
1708
setVisible(bool visible)1709 void IOGraph::setVisible(bool visible)
1710 {
1711 bool old_visibility = visible_;
1712 visible_ = visible;
1713 if (graph_) {
1714 graph_->setVisible(visible_);
1715 }
1716 if (bars_) {
1717 bars_->setVisible(visible_);
1718 }
1719 if (old_visibility != visible_) {
1720 emit requestReplot();
1721 }
1722 }
1723
setName(const QString & name)1724 void IOGraph::setName(const QString &name)
1725 {
1726 name_ = name;
1727 if (graph_) {
1728 graph_->setName(name_);
1729 }
1730 if (bars_) {
1731 bars_->setName(name_);
1732 }
1733 }
1734
color()1735 QRgb IOGraph::color()
1736 {
1737 return color_.color().rgb();
1738 }
1739
setColor(const QRgb color)1740 void IOGraph::setColor(const QRgb color)
1741 {
1742 color_ = QBrush(color);
1743 applyCurrentColor();
1744 }
1745
setPlotStyle(int style)1746 void IOGraph::setPlotStyle(int style)
1747 {
1748 // Switch plottable if needed
1749 switch (style) {
1750 case psBar:
1751 case psStackedBar:
1752 if (graph_) {
1753 bars_ = new QCPBars(parent_->xAxis, parent_->yAxis);
1754 parent_->removeGraph(graph_);
1755 graph_ = NULL;
1756 }
1757 break;
1758 default:
1759 if (bars_) {
1760 graph_ = parent_->addGraph(parent_->xAxis, parent_->yAxis);
1761 parent_->removePlottable(bars_);
1762 bars_ = NULL;
1763 }
1764 break;
1765 }
1766 setValueUnits(val_units_);
1767
1768 if (graph_) {
1769 graph_->setLineStyle(QCPGraph::lsNone);
1770 graph_->setScatterStyle(QCPScatterStyle::ssNone);
1771 }
1772 switch (style) {
1773 case psLine:
1774 if (graph_) {
1775 graph_->setLineStyle(QCPGraph::lsLine);
1776 }
1777 break;
1778 case psImpulse:
1779 if (graph_) {
1780 graph_->setLineStyle(QCPGraph::lsImpulse);
1781 }
1782 break;
1783 case psDot:
1784 if (graph_) {
1785 graph_->setScatterStyle(QCPScatterStyle::ssDisc);
1786 }
1787 break;
1788 case psSquare:
1789 if (graph_) {
1790 graph_->setScatterStyle(QCPScatterStyle::ssSquare);
1791 }
1792 break;
1793 case psDiamond:
1794 if (graph_) {
1795 graph_->setScatterStyle(QCPScatterStyle::ssDiamond);
1796 }
1797 break;
1798 case psCross:
1799 if (graph_) {
1800 graph_->setScatterStyle(QCPScatterStyle::ssCross);
1801 }
1802 break;
1803 case psPlus:
1804 if (graph_) {
1805 graph_->setScatterStyle(QCPScatterStyle::ssPlus);
1806 }
1807 break;
1808 case psCircle:
1809 if (graph_) {
1810 graph_->setScatterStyle(QCPScatterStyle::ssCircle);
1811 }
1812 break;
1813
1814 case psBar:
1815 case IOGraph::psStackedBar:
1816 // Stacking set in scanGraphs
1817 bars_->moveBelow(NULL);
1818 break;
1819 }
1820
1821 setName(name_);
1822 applyCurrentColor();
1823 }
1824
valueUnitLabel()1825 const QString IOGraph::valueUnitLabel()
1826 {
1827 return val_to_str_const(val_units_, y_axis_vs, "Unknown");
1828 }
1829
setValueUnits(int val_units)1830 void IOGraph::setValueUnits(int val_units)
1831 {
1832 if (val_units >= IOG_ITEM_UNIT_FIRST && val_units <= IOG_ITEM_UNIT_LAST) {
1833 int old_val_units = val_units_;
1834 val_units_ = (io_graph_item_unit_t)val_units;
1835
1836 if (old_val_units != val_units) {
1837 setFilter(filter_); // Check config & prime vu field
1838 if (val_units < IOG_ITEM_UNIT_CALC_SUM) {
1839 emit requestRecalc();
1840 }
1841 }
1842 }
1843 }
1844
setValueUnitField(const QString & vu_field)1845 void IOGraph::setValueUnitField(const QString &vu_field)
1846 {
1847 int old_hf_index = hf_index_;
1848
1849 vu_field_ = vu_field.trimmed();
1850 hf_index_ = -1;
1851
1852 header_field_info *hfi = proto_registrar_get_byname(vu_field_.toUtf8().constData());
1853 if (hfi) {
1854 hf_index_ = hfi->id;
1855 }
1856
1857 if (old_hf_index != hf_index_) {
1858 setFilter(filter_); // Check config & prime vu field
1859 }
1860 }
1861
addToLegend()1862 bool IOGraph::addToLegend()
1863 {
1864 if (graph_) {
1865 return graph_->addToLegend();
1866 }
1867 if (bars_) {
1868 return bars_->addToLegend();
1869 }
1870 return false;
1871 }
1872
removeFromLegend()1873 bool IOGraph::removeFromLegend()
1874 {
1875 if (graph_) {
1876 return graph_->removeFromLegend();
1877 }
1878 if (bars_) {
1879 return bars_->removeFromLegend();
1880 }
1881 return false;
1882 }
1883
startOffset()1884 double IOGraph::startOffset()
1885 {
1886 if (graph_ && qSharedPointerDynamicCast<QCPAxisTickerDateTime>(graph_->keyAxis()->ticker()) && graph_->data()->size() > 0) {
1887 return graph_->data()->at(0)->key;
1888 }
1889 if (bars_ && qSharedPointerDynamicCast<QCPAxisTickerDateTime>(bars_->keyAxis()->ticker()) && bars_->data()->size() > 0) {
1890 return bars_->data()->at(0)->key;
1891 }
1892 return 0.0;
1893 }
1894
packetFromTime(double ts)1895 int IOGraph::packetFromTime(double ts)
1896 {
1897 int idx = ts * 1000 / interval_;
1898 if (idx >= 0 && idx < (int) cur_idx_) {
1899 switch (val_units_) {
1900 case IOG_ITEM_UNIT_CALC_MAX:
1901 case IOG_ITEM_UNIT_CALC_MIN:
1902 return items_[idx].extreme_frame_in_invl;
1903 default:
1904 return items_[idx].last_frame_in_invl;
1905 }
1906 }
1907 return -1;
1908 }
1909
clearAllData()1910 void IOGraph::clearAllData()
1911 {
1912 cur_idx_ = -1;
1913 reset_io_graph_items(items_, max_io_items_);
1914 if (graph_) {
1915 graph_->data()->clear();
1916 }
1917 if (bars_) {
1918 bars_->data()->clear();
1919 }
1920 start_time_ = 0.0;
1921 }
1922
recalcGraphData(capture_file * cap_file,bool enable_scaling)1923 void IOGraph::recalcGraphData(capture_file *cap_file, bool enable_scaling)
1924 {
1925 /* Moving average variables */
1926 unsigned int mavg_in_average_count = 0, mavg_left = 0, mavg_right = 0;
1927 unsigned int mavg_to_remove = 0, mavg_to_add = 0;
1928 double mavg_cumulated = 0;
1929 QCPAxis *x_axis = nullptr;
1930
1931 if (graph_) {
1932 graph_->data()->clear();
1933 x_axis = graph_->keyAxis();
1934 }
1935 if (bars_) {
1936 bars_->data()->clear();
1937 x_axis = bars_->keyAxis();
1938 }
1939
1940 if (moving_avg_period_ > 0 && cur_idx_ >= 0) {
1941 /* "Warm-up phase" - calculate average on some data not displayed;
1942 * just to make sure average on leftmost and rightmost displayed
1943 * values is as reliable as possible
1944 */
1945 guint64 warmup_interval = 0;
1946
1947 // for (; warmup_interval < first_interval; warmup_interval += interval_) {
1948 // mavg_cumulated += get_it_value(io, i, (int)warmup_interval/interval_);
1949 // mavg_in_average_count++;
1950 // mavg_left++;
1951 // }
1952 mavg_cumulated += getItemValue((int)warmup_interval/interval_, cap_file);
1953 mavg_in_average_count++;
1954 for (warmup_interval = interval_;
1955 ((warmup_interval < (0 + (moving_avg_period_ / 2) * (guint64)interval_)) &&
1956 (warmup_interval <= (cur_idx_ * (guint64)interval_)));
1957 warmup_interval += interval_) {
1958
1959 mavg_cumulated += getItemValue((int)warmup_interval / interval_, cap_file);
1960 mavg_in_average_count++;
1961 mavg_right++;
1962 }
1963 mavg_to_add = (unsigned int)warmup_interval;
1964 }
1965
1966 for (int i = 0; i <= cur_idx_; i++) {
1967 double ts = (double) i * interval_ / 1000;
1968 if (x_axis && qSharedPointerDynamicCast<QCPAxisTickerDateTime>(x_axis->ticker())) {
1969 ts += start_time_;
1970 }
1971 double val = getItemValue(i, cap_file);
1972
1973 if (moving_avg_period_ > 0) {
1974 if (i != 0) {
1975 mavg_left++;
1976 if (mavg_left > moving_avg_period_ / 2) {
1977 mavg_left--;
1978 mavg_in_average_count--;
1979 mavg_cumulated -= getItemValue((int)mavg_to_remove / interval_, cap_file);
1980 mavg_to_remove += interval_;
1981 }
1982 if (mavg_to_add <= (unsigned int) cur_idx_ * interval_) {
1983 mavg_in_average_count++;
1984 mavg_cumulated += getItemValue((int)mavg_to_add / interval_, cap_file);
1985 mavg_to_add += interval_;
1986 } else {
1987 mavg_right--;
1988 }
1989 }
1990 if (mavg_in_average_count > 0) {
1991 val = mavg_cumulated / mavg_in_average_count;
1992 }
1993 }
1994
1995 val *= y_axis_factor_;
1996
1997 if (hasItemToShow(i, val))
1998 {
1999 if (graph_) {
2000 graph_->addData(ts, val);
2001 }
2002 if (bars_) {
2003 bars_->addData(ts, val);
2004 }
2005 }
2006 // qDebug() << "=rgd i" << i << ts << val;
2007 }
2008
2009 // attempt to rescale time values to specific units
2010 if (enable_scaling) {
2011 calculateScaledValueUnit();
2012 } else {
2013 scaled_value_unit_.clear();
2014 }
2015
2016 emit requestReplot();
2017 }
2018
calculateScaledValueUnit()2019 void IOGraph::calculateScaledValueUnit()
2020 {
2021 // Reset unit and recalculate if needed.
2022 scaled_value_unit_.clear();
2023
2024 // If there is no field, scaling is not possible.
2025 if (hf_index_ < 0) {
2026 return;
2027 }
2028
2029 switch (val_units_) {
2030 case IOG_ITEM_UNIT_CALC_SUM:
2031 case IOG_ITEM_UNIT_CALC_MAX:
2032 case IOG_ITEM_UNIT_CALC_MIN:
2033 case IOG_ITEM_UNIT_CALC_AVERAGE:
2034 // Unit is not yet known, continue detecting it.
2035 break;
2036 default:
2037 // Unit is Packets, Bytes, Bits, etc.
2038 return;
2039 }
2040
2041 if (proto_registrar_get_ftype(hf_index_) == FT_RELATIVE_TIME) {
2042 // find maximum absolute value and scale accordingly
2043 double maxValue = 0;
2044 if (graph_) {
2045 maxValue = maxValueFromGraphData(*graph_->data());
2046 } else if (bars_) {
2047 maxValue = maxValueFromGraphData(*bars_->data());
2048 }
2049 // If the maximum value is zero, then either we have no data or
2050 // everything is zero, do not scale the unit in this case.
2051 if (maxValue == 0) {
2052 return;
2053 }
2054
2055 // XXX GTK+ always uses "ms" for log scale, should we do that too?
2056 int value_multiplier;
2057 if (maxValue >= 1.0) {
2058 scaled_value_unit_ = "s";
2059 value_multiplier = 1;
2060 } else if (maxValue >= 0.001) {
2061 scaled_value_unit_ = "ms";
2062 value_multiplier = 1000;
2063 } else {
2064 scaled_value_unit_ = "us";
2065 value_multiplier = 1000000;
2066 }
2067
2068 if (graph_) {
2069 scaleGraphData(*graph_->data(), value_multiplier);
2070 } else if (bars_) {
2071 scaleGraphData(*bars_->data(), value_multiplier);
2072 }
2073 }
2074 }
2075
2076 template<class DataMap>
maxValueFromGraphData(const DataMap & map)2077 double IOGraph::maxValueFromGraphData(const DataMap &map)
2078 {
2079 double maxValue = 0;
2080 typename DataMap::const_iterator it = map.constBegin();
2081 while (it != map.constEnd()) {
2082 maxValue = MAX(fabs((*it).value), maxValue);
2083 ++it;
2084 }
2085 return maxValue;
2086 }
2087
2088 template<class DataMap>
scaleGraphData(DataMap & map,int scalar)2089 void IOGraph::scaleGraphData(DataMap &map, int scalar)
2090 {
2091 if (scalar != 1) {
2092 typename DataMap::iterator it = map.begin();
2093 while (it != map.end()) {
2094 (*it).value *= scalar;
2095 ++it;
2096 }
2097 }
2098 }
2099
captureEvent(CaptureEvent e)2100 void IOGraph::captureEvent(CaptureEvent e)
2101 {
2102 if ((e.captureContext() == CaptureEvent::File) &&
2103 (e.eventType() == CaptureEvent::Closing))
2104 {
2105 remove_tap_listener(this);
2106 }
2107 }
2108
reloadValueUnitField()2109 void IOGraph::reloadValueUnitField()
2110 {
2111 if (vu_field_.length() > 0) {
2112 setValueUnitField(vu_field_);
2113 }
2114 }
2115
2116 // Check if a packet is available at the given interval (idx).
hasItemToShow(int idx,double value) const2117 bool IOGraph::hasItemToShow(int idx, double value) const
2118 {
2119 ws_assert(idx < max_io_items_);
2120
2121 bool result = false;
2122
2123 const io_graph_item_t *item = &items_[idx];
2124
2125 switch (val_units_) {
2126 case IOG_ITEM_UNIT_PACKETS:
2127 case IOG_ITEM_UNIT_BYTES:
2128 case IOG_ITEM_UNIT_BITS:
2129 case IOG_ITEM_UNIT_CALC_FRAMES:
2130 case IOG_ITEM_UNIT_CALC_FIELDS:
2131 if(value == 0.0 && (graph_ && graph_->scatterStyle().shape() != QCPScatterStyle::ssNone)) {
2132 result = false;
2133 }
2134 else {
2135 result = true;
2136 }
2137 break;
2138
2139 case IOG_ITEM_UNIT_CALC_SUM:
2140 case IOG_ITEM_UNIT_CALC_MAX:
2141 case IOG_ITEM_UNIT_CALC_MIN:
2142 case IOG_ITEM_UNIT_CALC_AVERAGE:
2143 case IOG_ITEM_UNIT_CALC_LOAD:
2144 if (item->fields) {
2145 result = true;
2146 }
2147 break;
2148
2149 default:
2150 result = true;
2151 break;
2152 }
2153
2154 return result;
2155 }
2156
setInterval(int interval)2157 void IOGraph::setInterval(int interval)
2158 {
2159 interval_ = interval;
2160 }
2161
2162 // Get the value at the given interval (idx) for the current value unit.
getItemValue(int idx,const capture_file * cap_file) const2163 double IOGraph::getItemValue(int idx, const capture_file *cap_file) const
2164 {
2165 ws_assert(idx < max_io_items_);
2166
2167 return get_io_graph_item(items_, val_units_, idx, hf_index_, cap_file, interval_, cur_idx_);
2168 }
2169
2170 // "tap_reset" callback for register_tap_listener
tapReset(void * iog_ptr)2171 void IOGraph::tapReset(void *iog_ptr)
2172 {
2173 IOGraph *iog = static_cast<IOGraph *>(iog_ptr);
2174 if (!iog) return;
2175
2176 // qDebug() << "=tapReset" << iog->name_;
2177 iog->clearAllData();
2178 }
2179
2180 // "tap_packet" callback for register_tap_listener
tapPacket(void * iog_ptr,packet_info * pinfo,epan_dissect_t * edt,const void *)2181 tap_packet_status IOGraph::tapPacket(void *iog_ptr, packet_info *pinfo, epan_dissect_t *edt, const void *)
2182 {
2183 IOGraph *iog = static_cast<IOGraph *>(iog_ptr);
2184 if (!pinfo || !iog) {
2185 return TAP_PACKET_DONT_REDRAW;
2186 }
2187
2188 int idx = get_io_graph_index(pinfo, iog->interval_);
2189 bool recalc = false;
2190
2191 /* some sanity checks */
2192 if ((idx < 0) || (idx >= max_io_items_)) {
2193 iog->cur_idx_ = max_io_items_ - 1;
2194 return TAP_PACKET_DONT_REDRAW;
2195 }
2196
2197 /* update num_items */
2198 if (idx > iog->cur_idx_) {
2199 iog->cur_idx_ = (guint32) idx;
2200 recalc = true;
2201 }
2202
2203 /* set start time */
2204 if (iog->start_time_ == 0.0) {
2205 nstime_t start_nstime;
2206 nstime_set_zero(&start_nstime);
2207 nstime_delta(&start_nstime, &pinfo->abs_ts, &pinfo->rel_ts);
2208 iog->start_time_ = nstime_to_sec(&start_nstime);
2209 }
2210
2211 epan_dissect_t *adv_edt = NULL;
2212 /* For ADVANCED mode we need to keep track of some more stuff than just frame and byte counts */
2213 if (iog->val_units_ >= IOG_ITEM_UNIT_CALC_SUM) {
2214 adv_edt = edt;
2215 }
2216
2217 if (!update_io_graph_item(iog->items_, idx, pinfo, adv_edt, iog->hf_index_, iog->val_units_, iog->interval_)) {
2218 return TAP_PACKET_DONT_REDRAW;
2219 }
2220
2221 // qDebug() << "=tapPacket" << iog->name_ << idx << iog->hf_index_ << iog->val_units_ << iog->num_items_;
2222
2223 if (recalc) {
2224 emit iog->requestRecalc();
2225 }
2226 return TAP_PACKET_REDRAW;
2227 }
2228
2229 // "tap_draw" callback for register_tap_listener
tapDraw(void * iog_ptr)2230 void IOGraph::tapDraw(void *iog_ptr)
2231 {
2232 IOGraph *iog = static_cast<IOGraph *>(iog_ptr);
2233 if (!iog) return;
2234 emit iog->requestRecalc();
2235
2236 if (iog->graph_) {
2237 // qDebug() << "=tapDraw g" << iog->name_ << iog->graph_->data()->keys().size();
2238 }
2239 if (iog->bars_) {
2240 // qDebug() << "=tapDraw b" << iog->name_ << iog->bars_->data()->keys().size();
2241 }
2242 }
2243
2244 // Stat command + args
2245
2246 static void
io_graph_init(const char *,void *)2247 io_graph_init(const char *, void*) {
2248 wsApp->emitStatCommandSignal("IOGraph", NULL, NULL);
2249 }
2250
2251 static stat_tap_ui io_stat_ui = {
2252 REGISTER_STAT_GROUP_GENERIC,
2253 NULL,
2254 "io,stat",
2255 io_graph_init,
2256 0,
2257 NULL
2258 };
2259
2260 extern "C" {
2261
2262 void register_tap_listener_qt_iostat(void);
2263
2264 void
register_tap_listener_qt_iostat(void)2265 register_tap_listener_qt_iostat(void)
2266 {
2267 register_stat_tap_ui(&io_stat_ui, NULL);
2268 }
2269
2270 }
2271