| // 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, &QComboBox::currentIndexChanged, |
| this, &KsComboPlotDialog::_guestStreamChanged); |
| |
| 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 it = _plotMap.cbegin(), end = _plotMap.cend(); it != end; ++it) { |
| for (auto const &combo: it.value()) { |
| 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(int) |
| { |
| QString sdStr = _guestStreamComboBox.currentText(); |
| if (sdStr.isEmpty()) |
| return; |
| |
| int newGuestId = _guestStreamComboBox.currentData().toInt(); |
| |
| _plotMap[_currentGuestStream] = _streamCombos(_currentGuestStream); |
| |
| _vcpuTree.update(newGuestId, _guestMap, _guestMapCount); |
| _setCurrentPlots(newGuestId); |
| |
| _currentGuestStream = newGuestId; |
| } |