aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYordan Karadzhov (VMware) <y.karadz@gmail.com>2021-05-11 16:39:00 +0300
committerYordan Karadzhov (VMware) <y.karadz@gmail.com>2021-05-14 14:30:09 +0300
commit60b5c2f8bd56b9db968f960802727eceb07b534f (patch)
tree1b168e6cb5652711294a2d68803997fe0f0528e5
parent6555daee2da02dd3f0b13399ea99cd0459379c7a (diff)
downloadkernel-shark-60b5c2f8bd56b9db968f960802727eceb07b534f.tar.gz
kernel-shark: Add KVMCombo plugin
The plugin allows the user to visualize the execution flow between the host and guest virtual machines. It exploits the concept of "Combo Plots" that allows to have two normal graphs stacked together (on top of each other). The plugin uses a "combo" between the task in the host that emulates a virtual CPU and the corresponding CPU graph from the VM. The plugin draws additional graphical elements on top of this "combo", helping the user to intuitively interpret the data and see how the execution flow goes from host to guest and back. The core KVM-specific logic that implements the processing of the data and the visualization itself is implemented in: src/plugins/kvm_combo.h src/plugins/kvm_combo.c src/plugins/KVMCombo.cpp Some general purposes tools for plotting, that in the future can be used to implement similar plugins for other hypervisor are available in: plugins/VirtComboPlotTools.hpp The plugin also registers its own dialog, that allows the user to select the "combos" to be visualized. The widget of the dialog gets implemented in: src/plugins/KVMComboDialog.hpp src/plugins/KVMComboDialog.cpp Link: https://lore.kernel.org/linux-trace-devel/20210511133900.287636-1-y.karadz@gmail.com Signed-off-by: Yordan Karadzhov (VMware) <y.karadz@gmail.com>
-rw-r--r--src/libkshark-tepdata.c1
-rw-r--r--src/plugins/CMakeLists.txt5
-rw-r--r--src/plugins/KVMCombo.cpp39
-rw-r--r--src/plugins/KVMComboDialog.cpp350
-rw-r--r--src/plugins/KVMComboDialog.hpp89
-rw-r--r--src/plugins/VirtComboPlotTools.hpp171
-rw-r--r--src/plugins/kvm_combo.c72
-rw-r--r--src/plugins/kvm_combo.h49
-rw-r--r--tests/libkshark-gui-tests.cpp1
9 files changed, 777 insertions, 0 deletions
diff --git a/src/libkshark-tepdata.c b/src/libkshark-tepdata.c
index f7cd0838..bc5babb2 100644
--- a/src/libkshark-tepdata.c
+++ b/src/libkshark-tepdata.c
@@ -1210,6 +1210,7 @@ static void kshark_tep_init_methods(struct kshark_generic_stream_interface *inte
const char *tep_plugin_names[] = {
"sched_events",
"missed_events",
+ "kvm_combo",
};
/**
diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt
index 2906dd4f..3e170faa 100644
--- a/src/plugins/CMakeLists.txt
+++ b/src/plugins/CMakeLists.txt
@@ -54,6 +54,11 @@ if (Qt5Widgets_FOUND AND TT_FONT_FILE)
SOURCE latency_plot.c LatencyPlot.cpp LatencyPlotDialog.cpp)
list(APPEND PLUGIN_LIST "latency_plot")
+ BUILD_GUI_PLUGIN(NAME kvm_combo
+ MOC KVMComboDialog.hpp
+ SOURCE kvm_combo.c KVMCombo.cpp KVMComboDialog.cpp)
+ list(APPEND PLUGIN_LIST "kvm_combo")
+
endif (Qt5Widgets_FOUND AND TT_FONT_FILE)
BUILD_PLUGIN(NAME missed_events
diff --git a/src/plugins/KVMCombo.cpp b/src/plugins/KVMCombo.cpp
new file mode 100644
index 00000000..c7e89d83
--- /dev/null
+++ b/src/plugins/KVMCombo.cpp
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: LGPL-2.1
+
+/*
+ * Copyright (C) 2019 VMware Inc, Yordan Karadzhov <ykaradzhov@vmware.com>
+ */
+
+/**
+ * @file KVMCombo.cpp
+ * @brief Plugin for visualization of KVM events.
+ */
+
+// KernelShark
+#include "plugins/kvm_combo.h"
+#include "VirtComboPlotTools.hpp"
+#include "KsPlugins.hpp"
+
+/**
+ * @brief Plugin's draw function.
+ *
+ * @param argv_c: A C pointer to be converted to KsCppArgV (C++ struct).
+ * @param sdHost: Data stream identifier of the Host.
+ * @param pidHost: Process Id of the virtual CPU process in the Host.
+ * @param draw_action: Draw action identifier.
+ */
+__hidden void draw_kvm_combos(kshark_cpp_argv *argv_c,
+ int sdHost, int pidHost,
+ int draw_action)
+{
+ plugin_kvm_context *plugin_ctx = __get_context(sdHost);
+ if (!plugin_ctx)
+ return;
+
+ drawVirtCombos(argv_c,
+ sdHost,
+ pidHost,
+ plugin_ctx->vm_entry_id,
+ plugin_ctx->vm_exit_id,
+ draw_action);
+}
diff --git a/src/plugins/KVMComboDialog.cpp b/src/plugins/KVMComboDialog.cpp
new file mode 100644
index 00000000..b3ec8460
--- /dev/null
+++ b/src/plugins/KVMComboDialog.cpp
@@ -0,0 +1,350 @@
+// SPDX-License-Identifier: LGPL-2.1
+
+/*
+ * Copyright (C) 2021 VMware Inc, Yordan Karadzhov (VMware) <y.karadz@gmail.com>
+ */
+
+/**
+ * @file KVMComboDialog.cpp
+ * @brief Dialog class used by the KVMCombo plugin.
+ */
+
+// KernelShark
+#include "libkshark.h"
+#include "libkshark-tepdata.h"
+#include "plugins/kvm_combo.h"
+#include "KsMainWindow.hpp"
+#include "KVMComboDialog.hpp"
+
+using namespace KsWidgetsLib;
+
+static KsComboPlotDialog *combo_dialog(nullptr);
+
+static QMetaObject::Connection combo_dialogConnection;
+
+/** The name of the menu item used to start the dialog of the plugin. */
+#define DIALOG_NAME "KVM Combo plots"
+
+static void showDialog(KsMainWindow *ks)
+{
+ kshark_context *kshark_ctx(nullptr);
+
+ if (!kshark_instance(&kshark_ctx))
+ return;
+
+ if (kshark_ctx->n_streams < 2) {
+ QString err("Data from one Host and at least one Guest is required.");
+ QMessageBox msgBox;
+ msgBox.critical(nullptr, "Error", err);
+
+ return;
+ }
+
+ combo_dialog->update();
+
+ if (!combo_dialogConnection) {
+ combo_dialogConnection =
+ QObject::connect(combo_dialog, &KsComboPlotDialog::apply,
+ ks->graphPtr(),&KsTraceGraph::comboReDraw);
+ }
+
+ combo_dialog->show();
+}
+
+/** Add the dialog of the plugin to the KernelShark menus. */
+__hidden void *plugin_kvm_add_menu(void *ks_ptr)
+{
+ KsMainWindow *ks = static_cast<KsMainWindow *>(ks_ptr);
+ QString menu("Plots/");
+ menu += DIALOG_NAME;
+ ks->addPluginMenu(menu, showDialog);
+
+ if (!combo_dialog)
+ combo_dialog = new KsComboPlotDialog();
+
+ combo_dialog->_gui_ptr = ks;
+
+ return combo_dialog;
+}
+
+/**
+ * @brief Create KsCPUCheckBoxWidget.
+ *
+ * @param parent: The parent of this widget.
+ */
+KsVCPUCheckBoxWidget::KsVCPUCheckBoxWidget(QWidget *parent)
+: KsCheckBoxTreeWidget(0, "vCPUs", parent)
+{
+ int height(FONT_HEIGHT * 1.5);
+ QString style;
+
+ style = QString("QTreeView::item { height: %1 ;}").arg(height);
+ _tree.setStyleSheet(style);
+
+ _initTree();
+}
+
+/**
+ * Update the widget according to the mapping between host processes and guest
+ * virtual CPUs.
+ */
+void KsVCPUCheckBoxWidget::update(int guestId,
+ kshark_host_guest_map *gMap, int gMapCount)
+{
+ KsPlot::ColorTable colTable;
+ QColor color;
+ int j;
+
+ for (j = 0; j < gMapCount; j++)
+ if (gMap[j].guest_id == guestId)
+ break;
+ if (j == gMapCount)
+ return;
+
+ _tree.clear();
+ _id.resize(gMap[j].vcpu_count);
+ _cb.resize(gMap[j].vcpu_count);
+ colTable = KsPlot::CPUColorTable();
+
+ for (int i = 0; i < gMap[j].vcpu_count; ++i) {
+ QString strCPU = QLatin1String("vCPU ") + QString::number(i);
+ strCPU += (QLatin1String("\t<") + QLatin1String(gMap[j].guest_name) + QLatin1Char('>'));
+
+ QTreeWidgetItem *cpuItem = new QTreeWidgetItem;
+ cpuItem->setText(0, " ");
+ cpuItem->setText(1, strCPU);
+ cpuItem->setCheckState(0, Qt::Checked);
+ color << colTable[i];
+ cpuItem->setBackground(0, color);
+ _tree.addTopLevelItem(cpuItem);
+ _id[i] = i;
+ _cb[i] = cpuItem;
+ }
+
+ _adjustSize();
+ setDefault(false);
+}
+
+//! @cond Doxygen_Suppress
+
+#define LABEL_WIDTH (FONT_WIDTH * 50)
+
+//! @endcond
+
+/** Create default KsComboPlotDialog. */
+KsComboPlotDialog::KsComboPlotDialog(QWidget *parent)
+: QDialog(parent),
+ _vcpuTree(this),
+ _hostLabel("Host:", this),
+ _hostFileLabel("", this),
+ _guestLabel("Guest:", this),
+ _guestStreamComboBox(this),
+ _applyButton("Apply", this),
+ _cancelButton("Cancel", this),
+ _currentGuestStream(0)
+{
+ kshark_context *kshark_ctx(nullptr);
+ int buttonWidth;
+
+ auto lamAddLine = [&] {
+ QFrame* line = new QFrame();
+
+ line->setFrameShape(QFrame::HLine);
+ line->setFrameShadow(QFrame::Sunken);
+ _topLayout.addWidget(line);
+ };
+
+ setWindowTitle(DIALOG_NAME);
+
+ if (!kshark_instance(&kshark_ctx))
+ return;
+
+ _guestStreamComboBox.setMaximumWidth(LABEL_WIDTH);
+
+ _streamMenuLayout.addWidget(&_hostLabel, 0, 0);
+ _streamMenuLayout.addWidget(&_hostFileLabel, 0, 1);
+ _streamMenuLayout.addWidget(&_guestLabel, 1, 0);
+ _streamMenuLayout.addWidget(&_guestStreamComboBox, 1, 1);
+
+ _topLayout.addLayout(&_streamMenuLayout);
+
+ lamAddLine();
+
+ _topLayout.addWidget(&_vcpuTree);
+
+ lamAddLine();
+
+ buttonWidth = STRING_WIDTH("--Cancel--");
+ _applyButton.setFixedWidth(buttonWidth);
+ _cancelButton.setFixedWidth(buttonWidth);
+
+ _buttonLayout.addWidget(&_applyButton);
+ _applyButton.setAutoDefault(false);
+
+ _buttonLayout.addWidget(&_cancelButton);
+ _cancelButton.setAutoDefault(false);
+
+ _buttonLayout.setAlignment(Qt::AlignLeft);
+ _topLayout.addLayout(&_buttonLayout);
+
+ connect(&_applyButton, &QPushButton::pressed,
+ this, &QWidget::close);
+
+ connect(&_cancelButton, &QPushButton::pressed,
+ this, &QWidget::close);
+
+ /*
+ * Using the old Signal-Slot syntax because QComboBox::currentIndexChanged
+ * has overloads.
+ */
+ connect(&_guestStreamComboBox, SIGNAL(currentIndexChanged(const QString &)),
+ this, SLOT(_guestStreamChanged(const QString &)));
+
+ setLayout(&_topLayout);
+
+ _guestMapCount = 0;
+ _guestMap = nullptr;
+}
+
+KsComboPlotDialog::~KsComboPlotDialog()
+{
+ kshark_tracecmd_free_hostguest_map(_guestMap, _guestMapCount);
+}
+
+/** Update the Plugin dialog. */
+void KsComboPlotDialog::update()
+{
+ kshark_context *kshark_ctx(nullptr);
+ KsPlot::ColorTable colTable;
+ QString streamName;
+ QColor color;
+ int ret, sd, i;
+
+ if (!kshark_instance(&kshark_ctx))
+ return;
+
+ kshark_tracecmd_free_hostguest_map(_guestMap, _guestMapCount);
+ _guestMap = nullptr;
+ _guestMapCount = 0;
+ ret = kshark_tracecmd_get_hostguest_mapping(&_guestMap);
+ if (ret <= 0) {
+ QString err("Cannot find host / guest tracing into the loaded streams");
+ QMessageBox msgBox;
+ msgBox.critical(nullptr, "Error", err);
+ return;
+ } else {
+ _guestMapCount = ret;
+ }
+
+ streamName = KsUtils::streamDescription(kshark_ctx->stream[_guestMap[0].host_id]);
+ KsUtils::setElidedText(&_hostFileLabel, streamName, Qt::ElideLeft, LABEL_WIDTH);
+
+ _guestStreamComboBox.clear();
+ colTable = KsPlot::streamColorTable();
+ for (i = 0; i < _guestMapCount; i++) {
+ sd = _guestMap[i].guest_id;
+ if (sd >= kshark_ctx->n_streams)
+ continue;
+
+ streamName = KsUtils::streamDescription(kshark_ctx->stream[sd]);
+ _guestStreamComboBox.addItem(streamName, sd);
+ color << colTable[sd];
+ _guestStreamComboBox.setItemData(i, QBrush(color),
+ Qt::BackgroundRole);
+ }
+
+ if (!_applyButtonConnection) {
+ _applyButtonConnection =
+ connect(&_applyButton, &QPushButton::pressed,
+ this, &KsComboPlotDialog::_applyPress);
+ }
+
+ sd = _guestStreamComboBox.currentData().toInt();
+ _setCurrentPlots(sd);
+}
+
+int KsComboPlotDialog::_findGuestPlots(int sdGuest)
+{
+ for (int i = 0; i < _guestMapCount; i++)
+ if (_guestMap[i].guest_id == sdGuest)
+ return i;
+ return -1;
+}
+
+QVector<KsComboPlot> KsComboPlotDialog::_streamCombos(int sdGuest)
+{
+ QVector<int> cbVec = _vcpuTree.getCheckedIds();
+ int j = _findGuestPlots(sdGuest);
+ QVector <KsComboPlot> plots;
+ KsComboPlot combo(2);
+
+ if (j < 0)
+ return {};
+
+ for (auto const &i: cbVec) {
+ if (i >= _guestMap[j].vcpu_count)
+ continue;
+
+ combo[0]._streamId = _guestMap[j].guest_id;
+ combo[0]._id = i;
+ combo[0]._type = KSHARK_CPU_DRAW |
+ KSHARK_GUEST_DRAW;
+
+ combo[1]._streamId = _guestMap[j].host_id;
+ combo[1]._id = _guestMap[j].cpu_pid[i];
+ combo[1]._type = KSHARK_TASK_DRAW |
+ KSHARK_HOST_DRAW;
+
+ plots.append(combo);
+ }
+
+ return plots;
+}
+
+void KsComboPlotDialog::_applyPress()
+{
+ int guestId = _guestStreamComboBox.currentData().toInt();
+ QVector<int> allCombosVec;
+ int nPlots(0);
+
+ _plotMap[guestId] = _streamCombos(guestId);
+ for (auto const &stream: _plotMap)
+ for (auto const &combo: stream) {
+ allCombosVec.append(2);
+ combo[0] >> allCombosVec;
+ combo[1] >> allCombosVec;
+ ++nPlots;
+ }
+
+ emit apply(nPlots, allCombosVec);
+}
+
+void KsComboPlotDialog::_setCurrentPlots(int sdGuest)
+{
+ QVector<KsComboPlot> currentCombos =_plotMap[sdGuest];
+ int i = _findGuestPlots(sdGuest);
+ if (i < 0 || _guestMap[i].vcpu_count <= 0)
+ return;
+
+ QVector<int> vcpuCBs(_guestMap[i].vcpu_count, 0);
+ for(auto const &p: currentCombos)
+ vcpuCBs[p[0]._id] = 1;
+
+ _vcpuTree.set(vcpuCBs);
+}
+
+void KsComboPlotDialog::_guestStreamChanged(const QString &sdStr)
+{
+ if (sdStr.isEmpty())
+ return;
+
+ int newGuestId = _guestStreamComboBox.currentData().toInt();
+ QVector<int> vcpuCBs(_guestMapCount, 0);
+
+ _plotMap[_currentGuestStream] = _streamCombos(_currentGuestStream);
+
+ _vcpuTree.update(newGuestId, _guestMap, _guestMapCount);
+ _setCurrentPlots(newGuestId);
+
+ _currentGuestStream = newGuestId;
+}
diff --git a/src/plugins/KVMComboDialog.hpp b/src/plugins/KVMComboDialog.hpp
new file mode 100644
index 00000000..52e7e92e
--- /dev/null
+++ b/src/plugins/KVMComboDialog.hpp
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: LGPL-2.1 */
+
+/*
+ * Copyright (C) 2019 VMware Inc, Yordan Karadzhov <ykaradzhov@vmware.com>
+ */
+
+/**
+ * @file KVMComboDialog.hpp
+ * @brief Plugin for visualization of KVM events.
+ */
+
+#ifndef _KS_COMBO_DIALOG_H
+#define _KS_COMBO_DIALOG_H
+
+#include "KsMainWindow.hpp"
+#include "KsWidgetsLib.hpp"
+
+/**
+ * The KsVCPUCheckBoxWidget class provides a widget for selecting CPU plots to
+ * show.
+ */
+struct KsVCPUCheckBoxWidget : public KsWidgetsLib::KsCheckBoxTreeWidget
+{
+ explicit KsVCPUCheckBoxWidget(QWidget *parent = nullptr);
+
+ void update(int GuestId,
+ struct kshark_host_guest_map *gMap, int gMapCount);
+};
+
+/**
+ * The KsComboPlotDialog class provides a widget for selecting Combo plots to
+ * show.
+ */
+class KsComboPlotDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ explicit KsComboPlotDialog(QWidget *parent = nullptr);
+
+ ~KsComboPlotDialog();
+
+ void update();
+
+ /** KernelShark GUI (main window) object. */
+ KsMainWindow *_gui_ptr;
+
+signals:
+ /** Signal emitted when the "Apply" button is pressed. */
+ void apply(int sd, QVector<int>);
+
+private:
+ int _guestMapCount;
+
+ struct kshark_host_guest_map *_guestMap;
+
+ KsVCPUCheckBoxWidget _vcpuTree;
+
+ QVBoxLayout _topLayout;
+
+ QGridLayout _streamMenuLayout;
+
+ QHBoxLayout _buttonLayout;
+
+ QLabel _hostLabel, _hostFileLabel, _guestLabel;
+
+ QComboBox _guestStreamComboBox;
+
+ QPushButton _applyButton, _cancelButton;
+
+ QMetaObject::Connection _applyButtonConnection;
+
+ QMap<int, QVector<KsComboPlot>> _plotMap;
+
+ int _currentGuestStream;
+
+ int _findGuestPlots(int sdGuest);
+
+ QVector<KsComboPlot> _streamCombos(int sdGuest);
+
+ void _setCurrentPlots(int guestSd);
+
+ void _applyPress();
+
+private slots:
+
+ void _guestStreamChanged(const QString&);
+};
+
+#endif
diff --git a/src/plugins/VirtComboPlotTools.hpp b/src/plugins/VirtComboPlotTools.hpp
new file mode 100644
index 00000000..4daea797
--- /dev/null
+++ b/src/plugins/VirtComboPlotTools.hpp
@@ -0,0 +1,171 @@
+/* SPDX-License-Identifier: LGPL-2.1 */
+
+/*
+ * Copyright (C) 2019 VMware Inc, Yordan Karadzhov <y.karadz@gmail.com>
+ */
+
+/**
+ * @file VirtComboPlotTools.hpp
+ * @brief Tools for plotting Virt Combs.
+ */
+
+// C++
+#include <iostream>
+
+// KernelShark
+#include "KsPlugins.hpp"
+#include "KsPlotTools.hpp"
+
+static void drawVirt(kshark_trace_histo *histo,
+ KsPlot::Graph *hostGraph,
+ int sdHost, int pidHost,
+ int vcpuEntryId, int vcpuExitId,
+ KsPlot::PlotObjList *shapes)
+{
+ int guestBaseY = hostGraph->bin(0)._base.y() - hostGraph->height();
+ int gapHeight = hostGraph->height() * .3;
+ KsPlot::VirtBridge *bridge = new KsPlot::VirtBridge();
+ KsPlot::VirtGap *gap = new KsPlot::VirtGap(gapHeight);
+ const kshark_entry *entry, *exit;
+ ssize_t indexEntry, indexExit;
+ int values[2] = {-1, pidHost};
+
+ bridge->_size = 2;
+ bridge->_visible = false;
+ bridge->setEntryHost(hostGraph->bin(0)._base.x(), guestBaseY);
+ bridge->setEntryGuest(hostGraph->bin(0)._base.x(), guestBaseY);
+
+ gap->_size = 2;
+ gap->_visible = false;
+ gap->_exitPoint = KsPlot::Point(hostGraph->bin(0)._base.x(),
+ guestBaseY);
+
+ auto lamStartBridg = [&] (int bin) {
+ if (!bridge)
+ bridge = new KsPlot::VirtBridge();
+
+ bridge->setEntryHost(hostGraph->bin(bin)._base.x(),
+ hostGraph->bin(bin)._base.y());
+
+ bridge->setEntryGuest(hostGraph->bin(bin)._base.x(),
+ guestBaseY);
+
+ bridge->_color = hostGraph->bin(bin)._color;
+ };
+
+ auto lamCloseBridg = [&] (int bin) {
+ if (!bridge)
+ return;
+
+ bridge->setExitGuest(hostGraph->bin(bin)._base.x(),
+ guestBaseY);
+
+ bridge->setExitHost(hostGraph->bin(bin)._base.x(),
+ hostGraph->bin(bin)._base.y());
+
+ bridge->_color = hostGraph->bin(bin)._color;
+ bridge->_visible = true;
+ bridge->_size = -1; // Default size
+
+ shapes->push_front(bridge);
+ bridge = nullptr;
+ };
+
+ auto lamStartGap = [&] (int bin) {
+ if (!gap)
+ gap = new KsPlot::VirtGap(gapHeight);
+
+ gap->_exitPoint =
+ KsPlot::Point(hostGraph->bin(bin)._base.x(),
+ guestBaseY);
+ };
+
+ auto lamCloseGap = [&] (int bin) {
+ if (!gap)
+ return;
+
+ gap->_entryPoint =
+ KsPlot::Point(hostGraph->bin(bin)._base.x(),
+ guestBaseY);
+
+ gap->_visible = true;
+ gap->_size = -1; // Default size
+
+ shapes->push_front(gap);
+ gap = nullptr;
+ };
+
+ for (int bin = 0; bin < histo->n_bins; ++bin) {
+ values[0] = vcpuEntryId;
+ entry = ksmodel_get_entry_back(histo, bin, true,
+ kshark_match_event_and_pid,
+ sdHost, values,
+ nullptr, &indexEntry);
+
+ values[0] = vcpuExitId;
+ exit = ksmodel_get_entry_back(histo, bin, true,
+ kshark_match_event_and_pid,
+ sdHost, values,
+ nullptr, &indexExit);
+
+ if (entry && !exit) {
+ lamStartBridg(bin);
+ lamCloseGap(bin);
+ }
+
+ if (exit && !entry) {
+ lamCloseBridg(bin);
+ lamStartGap(bin);
+ }
+
+ if (exit && entry) {
+ if (bridge && bridge->_visible)
+ lamCloseBridg(bin);
+
+ if (gap && gap->_visible)
+ lamCloseGap(bin);
+
+ if (indexEntry > indexExit) {
+ lamStartBridg(bin);
+ } else {
+ lamStartBridg(bin);
+ lamCloseBridg(bin);
+ lamStartGap(bin);
+ }
+ }
+ }
+
+ if (bridge && bridge->_visible) {
+ bridge->setExitGuest(hostGraph->bin(histo->n_bins - 1)._base.x(),
+ guestBaseY);
+
+ bridge->setExitHost(hostGraph->bin(histo->n_bins - 1)._base.x(),
+ guestBaseY);
+ bridge->_size = -1; // Default size
+
+ shapes->push_front(bridge);
+ }
+}
+
+static void drawVirtCombos(kshark_cpp_argv *argv_c,
+ int sdHost, int pidHost,
+ int entryId, int exitId,
+ int draw_action)
+{
+ KsCppArgV *argvCpp;
+
+ if (!(draw_action & KSHARK_HOST_DRAW) || pidHost == 0)
+ return;
+
+ argvCpp = KS_ARGV_TO_CPP(argv_c);
+ try {
+ drawVirt(argvCpp->_histo,
+ argvCpp->_graph,
+ sdHost, pidHost,
+ entryId,
+ exitId,
+ argvCpp->_shapes);
+ } catch (const std::exception &exc) {
+ std::cerr << "Exception in drawVirtCombos()\n" << exc.what();
+ }
+}
diff --git a/src/plugins/kvm_combo.c b/src/plugins/kvm_combo.c
new file mode 100644
index 00000000..87398e63
--- /dev/null
+++ b/src/plugins/kvm_combo.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: LGPL-2.1
+
+/*
+ * Copyright (C) 2019 VMware Inc, Yordan Karadzhov <ykaradzhov@vmware.com>
+ */
+
+/**
+ * @file kvm_combo.c
+ * @brief Plugin for visualization of KVM events.
+ */
+
+// C
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+// KernelShark
+#include "plugins/kvm_combo.h"
+#include "libkshark-plugin.h"
+#include "libkshark-tepdata.h"
+
+/** A general purpose macro is used to define plugin context. */
+KS_DEFINE_PLUGIN_CONTEXT(struct plugin_kvm_context, free);
+
+static bool plugin_kvm_init_context(struct kshark_data_stream *stream,
+ struct plugin_kvm_context *plugin_ctx)
+{
+ plugin_ctx->vm_entry_id = kshark_find_event_id(stream, "kvm/kvm_entry");
+ plugin_ctx->vm_exit_id = kshark_find_event_id(stream, "kvm/kvm_exit");
+ if (plugin_ctx->vm_entry_id < 0 ||
+ plugin_ctx->vm_exit_id < 0)
+ return false;
+
+ return true;
+}
+
+/** Load this plugin. */
+int KSHARK_PLOT_PLUGIN_INITIALIZER(struct kshark_data_stream *stream)
+{
+ struct plugin_kvm_context *plugin_ctx = __init(stream->stream_id);
+
+ if (!plugin_ctx || !plugin_kvm_init_context(stream, plugin_ctx)) {
+ __close(stream->stream_id);
+ return 0;
+ }
+
+ kshark_register_draw_handler(stream, draw_kvm_combos);
+
+ return 1;
+}
+
+/** Unload this plugin. */
+int KSHARK_PLOT_PLUGIN_DEINITIALIZER(struct kshark_data_stream *stream)
+{
+ struct plugin_kvm_context *plugin_ctx = __get_context(stream->stream_id);
+ int ret = 0;
+
+ if (plugin_ctx) {
+ kshark_unregister_draw_handler(stream, draw_kvm_combos);
+ 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_kvm_add_menu(gui_ptr);
+}
diff --git a/src/plugins/kvm_combo.h b/src/plugins/kvm_combo.h
new file mode 100644
index 00000000..86d0da93
--- /dev/null
+++ b/src/plugins/kvm_combo.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: LGPL-2.1 */
+
+/*
+ * Copyright (C) 2019 VMware Inc, Yordan Karadzhov <ykaradzov@vmware.com>
+ */
+
+/**
+ * @file kvm_combo.h
+ * @brief Plugin for visualization of KVM events.
+ */
+
+#ifndef _KS_PLUGIN_KVM_COMBO_H
+#define _KS_PLUGIN_KVM_COMBO_H
+
+// KernelShark
+#include "libkshark.h"
+#include "libkshark-plugin.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Structure representing a plugin-specific context. */
+struct plugin_kvm_context {
+ /** Input handle for the trace data file. */
+ struct tracecmd_input *handle;
+
+ /** Page event used to parse the page. */
+ struct tep_handle *pevent;
+
+ /** kvm_entry Id. */
+ int vm_entry_id;
+
+ /** kvm_exit Id. */
+ int vm_exit_id;
+};
+
+KS_DECLARE_PLUGIN_CONTEXT_METHODS(struct plugin_kvm_context)
+
+void draw_kvm_combos(struct kshark_cpp_argv *argv,
+ int sd, int pid, int draw_action);
+
+void *plugin_kvm_add_menu(void *ks_ptr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tests/libkshark-gui-tests.cpp b/tests/libkshark-gui-tests.cpp
index f96b3ddf..031e8369 100644
--- a/tests/libkshark-gui-tests.cpp
+++ b/tests/libkshark-gui-tests.cpp
@@ -149,6 +149,7 @@ BOOST_AUTO_TEST_CASE(KsUtils_getPluginList)
QStringList plugins{"sched_events",
"event_field_plot",
"latency_plot",
+ "kvm_combo",
"missed_events"
};