aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYordan Karadzhov (VMware) <y.karadz@gmail.com>2021-04-29 18:44:21 +0300
committerYordan Karadzhov (VMware) <y.karadz@gmail.com>2021-05-11 16:16:48 +0300
commit358d1ca58264f861f59ed981ec75c4224657a5d4 (patch)
treea32121e99e2e7cfb7ac71dee93a37ceca5847088
parent8486f90333cc12707c0ef44cc82baa109e2fb469 (diff)
downloadkernel-shark-358d1ca58264f861f59ed981ec75c4224657a5d4.tar.gz
kernel-shark: Add LatencyPlot plugin
The plugin allows the user to visualize the latency between two events under the condition that the values of given data fields in the two events are identical (for example having the same PID). The core logic that implements the processing of the data and the visualization itself is implemented in: src/plugins/latency_plot.h, src/plugins/latency_plot.c and src/plugins/LatencyPlot.cpp The plugin also registers its own dialog, that allows the user to select the events (and the matching field) to be visualized. The widget of the dialog gets implemented in: src/plugins/LatencyPlotDialog.hpp src/plugins/LatencyPlotDialog.cpp Link: https://lore.kernel.org/linux-trace-devel/20210429154421.255872-3-y.karadz@gmail.com Signed-off-by: Yordan Karadzhov (VMware) <y.karadz@gmail.com>
-rw-r--r--src/plugins/CMakeLists.txt5
-rw-r--r--src/plugins/LatencyPlot.cpp305
-rw-r--r--src/plugins/LatencyPlotDialog.cpp184
-rw-r--r--src/plugins/LatencyPlotDialog.hpp59
-rw-r--r--src/plugins/latency_plot.c168
-rw-r--r--src/plugins/latency_plot.h62
-rw-r--r--tests/libkshark-gui-tests.cpp1
7 files changed, 784 insertions, 0 deletions
diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt
index 333f0da2..2906dd4f 100644
--- a/src/plugins/CMakeLists.txt
+++ b/src/plugins/CMakeLists.txt
@@ -49,6 +49,11 @@ if (Qt5Widgets_FOUND AND TT_FONT_FILE)
SOURCE event_field_plot.c EventFieldDialog.cpp EventFieldPlot.cpp)
list(APPEND PLUGIN_LIST "event_field_plot")
+ BUILD_GUI_PLUGIN(NAME latency_plot
+ MOC LatencyPlotDialog.hpp
+ SOURCE latency_plot.c LatencyPlot.cpp LatencyPlotDialog.cpp)
+ list(APPEND PLUGIN_LIST "latency_plot")
+
endif (Qt5Widgets_FOUND AND TT_FONT_FILE)
BUILD_PLUGIN(NAME missed_events
diff --git a/src/plugins/LatencyPlot.cpp b/src/plugins/LatencyPlot.cpp
new file mode 100644
index 00000000..7dd0af02
--- /dev/null
+++ b/src/plugins/LatencyPlot.cpp
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: LGPL-2.1
+
+/*
+ * Copyright (C) 2020 VMware Inc, Yordan Karadzhov (VMware) <y.karadz@gmail.com>
+ */
+
+/**
+ * @file LatencyPlot.cpp
+ * @brief Plugin for visualizing the latency between two trace events.
+ */
+
+// C
+#include <math.h>
+
+// C++
+#include <unordered_map>
+#include <iostream>
+
+// KernelShark
+#include "plugins/latency_plot.h"
+#include "KsPlotTools.hpp"
+#include "KsPlugins.hpp"
+
+/** A pair of events defining the latency. */
+typedef std::pair<kshark_entry *, kshark_entry *> LatencyPair;
+
+/** Hash table of latency pairs. */
+typedef std::unordered_multimap<int, LatencyPair> LatencyHashTable;
+
+/** Hash table storing the latency pairs per CPU.*/
+LatencyHashTable latencyCPUMap;
+
+/** Hash table storing the latency pairs per Task.*/
+LatencyHashTable latencyTaskMap;
+
+/**
+ * Macro used to forward the arguments and construct the pair directly into
+ * a hash table without unnecessary copies (or moves).
+ */
+#define LATENCY_EMPLACE(map, key ,eA, eB) \
+ map.emplace(std::piecewise_construct, \
+ std::forward_as_tuple(key), \
+ std::forward_as_tuple(eA, eB)); \
+
+using namespace KsPlot;
+
+/*
+ * A second pass over the data is used to populate the hash tables of latency
+ * pairs.
+ */
+static void secondPass(plugin_latency_context *plugin_ctx)
+{
+ kshark_data_field_int64 **dataA = plugin_ctx->data[0]->data;
+ kshark_data_field_int64 **dataB = plugin_ctx->data[1]->data;
+
+ size_t nEvtAs = plugin_ctx->data[0]->size;
+ size_t nEvtBs = plugin_ctx->data[1]->size;
+ int64_t timeA, timeANext, valFieldA;
+ size_t iB(0);
+
+ /*
+ * The order of the events in the container is the same as in the raw
+ * data in the file. This means the data is not sorted in time.
+ */
+ kshark_data_container_sort(plugin_ctx->data[0]);
+ kshark_data_container_sort(plugin_ctx->data[1]);
+
+ latencyCPUMap.clear();
+ latencyTaskMap.clear();
+
+ for (size_t iA = 0; iA < nEvtAs; ++iA) {
+ timeA = dataA[iA]->entry->ts;
+ valFieldA = dataA[iA]->field;
+
+ /*
+ * Find the time of the next "A event" having the same field
+ * value.
+ */
+ timeANext = INT64_MAX;
+ for (size_t i = iA + 1; i <nEvtAs; ++i) {
+ if (dataA[i]->field == valFieldA) {
+ timeANext = dataA[i]->entry->ts;
+ break;
+ }
+ }
+
+ for (size_t i = iB; i < nEvtBs; ++i) {
+ if (dataB[i]->entry->ts < timeA) {
+ /*
+ * We only care about the "B evenys" that are
+ * after (in time) the current "A event".
+ * Skip these "B events", when you search to
+ * pair the next "A event".
+ */
+ ++iB;
+ continue;
+ }
+
+ if (dataB[i]->entry->ts > timeANext) {
+ /*
+ * We already bypassed in time the next
+ * "A event" having the same field value.
+ */
+ break;
+ }
+
+ if (dataB[i]->field == valFieldA) {
+ int64_t delta = dataB[i]->entry->ts - timeA;
+
+ if (delta > plugin_ctx->max_latency)
+ plugin_ctx->max_latency = delta;
+
+ /*
+ * Store this pair of events in the hash
+ * tables. Use the CPU Id as a key.
+ */
+ LATENCY_EMPLACE(latencyCPUMap,
+ dataB[i]->entry->cpu,
+ dataA[iA]->entry,
+ dataB[i]->entry)
+
+ /*
+ * Use the PID as a key.
+ */
+ LATENCY_EMPLACE(latencyTaskMap,
+ dataB[i]->entry->pid,
+ dataA[iA]->entry,
+ dataB[i]->entry)
+ break;
+ }
+ }
+ }
+}
+
+//! @cond Doxygen_Suppress
+
+#define ORANGE {255, 165, 0}
+
+//! @endcond
+
+static void liftBase(Point *point, Graph *graph)
+{
+ point->setY(point->y() - graph->height() * .8);
+};
+
+static Line *baseLine(Graph *graph)
+{
+ Point p0, p1;
+ Line *l;
+
+ p0 = graph->bin(0)._base;
+ liftBase(&p0, graph);
+ p1 = graph->bin(graph->size() - 1)._base;
+ liftBase(&p1, graph);
+
+ l = new Line(p0, p1);
+ l->_color = ORANGE;
+ return l;
+}
+
+/**
+ * This class represents the graphical element visualizing the latency between
+ * two trace events.
+ */
+class LatencyTick : public Line {
+public:
+ /** Constructor. */
+ LatencyTick(const Point &p0, const Point &p1, const LatencyPair &l)
+ : Line(p0, p1), _l(l) {
+ _color = ORANGE;
+ }
+
+ /**
+ * @brief Distance between the click and the shape. Used to decide if
+ * the double click action must be executed.
+ *
+ * @param x: X coordinate of the click.
+ * @param y: Y coordinate of the click.
+ *
+ * @returns If the click is inside the box, the distance is zero.
+ * Otherwise infinity.
+ */
+ double distance(int x, int y) const override
+ {
+ int dx, dy;
+
+ dx = pointX(0) - x;
+ dy = pointY(0) - y;
+
+ return sqrt(dx *dx + dy * dy);
+ }
+
+private:
+ LatencyTick() = delete;
+
+ LatencyPair _l;
+
+ /** On double click do. */
+ void _doubleClick() const override;
+
+};
+
+void LatencyTick::_doubleClick() const
+{
+ plugin_mark_entry(_l.first, 'A');
+ plugin_mark_entry(_l.second, 'B');
+}
+
+
+static LatencyTick *tick(Graph *graph, int bin, int height, const LatencyPair &l)
+{
+ Point p0, p1;
+
+ p0 = graph->bin(bin)._base;
+ liftBase(&p0, graph);
+ p1 = p0;
+ p1.setY(p1.y() - height);
+
+ return new LatencyTick(p0, p1, l);
+
+}
+
+/**
+ * @brief Plugin's draw function.
+ *
+ * @param argv_c: A C pointer to be converted to KsCppArgV (C++ struct).
+ * @param sd: Data stream identifier.
+ * @param val: Can be CPU Id or Process Id.
+ * @param draw_action: Draw action identifier.
+ */
+__hidden void draw_latency(struct kshark_cpp_argv *argv_c,
+ int sd, int val, int draw_action)
+{
+ plugin_latency_context *plugin_ctx = __get_context(sd);
+ struct kshark_context *kshark_ctx(nullptr);
+ kshark_data_stream *stream;
+ kshark_trace_histo *histo;
+ LatencyHashTable *hash;
+ KsCppArgV *argvCpp;
+ PlotObjList *shapes;
+ Graph *thisGraph;
+ int graphHeight;
+
+ if (!plugin_ctx)
+ return;
+
+ if (!plugin_ctx->second_pass_done) {
+ /* The second pass is not done yet. */
+ secondPass(plugin_ctx);
+ plugin_ctx->second_pass_done = true;
+ }
+
+ if (!kshark_instance(&kshark_ctx))
+ return;
+
+ stream = kshark_get_data_stream(kshark_ctx, sd);
+ if (!stream)
+ return;
+
+ /* Retrieve the arguments (C++ objects). */
+ argvCpp = KS_ARGV_TO_CPP(argv_c);
+ thisGraph = argvCpp->_graph;
+
+ if (thisGraph->size() == 0)
+ return;
+
+ graphHeight = thisGraph->height();
+ shapes = argvCpp->_shapes;
+ histo = argvCpp->_histo;
+
+ /* Start by drawing the base line of the Latency plot. */
+ shapes->push_front(baseLine(thisGraph));
+
+ auto lamScaledDelta = [=] (kshark_entry *eA, kshark_entry *eB) {
+ double height;
+
+ height = (eB->ts - eA->ts) / (double) plugin_ctx->max_latency;
+ height *= graphHeight * .6;
+ return height + 4;
+ };
+
+ auto lamPlotLat = [=] (auto p) {
+ kshark_entry *eA = p.second.first;
+ kshark_entry *eB = p.second.second;
+ int binB = ksmodel_get_bin(histo, eB);
+
+ if (binB >= 0)
+ shapes->push_front(tick(thisGraph,
+ binB,
+ lamScaledDelta(eA, eB),
+ p.second));
+ };
+
+ /*
+ * Use the latency hash tables to get all pairs that are relevant for
+ * this plot.
+ */
+ if (draw_action & KSHARK_CPU_DRAW)
+ hash = &latencyCPUMap;
+ else if (draw_action & KSHARK_TASK_DRAW)
+ hash = &latencyTaskMap;
+
+ auto range = hash->equal_range(val);
+ std::for_each(range.first, range.second, lamPlotLat);
+}
diff --git a/src/plugins/LatencyPlotDialog.cpp b/src/plugins/LatencyPlotDialog.cpp
new file mode 100644
index 00000000..1fe8c39f
--- /dev/null
+++ b/src/plugins/LatencyPlotDialog.cpp
@@ -0,0 +1,184 @@
+// SPDX-License-Identifier: LGPL-2.1
+
+/*
+ * Copyright (C) 2020 VMware Inc, Yordan Karadzhov (VMware) <y.karadz@gmail.com>
+ */
+
+/**
+ * @file EventFieldDialog.cpp
+ * @brief Dialog class used by the LatencyPlot plugin.
+ */
+
+// C++
+#include <iostream>
+#include <vector>
+
+// KernelShark
+#include "KsMainWindow.hpp"
+#include "LatencyPlotDialog.hpp"
+
+/** The name of the menu item used to start the dialog of the plugin. */
+#define DIALOG_NAME "Plot Latency"
+
+/** Create plugin dialog widget. */
+LatencyPlotDialog::LatencyPlotDialog(QWidget *parent)
+: QDialog(parent),
+ _evtALabel("\tEvent A", this),
+ _evtBLabel("\tEvent B", this),
+ _applyButton("Apply", this),
+ _resetButton("Reset", this),
+ _cancelButton("Cancel", this)
+{
+ setWindowTitle(DIALOG_NAME);
+
+ _fieldSelectLayout.addWidget(&_evtALabel, 0, 0);
+ _fieldSelectLayout.addWidget(&_evtBLabel, 0, 1);
+
+ _fieldSelectLayout.addWidget(&_efsWidgetA, 1, 0);
+ _fieldSelectLayout.addWidget(&_efsWidgetB, 1, 1);
+ _topLayout.addLayout(&_fieldSelectLayout);
+
+ _buttonLayout.addWidget(&_applyButton);
+ _applyButton.setAutoDefault(false);
+
+ _buttonLayout.addWidget(&_resetButton);
+ _resetButton.setAutoDefault(false);
+
+ _buttonLayout.addWidget(&_cancelButton);
+ _cancelButton.setAutoDefault(false);
+
+ _buttonLayout.setAlignment(Qt::AlignLeft);
+ _topLayout.addLayout(&_buttonLayout);
+
+ connect(&_applyButton, &QPushButton::pressed,
+ this, &LatencyPlotDialog::_apply);
+
+ connect(&_applyButton, &QPushButton::pressed,
+ this, &QWidget::close);
+
+ connect(&_resetButton, &QPushButton::pressed,
+ this, &LatencyPlotDialog::_reset);
+
+ connect(&_resetButton, &QPushButton::pressed,
+ this, &QWidget::close);
+
+ connect(&_cancelButton, &QPushButton::pressed,
+ this, &QWidget::close);
+
+ setLayout(&_topLayout);
+}
+
+/** Update the dialog, using the current settings of the plugin. */
+void LatencyPlotDialog::update()
+{
+ _efsWidgetA.setStreamCombo();
+ _efsWidgetB.setStreamCombo();
+}
+
+static LatencyPlotDialog *lp_dialog(nullptr);
+
+/**
+ * Use the Events and Field names selected by the user to update the plugin's
+ * context.
+ */
+__hidden void plugin_set_event_fields(struct plugin_latency_context *plugin_ctx)
+{
+ std::string buff;
+ char *name;
+ int ret;
+
+ plugin_ctx->event_name[0] = plugin_ctx->event_name[1] = NULL;
+
+ buff = lp_dialog->_efsWidgetA.eventName().toStdString();
+ ret = asprintf(&name, "%s", buff.c_str());
+ if (ret > 0)
+ plugin_ctx->event_name[0] = name;
+
+ buff = lp_dialog->_efsWidgetB.eventName().toStdString();
+ ret = asprintf(&name, "%s", buff.c_str());
+ if (ret > 0)
+ plugin_ctx->event_name[1] = name;
+
+ plugin_ctx->field_name[0] = plugin_ctx->field_name[1] = NULL;
+
+ buff = lp_dialog->_efsWidgetA.fieldName().toStdString();
+ ret = asprintf(&name, "%s", buff.c_str());
+ if (ret > 0)
+ plugin_ctx->field_name[0] = name;
+
+ buff = lp_dialog->_efsWidgetB.fieldName().toStdString();
+ ret = asprintf(&name, "%s", buff.c_str());
+ if (ret > 0)
+ plugin_ctx->field_name[1] = name;
+}
+
+/**
+ * @brief Mark an entry in the KernelShark GUI.
+ *
+ * @param e: The entry to be selected ith the marker.
+ * @param mark: The marker to be used (A or B).
+ */
+__hidden void plugin_mark_entry(const struct kshark_entry *e, char mark)
+{
+ DualMarkerState st = DualMarkerState::A;
+ if (mark == 'B')
+ st = DualMarkerState::B;
+
+ lp_dialog->_gui_ptr->markEntry(e, st);
+}
+
+void LatencyPlotDialog::_apply()
+{
+ auto work = KsWidgetsLib::KsDataWork::UpdatePlugins;
+ int sdA = lp_dialog->_efsWidgetA.streamId();
+ int sdB = lp_dialog->_efsWidgetB.streamId();
+
+ /*
+ * The plugin needs to process the data and this may take time
+ * on large datasets. Show a "Work In Process" warning.
+ */
+ _gui_ptr->wipPtr()->show(work);
+ _gui_ptr->registerPluginToStream("latency_plot", {sdA, sdB});
+ _gui_ptr->wipPtr()->hide(work);
+}
+
+void LatencyPlotDialog::_reset()
+{
+ auto work = KsWidgetsLib::KsDataWork::UpdatePlugins;
+ kshark_context *kshark_ctx(nullptr);
+ QVector<int> streamIds;
+
+ if (!kshark_instance(&kshark_ctx))
+ return;
+
+ streamIds = KsUtils::getStreamIdList(kshark_ctx);
+
+ /*
+ * The plugin needs to process the data and this may take time
+ * on large datasets. Show a "Work In Process" warning.
+ */
+ _gui_ptr->wipPtr()->show(work);
+ _gui_ptr->unregisterPluginFromStream("latency_plot", streamIds);
+ _gui_ptr->wipPtr()->hide(work);
+}
+
+static void showDialog(KsMainWindow *ks)
+{
+ lp_dialog->update();
+ lp_dialog->show();
+}
+
+/** Add the dialog of the plugin to the KernelShark menus. */
+__hidden void *plugin_latency_add_menu(void *ks_ptr)
+{
+ if (!lp_dialog) {
+ lp_dialog = new LatencyPlotDialog();
+ lp_dialog->_gui_ptr = static_cast<KsMainWindow *>(ks_ptr);
+ }
+
+ QString menu("Tools/");
+ menu += DIALOG_NAME;
+ lp_dialog->_gui_ptr->addPluginMenu(menu, showDialog);
+
+ return lp_dialog;
+}
diff --git a/src/plugins/LatencyPlotDialog.hpp b/src/plugins/LatencyPlotDialog.hpp
new file mode 100644
index 00000000..84514880
--- /dev/null
+++ b/src/plugins/LatencyPlotDialog.hpp
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: LGPL-2.1 */
+
+/*
+ * Copyright (C) 2020 VMware Inc, Yordan Karadzhov (VMware) <y.karadz@gmail.com>
+ */
+
+/**
+ * @file LatencyPlotDialog.hpp
+ * @brief Dialog class used by the LatencyPlot plugin.
+ */
+
+#ifndef _KS_EFP_DIALOG_H
+#define _KS_EFP_DIALOG_H
+
+// KernelShark
+#include "plugins/latency_plot.h"
+#include "KsWidgetsLib.hpp"
+
+class KsMainWindow;
+
+/**
+ * The LatencyPlotDialog class provides a widget for selecting Trace event field to
+ * be visualized.
+ */
+
+class LatencyPlotDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ explicit LatencyPlotDialog(QWidget *parent = nullptr);
+
+ void update();
+
+ /** Widget for selecting Treace event A. */
+ KsWidgetsLib::KsEventFieldSelectWidget _efsWidgetA;
+
+ /** Widget for selecting Treace event B. */
+ KsWidgetsLib::KsEventFieldSelectWidget _efsWidgetB;
+
+ /** KernelShark GUI (main window) object. */
+ KsMainWindow *_gui_ptr;
+
+private:
+ QVBoxLayout _topLayout;
+
+ QGridLayout _fieldSelectLayout;
+
+ QHBoxLayout _buttonLayout;
+
+ QLabel _evtALabel, _evtBLabel;
+
+ QPushButton _applyButton, _resetButton, _cancelButton;
+
+ void _apply();
+
+ void _reset();
+};
+
+#endif
diff --git a/src/plugins/latency_plot.c b/src/plugins/latency_plot.c
new file mode 100644
index 00000000..09b1eca0
--- /dev/null
+++ b/src/plugins/latency_plot.c
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: LGPL-2.1
+
+/*
+ * Copyright (C) 2020 VMware Inc, Yordan Karadzhov (VMware) <y.karadz@gmail.com>
+ */
+
+/**
+ * @file latency_plot.c
+ * @brief Plugin for visualizing the latency between two trace events.
+ */
+
+// C
+#ifndef _GNU_SOURCE
+/** Use GNU C Library. */
+#define _GNU_SOURCE
+#endif // _GNU_SOURCE
+
+#include <stdio.h>
+#include <assert.h>
+
+// KernelShark
+#include "plugins/latency_plot.h"
+
+static void latency_free_context(struct plugin_latency_context *plugin_ctx)
+{
+ if (!plugin_ctx)
+ return;
+
+ free(plugin_ctx->event_name[0]);
+ free(plugin_ctx->field_name[0]);
+
+ free(plugin_ctx->event_name[1]);
+ free(plugin_ctx->field_name[1]);
+
+ kshark_free_data_container(plugin_ctx->data[0]);
+ kshark_free_data_container(plugin_ctx->data[1]);
+}
+
+/** A general purpose macro is used to define plugin context. */
+KS_DEFINE_PLUGIN_CONTEXT(struct plugin_latency_context, latency_free_context);
+
+static bool plugin_latency_init_context(struct kshark_data_stream *stream,
+ struct plugin_latency_context *plugin_ctx)
+{
+ plugin_set_event_fields(plugin_ctx);
+
+ plugin_ctx->event_id[0] =
+ kshark_find_event_id(stream, plugin_ctx->event_name[0]);
+
+ if (plugin_ctx->event_id[0] < 0) {
+ fprintf(stderr, "Event %s not found in stream %s:%s\n",
+ plugin_ctx->event_name[0], stream->file, stream->name);
+ return false;
+ }
+
+ plugin_ctx->event_id[1] =
+ kshark_find_event_id(stream, plugin_ctx->event_name[1]);
+ if (plugin_ctx->event_id[1] < 0) {
+ fprintf(stderr, "Event %s not found in stream %s:%s\n",
+ plugin_ctx->event_name[1], stream->file, stream->name);
+ return false;
+ }
+
+ plugin_ctx->second_pass_done = false;
+ plugin_ctx->max_latency = INT64_MIN;
+
+ plugin_ctx->data[0] = kshark_init_data_container();
+ plugin_ctx->data[1] = kshark_init_data_container();
+ if (!plugin_ctx->data[0] || !plugin_ctx->data[1])
+ return false;
+
+ return true;
+}
+
+static void plugin_get_field(struct kshark_data_stream *stream, void *rec,
+ struct kshark_entry *entry,
+ char *field_name,
+ struct kshark_data_container *data)
+{
+ int64_t val;
+
+ kshark_read_record_field_int(stream, rec, field_name, &val);
+ kshark_data_container_append(data, entry, val);
+}
+
+static void plugin_get_field_a(struct kshark_data_stream *stream, void *rec,
+ struct kshark_entry *entry)
+{
+ struct plugin_latency_context *plugin_ctx;
+
+ plugin_ctx = __get_context(stream->stream_id);
+ if (!plugin_ctx)
+ return;
+
+ plugin_get_field(stream, rec, entry,
+ plugin_ctx->field_name[0],
+ plugin_ctx->data[0]);
+}
+
+static void plugin_get_field_b(struct kshark_data_stream *stream, void *rec,
+ struct kshark_entry *entry)
+{
+ struct plugin_latency_context *plugin_ctx;
+
+ plugin_ctx = __get_context(stream->stream_id);
+ if (!plugin_ctx)
+ return;
+
+ plugin_get_field(stream, rec, entry,
+ plugin_ctx->field_name[1],
+ plugin_ctx->data[1]);
+}
+
+/** Load this plugin. */
+int KSHARK_PLOT_PLUGIN_INITIALIZER(struct kshark_data_stream *stream)
+{
+ struct plugin_latency_context *plugin_ctx = __init(stream->stream_id);
+
+ if (!plugin_ctx || !plugin_latency_init_context(stream, plugin_ctx)) {
+ __close(stream->stream_id);
+ return 0;
+ }
+
+ /* Register Event handler to be executed during data loading. */
+ kshark_register_event_handler(stream,
+ plugin_ctx->event_id[0],
+ plugin_get_field_a);
+
+ kshark_register_event_handler(stream,
+ plugin_ctx->event_id[1],
+ plugin_get_field_b);
+
+ /* Register a drawing handler to plot on top of each Graph. */
+ kshark_register_draw_handler(stream, draw_latency);
+
+ return 1;
+}
+
+/** Unload this plugin. */
+int KSHARK_PLOT_PLUGIN_DEINITIALIZER(struct kshark_data_stream *stream)
+{
+ struct plugin_latency_context *plugin_ctx = __get_context(stream->stream_id);
+ int ret = 0;
+
+ if (plugin_ctx) {
+ kshark_unregister_event_handler(stream,
+ plugin_ctx->event_id[0],
+ plugin_get_field_a);
+
+ kshark_unregister_event_handler(stream,
+ plugin_ctx->event_id[1],
+ plugin_get_field_b);
+
+ kshark_unregister_draw_handler(stream, draw_latency);
+
+ ret = 1;
+ }
+
+ __close(stream->stream_id);
+
+ return ret;
+}
+
+/** Initialize the control interface of the plugin. */
+void *KSHARK_MENU_PLUGIN_INITIALIZER(void *gui_ptr)
+{
+ return plugin_latency_add_menu(gui_ptr);
+}
diff --git a/src/plugins/latency_plot.h b/src/plugins/latency_plot.h
new file mode 100644
index 00000000..16d78ad0
--- /dev/null
+++ b/src/plugins/latency_plot.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: LGPL-2.1 */
+
+/*
+ * Copyright (C) 2020 VMware Inc, Yordan Karadzhov (VMware) <y.karadz@gmail.com>
+ */
+
+/**
+ * @file latency_plot.h
+ * @brief Plugin for visualizing the latency between two trace events.
+ */
+
+#ifndef _KS_PLUGIN_LATENCY_PLOT_H
+#define _KS_PLUGIN_LATENCY_PLOT_H
+
+// KernelShark
+#include "libkshark.h"
+#include "libkshark-plugin.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Structure representing a plugin-specific context. */
+struct plugin_latency_context {
+ /** Trace event names. */
+ char *event_name[2];
+
+ /** Trace event identifiers. */
+ int event_id[2];
+
+ /** Event field names. */
+ char *field_name[2];
+
+ /** True if the second pass is already done. */
+ bool second_pass_done;
+
+ /**
+ * The maximum value of the latency between events A and B in this
+ * data sample.
+ */
+ int64_t max_latency;
+
+ /** Container objects to store the trace event field's data. */
+ struct kshark_data_container *data[2];
+};
+
+KS_DECLARE_PLUGIN_CONTEXT_METHODS(struct plugin_latency_context)
+
+void draw_latency(struct kshark_cpp_argv *argv_c,
+ int sd, int pid, int draw_action);
+
+void *plugin_latency_add_menu(void *gui_ptr);
+
+void plugin_set_event_fields(struct plugin_latency_context *plugin_ctx);
+
+void plugin_mark_entry(const struct kshark_entry *e, char mark);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tests/libkshark-gui-tests.cpp b/tests/libkshark-gui-tests.cpp
index a023c397..f96b3ddf 100644
--- a/tests/libkshark-gui-tests.cpp
+++ b/tests/libkshark-gui-tests.cpp
@@ -148,6 +148,7 @@ BOOST_AUTO_TEST_CASE(KsUtils_getPluginList)
{
QStringList plugins{"sched_events",
"event_field_plot",
+ "latency_plot",
"missed_events"
};