forked from qt-creator/qt-creator
Perf: Move config widget setup closer to settingspage
Change-Id: Idda753dc07103dc0f6d0c3ff9759d0ff083ac2d9 Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -10,7 +10,6 @@ endif()
|
|||||||
|
|
||||||
set(PERFPROFILER_CPP_SOURCES
|
set(PERFPROFILER_CPP_SOURCES
|
||||||
perfconfigeventsmodel.cpp perfconfigeventsmodel.h
|
perfconfigeventsmodel.cpp perfconfigeventsmodel.h
|
||||||
perfconfigwidget.cpp perfconfigwidget.h
|
|
||||||
perfdatareader.cpp perfdatareader.h
|
perfdatareader.cpp perfdatareader.h
|
||||||
perfevent.h
|
perfevent.h
|
||||||
perfeventtype.h
|
perfeventtype.h
|
||||||
|
@@ -1,369 +0,0 @@
|
|||||||
// Copyright (C) 2018 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
|
|
||||||
#include "perfconfigeventsmodel.h"
|
|
||||||
#include "perfconfigwidget.h"
|
|
||||||
#include "perfprofilertr.h"
|
|
||||||
|
|
||||||
#include <coreplugin/messagebox.h>
|
|
||||||
|
|
||||||
#include <projectexplorer/devicesupport/idevice.h>
|
|
||||||
#include <projectexplorer/kit.h>
|
|
||||||
#include <projectexplorer/kitinformation.h>
|
|
||||||
#include <projectexplorer/target.h>
|
|
||||||
|
|
||||||
#include <utils/aspects.h>
|
|
||||||
#include <utils/layoutbuilder.h>
|
|
||||||
#include <utils/process.h>
|
|
||||||
#include <utils/qtcassert.h>
|
|
||||||
|
|
||||||
#include <QComboBox>
|
|
||||||
#include <QHeaderView>
|
|
||||||
#include <QMessageBox>
|
|
||||||
#include <QMetaEnum>
|
|
||||||
#include <QPushButton>
|
|
||||||
#include <QStyledItemDelegate>
|
|
||||||
#include <QTableView>
|
|
||||||
|
|
||||||
using namespace Utils;
|
|
||||||
|
|
||||||
namespace PerfProfiler {
|
|
||||||
namespace Internal {
|
|
||||||
|
|
||||||
class SettingsDelegate : public QStyledItemDelegate
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
SettingsDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {}
|
|
||||||
|
|
||||||
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
|
|
||||||
const QModelIndex &index) const override;
|
|
||||||
|
|
||||||
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
|
|
||||||
void setModelData(QWidget *editor, QAbstractItemModel *model,
|
|
||||||
const QModelIndex &index) const override;
|
|
||||||
|
|
||||||
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
|
|
||||||
const QModelIndex &index) const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
PerfConfigWidget::PerfConfigWidget(PerfSettings *settings, QWidget *parent)
|
|
||||||
: m_settings(settings)
|
|
||||||
{
|
|
||||||
setParent(parent);
|
|
||||||
|
|
||||||
eventsView = new QTableView(this);
|
|
||||||
eventsView->setMinimumSize(QSize(0, 300));
|
|
||||||
eventsView->setEditTriggers(QAbstractItemView::AllEditTriggers);
|
|
||||||
eventsView->setSelectionMode(QAbstractItemView::SingleSelection);
|
|
||||||
eventsView->setSelectionBehavior(QAbstractItemView::SelectRows);
|
|
||||||
eventsView->setModel(new PerfConfigEventsModel(m_settings, this));
|
|
||||||
eventsView->setItemDelegate(new SettingsDelegate(this));
|
|
||||||
eventsView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
|
||||||
|
|
||||||
useTracePointsButton = new QPushButton(this);
|
|
||||||
useTracePointsButton->setText(Tr::tr("Use Trace Points"));
|
|
||||||
useTracePointsButton->setVisible(false);
|
|
||||||
connect(useTracePointsButton, &QPushButton::pressed,
|
|
||||||
this, &PerfConfigWidget::readTracePoints);
|
|
||||||
|
|
||||||
addEventButton = new QPushButton(this);
|
|
||||||
addEventButton->setText(Tr::tr("Add Event"));
|
|
||||||
connect(addEventButton, &QPushButton::pressed, this, [this]() {
|
|
||||||
auto model = eventsView->model();
|
|
||||||
model->insertRow(model->rowCount());
|
|
||||||
});
|
|
||||||
|
|
||||||
removeEventButton = new QPushButton(this);
|
|
||||||
removeEventButton->setText(Tr::tr("Remove Event"));
|
|
||||||
connect(removeEventButton, &QPushButton::pressed, this, [this]() {
|
|
||||||
QModelIndex index = eventsView->currentIndex();
|
|
||||||
if (index.isValid())
|
|
||||||
eventsView->model()->removeRow(index.row());
|
|
||||||
});
|
|
||||||
|
|
||||||
resetButton = new QPushButton(this);
|
|
||||||
resetButton->setText(Tr::tr("Reset"));
|
|
||||||
connect(resetButton, &QPushButton::pressed, m_settings, &PerfSettings::resetToDefault);
|
|
||||||
|
|
||||||
using namespace Layouting;
|
|
||||||
Column {
|
|
||||||
Row { st, useTracePointsButton, addEventButton, removeEventButton, resetButton },
|
|
||||||
|
|
||||||
eventsView,
|
|
||||||
|
|
||||||
Grid {
|
|
||||||
m_settings->callgraphMode, m_settings->stackSize, br,
|
|
||||||
m_settings->sampleMode, m_settings->period, br,
|
|
||||||
m_settings->extraArguments,
|
|
||||||
},
|
|
||||||
|
|
||||||
st
|
|
||||||
}.attachTo(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
PerfConfigWidget::~PerfConfigWidget() = default;
|
|
||||||
|
|
||||||
void PerfConfigWidget::setTarget(ProjectExplorer::Target *target)
|
|
||||||
{
|
|
||||||
ProjectExplorer::IDevice::ConstPtr device;
|
|
||||||
if (target) {
|
|
||||||
if (ProjectExplorer::Kit *kit = target->kit())
|
|
||||||
device = ProjectExplorer::DeviceKitAspect::device(kit);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (device.isNull()) {
|
|
||||||
useTracePointsButton->setEnabled(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QTC_ASSERT(device, return);
|
|
||||||
QTC_CHECK(!m_process || m_process->state() == QProcess::NotRunning);
|
|
||||||
|
|
||||||
m_process.reset(new Process);
|
|
||||||
m_process->setCommand({device->filePath("perf"), {"probe", "-l"}});
|
|
||||||
connect(m_process.get(), &Process::done,
|
|
||||||
this, &PerfConfigWidget::handleProcessDone);
|
|
||||||
|
|
||||||
useTracePointsButton->setEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PerfConfigWidget::setTracePointsButtonVisible(bool visible)
|
|
||||||
{
|
|
||||||
useTracePointsButton->setVisible(visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PerfConfigWidget::apply()
|
|
||||||
{
|
|
||||||
m_settings->writeGlobalSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PerfConfigWidget::readTracePoints()
|
|
||||||
{
|
|
||||||
QMessageBox messageBox;
|
|
||||||
messageBox.setWindowTitle(Tr::tr("Use Trace Points"));
|
|
||||||
messageBox.setIcon(QMessageBox::Question);
|
|
||||||
messageBox.setText(Tr::tr("Replace events with trace points read from the device?"));
|
|
||||||
messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
|
||||||
if (messageBox.exec() == QMessageBox::Yes) {
|
|
||||||
m_process->start();
|
|
||||||
useTracePointsButton->setEnabled(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PerfConfigWidget::handleProcessDone()
|
|
||||||
{
|
|
||||||
if (m_process->error() == QProcess::FailedToStart) {
|
|
||||||
Core::AsynchronousMessageBox::warning(
|
|
||||||
Tr::tr("Cannot List Trace Points"),
|
|
||||||
Tr::tr("\"perf probe -l\" failed to start. Is perf installed?"));
|
|
||||||
useTracePointsButton->setEnabled(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const QList<QByteArray> lines =
|
|
||||||
m_process->readAllRawStandardOutput().append(m_process->readAllRawStandardError())
|
|
||||||
.split('\n');
|
|
||||||
auto model = eventsView->model();
|
|
||||||
const int previousRows = model->rowCount();
|
|
||||||
QHash<QByteArray, QByteArray> tracePoints;
|
|
||||||
for (const QByteArray &line : lines) {
|
|
||||||
const QByteArray trimmed = line.trimmed();
|
|
||||||
const int space = trimmed.indexOf(' ');
|
|
||||||
if (space < 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// If the whole "on ..." string is the same, the trace points are redundant
|
|
||||||
tracePoints[trimmed.mid(space + 1)] = trimmed.left(space);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tracePoints.isEmpty()) {
|
|
||||||
Core::AsynchronousMessageBox::warning(
|
|
||||||
Tr::tr("No Trace Points Found"),
|
|
||||||
Tr::tr("Trace points can be defined with \"perf probe -a\"."));
|
|
||||||
} else {
|
|
||||||
for (const QByteArray &event : std::as_const(tracePoints)) {
|
|
||||||
int row = model->rowCount();
|
|
||||||
model->insertRow(row);
|
|
||||||
model->setData(model->index(row, PerfConfigEventsModel::ColumnEventType),
|
|
||||||
PerfConfigEventsModel::EventTypeCustom);
|
|
||||||
model->setData(model->index(row, PerfConfigEventsModel::ColumnSubType),
|
|
||||||
QString::fromUtf8(event));
|
|
||||||
}
|
|
||||||
model->removeRows(0, previousRows);
|
|
||||||
m_settings->sampleMode.setVolatileValue(1);
|
|
||||||
m_settings->period.setVolatileValue(1);
|
|
||||||
}
|
|
||||||
useTracePointsButton->setEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
QWidget *SettingsDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option,
|
|
||||||
const QModelIndex &index) const
|
|
||||||
{
|
|
||||||
Q_UNUSED(option)
|
|
||||||
const int row = index.row();
|
|
||||||
const int column = index.column();
|
|
||||||
const PerfConfigEventsModel *model = qobject_cast<const PerfConfigEventsModel *>(index.model());
|
|
||||||
|
|
||||||
auto getRowEventType = [&]() {
|
|
||||||
return qvariant_cast<PerfConfigEventsModel::EventType>(
|
|
||||||
model->data(model->index(row, PerfConfigEventsModel::ColumnEventType),
|
|
||||||
Qt::EditRole));
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (column) {
|
|
||||||
case PerfConfigEventsModel::ColumnEventType: {
|
|
||||||
QComboBox *editor = new QComboBox(parent);
|
|
||||||
QMetaEnum meta = QMetaEnum::fromType<PerfConfigEventsModel::EventType>();
|
|
||||||
for (int i = 0; i < PerfConfigEventsModel::EventTypeInvalid; ++i) {
|
|
||||||
editor->addItem(QString::fromLatin1(meta.valueToKey(i)).mid(
|
|
||||||
static_cast<int>(strlen("EventType"))).toLower(), i);
|
|
||||||
}
|
|
||||||
return editor;
|
|
||||||
}
|
|
||||||
case PerfConfigEventsModel::ColumnSubType: {
|
|
||||||
PerfConfigEventsModel::EventType eventType = getRowEventType();
|
|
||||||
switch (eventType) {
|
|
||||||
case PerfConfigEventsModel::EventTypeHardware: {
|
|
||||||
QComboBox *editor = new QComboBox(parent);
|
|
||||||
for (int i = PerfConfigEventsModel::SubTypeEventTypeHardware;
|
|
||||||
i < PerfConfigEventsModel::SubTypeEventTypeSoftware; ++i) {
|
|
||||||
editor->addItem(PerfConfigEventsModel::subTypeString(PerfConfigEventsModel::EventTypeHardware,
|
|
||||||
PerfConfigEventsModel::SubType(i)), i);
|
|
||||||
}
|
|
||||||
return editor;
|
|
||||||
}
|
|
||||||
case PerfConfigEventsModel::EventTypeSoftware: {
|
|
||||||
QComboBox *editor = new QComboBox(parent);
|
|
||||||
for (int i = PerfConfigEventsModel::SubTypeEventTypeSoftware;
|
|
||||||
i < PerfConfigEventsModel::SubTypeEventTypeCache; ++i) {
|
|
||||||
editor->addItem(PerfConfigEventsModel::subTypeString(PerfConfigEventsModel::EventTypeSoftware,
|
|
||||||
PerfConfigEventsModel::SubType(i)), i);
|
|
||||||
}
|
|
||||||
return editor;
|
|
||||||
}
|
|
||||||
case PerfConfigEventsModel::EventTypeCache: {
|
|
||||||
QComboBox *editor = new QComboBox(parent);
|
|
||||||
for (int i = PerfConfigEventsModel::SubTypeEventTypeCache;
|
|
||||||
i < PerfConfigEventsModel::SubTypeInvalid; ++i) {
|
|
||||||
editor->addItem(PerfConfigEventsModel::subTypeString(PerfConfigEventsModel::EventTypeCache,
|
|
||||||
PerfConfigEventsModel::SubType(i)), i);
|
|
||||||
}
|
|
||||||
return editor;
|
|
||||||
}
|
|
||||||
case PerfConfigEventsModel::EventTypeBreakpoint: {
|
|
||||||
QLineEdit *editor = new QLineEdit(parent);
|
|
||||||
editor->setText("0x0000000000000000");
|
|
||||||
editor->setValidator(new QRegularExpressionValidator(
|
|
||||||
QRegularExpression("0x[0-9a-f]{16}"), parent));
|
|
||||||
return editor;
|
|
||||||
}
|
|
||||||
case PerfConfigEventsModel::EventTypeCustom: {
|
|
||||||
QLineEdit *editor = new QLineEdit(parent);
|
|
||||||
return editor;
|
|
||||||
}
|
|
||||||
case PerfConfigEventsModel::EventTypeRaw: {
|
|
||||||
QLineEdit *editor = new QLineEdit(parent);
|
|
||||||
editor->setText("r000");
|
|
||||||
editor->setValidator(new QRegularExpressionValidator(
|
|
||||||
QRegularExpression("r[0-9a-f]{3}"), parent));
|
|
||||||
return editor;
|
|
||||||
}
|
|
||||||
case PerfConfigEventsModel::EventTypeInvalid:
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return nullptr; // Will never be reached, but GCC cannot figure this out.
|
|
||||||
}
|
|
||||||
case PerfConfigEventsModel::ColumnOperation: {
|
|
||||||
QComboBox *editor = new QComboBox(parent);
|
|
||||||
PerfConfigEventsModel::EventType eventType = getRowEventType();
|
|
||||||
if (eventType == PerfConfigEventsModel::EventTypeCache) {
|
|
||||||
editor->addItem("load", PerfConfigEventsModel::OperationLoad);
|
|
||||||
editor->addItem("store", PerfConfigEventsModel::OperationStore);
|
|
||||||
editor->addItem("prefetch", PerfConfigEventsModel::OperationPrefetch);
|
|
||||||
} else if (eventType == PerfConfigEventsModel::EventTypeBreakpoint) {
|
|
||||||
editor->addItem("r", PerfConfigEventsModel::OperationLoad);
|
|
||||||
editor->addItem("rw", PerfConfigEventsModel::OperationLoad
|
|
||||||
| PerfConfigEventsModel::OperationStore);
|
|
||||||
editor->addItem("rwx", PerfConfigEventsModel::OperationLoad
|
|
||||||
| PerfConfigEventsModel::OperationStore
|
|
||||||
| PerfConfigEventsModel::OperationExecute);
|
|
||||||
editor->addItem("rx", PerfConfigEventsModel::OperationLoad
|
|
||||||
| PerfConfigEventsModel::OperationExecute);
|
|
||||||
editor->addItem("w", PerfConfigEventsModel::OperationStore);
|
|
||||||
editor->addItem("wx", PerfConfigEventsModel::OperationStore
|
|
||||||
| PerfConfigEventsModel::OperationExecute);
|
|
||||||
editor->addItem("x", PerfConfigEventsModel::OperationExecute);
|
|
||||||
} else {
|
|
||||||
editor->setEnabled(false);
|
|
||||||
}
|
|
||||||
return editor;
|
|
||||||
}
|
|
||||||
case PerfConfigEventsModel::ColumnResult: {
|
|
||||||
QComboBox *editor = new QComboBox(parent);
|
|
||||||
PerfConfigEventsModel::EventType eventType = getRowEventType();
|
|
||||||
if (eventType != PerfConfigEventsModel::EventTypeCache) {
|
|
||||||
editor->setEnabled(false);
|
|
||||||
} else {
|
|
||||||
editor->addItem("refs", PerfConfigEventsModel::ResultRefs);
|
|
||||||
editor->addItem("misses", PerfConfigEventsModel::ResultMisses);
|
|
||||||
}
|
|
||||||
return editor;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
|
|
||||||
{
|
|
||||||
if (QComboBox *combo = qobject_cast<QComboBox *>(editor)) {
|
|
||||||
QVariant data = index.model()->data(index, Qt::EditRole);
|
|
||||||
for (int i = 0, end = combo->count(); i != end; ++i) {
|
|
||||||
if (combo->itemData(i) == data) {
|
|
||||||
combo->setCurrentIndex(i);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (QLineEdit *lineedit = qobject_cast<QLineEdit *>(editor)) {
|
|
||||||
lineedit->setText(index.model()->data(index, Qt::DisplayRole).toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
|
|
||||||
const QModelIndex &index) const
|
|
||||||
{
|
|
||||||
if (QComboBox *combo = qobject_cast<QComboBox *>(editor)) {
|
|
||||||
model->setData(index, combo->currentData());
|
|
||||||
} else if (QLineEdit *lineedit = qobject_cast<QLineEdit *>(editor)) {
|
|
||||||
QString text = lineedit->text();
|
|
||||||
QVariant type = model->data(model->index(index.row(),
|
|
||||||
PerfConfigEventsModel::ColumnEventType),
|
|
||||||
Qt::EditRole);
|
|
||||||
switch (qvariant_cast<PerfConfigEventsModel::EventType>(type)) {
|
|
||||||
case PerfConfigEventsModel::EventTypeRaw:
|
|
||||||
model->setData(index, text.mid(static_cast<int>(strlen("r"))).toULongLong(nullptr, 16));
|
|
||||||
break;
|
|
||||||
case PerfConfigEventsModel::EventTypeBreakpoint:
|
|
||||||
model->setData(index,
|
|
||||||
text.mid(static_cast<int>(strlen("0x"))).toULongLong(nullptr, 16));
|
|
||||||
break;
|
|
||||||
case PerfConfigEventsModel::EventTypeCustom:
|
|
||||||
model->setData(index, text);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
|
|
||||||
const QModelIndex &index) const
|
|
||||||
{
|
|
||||||
Q_UNUSED(index)
|
|
||||||
editor->setGeometry(option.rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Internal
|
|
||||||
} // namespace PerfProfiler
|
|
||||||
|
|
||||||
#include "perfconfigwidget.moc"
|
|
@@ -1,50 +0,0 @@
|
|||||||
// Copyright (C) 2018 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "perfsettings.h"
|
|
||||||
|
|
||||||
#include <coreplugin/dialogs/ioptionspage.h>
|
|
||||||
|
|
||||||
#include <QProcess>
|
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
|
||||||
class QPushButton;
|
|
||||||
class QTableView;
|
|
||||||
QT_END_NAMESPACE
|
|
||||||
|
|
||||||
namespace Utils { class Process; }
|
|
||||||
|
|
||||||
namespace PerfProfiler {
|
|
||||||
namespace Internal {
|
|
||||||
|
|
||||||
class PerfConfigWidget : public Core::IOptionsPageWidget
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit PerfConfigWidget(PerfSettings *settings, QWidget *parent = nullptr);
|
|
||||||
~PerfConfigWidget();
|
|
||||||
|
|
||||||
void updateUi();
|
|
||||||
void setTarget(ProjectExplorer::Target *target);
|
|
||||||
void setTracePointsButtonVisible(bool visible);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void apply() final;
|
|
||||||
|
|
||||||
void readTracePoints();
|
|
||||||
void handleProcessDone();
|
|
||||||
|
|
||||||
PerfSettings *m_settings;
|
|
||||||
std::unique_ptr<Utils::Process> m_process;
|
|
||||||
|
|
||||||
QTableView *eventsView;
|
|
||||||
QPushButton *useTracePointsButton;
|
|
||||||
QPushButton *addEventButton;
|
|
||||||
QPushButton *removeEventButton;
|
|
||||||
QPushButton *resetButton;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Internal
|
|
||||||
} // namespace PerfProfiler
|
|
@@ -18,8 +18,6 @@ QtcPlugin {
|
|||||||
files: [
|
files: [
|
||||||
"perfconfigeventsmodel.cpp",
|
"perfconfigeventsmodel.cpp",
|
||||||
"perfconfigeventsmodel.h",
|
"perfconfigeventsmodel.h",
|
||||||
"perfconfigwidget.cpp",
|
|
||||||
"perfconfigwidget.h",
|
|
||||||
"perfdatareader.cpp",
|
"perfdatareader.cpp",
|
||||||
"perfdatareader.h",
|
"perfdatareader.h",
|
||||||
"perfevent.h",
|
"perfevent.h",
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
#include "perfprofilertool.h"
|
#include "perfprofilertool.h"
|
||||||
|
|
||||||
#include "perfconfigwidget.h"
|
|
||||||
#include "perfloaddialog.h"
|
#include "perfloaddialog.h"
|
||||||
#include "perfprofilerplugin.h"
|
#include "perfprofilerplugin.h"
|
||||||
#include "perfprofilertr.h"
|
#include "perfprofilertr.h"
|
||||||
@@ -236,11 +235,8 @@ void PerfProfilerTool::createViews()
|
|||||||
settings = runConfig->currentSettings<PerfSettings>(Constants::PerfSettingsId);
|
settings = runConfig->currentSettings<PerfSettings>(Constants::PerfSettingsId);
|
||||||
}
|
}
|
||||||
|
|
||||||
PerfConfigWidget *widget = new PerfConfigWidget(
|
QWidget *widget = settings ? settings->createPerfConfigWidget(target)
|
||||||
settings ? settings : &globalSettings(),
|
: globalSettings().createPerfConfigWidget(target);
|
||||||
Core::ICore::dialogParent());
|
|
||||||
widget->setTracePointsButtonVisible(true);
|
|
||||||
widget->setTarget(target);
|
|
||||||
widget->setWindowFlags(Qt::Dialog);
|
widget->setWindowFlags(Qt::Dialog);
|
||||||
widget->setAttribute(Qt::WA_DeleteOnClose);
|
widget->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
widget->show();
|
widget->show();
|
||||||
|
@@ -1,24 +1,375 @@
|
|||||||
// Copyright (C) 2018 The Qt Company Ltd.
|
// Copyright (C) 2018 The Qt Company Ltd.
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
#include "perfconfigwidget.h"
|
#include "perfsettings.h"
|
||||||
|
|
||||||
|
#include "perfconfigeventsmodel.h"
|
||||||
#include "perfprofilerconstants.h"
|
#include "perfprofilerconstants.h"
|
||||||
#include "perfprofilertr.h"
|
#include "perfprofilertr.h"
|
||||||
#include "perfsettings.h"
|
|
||||||
|
|
||||||
#include <coreplugin/dialogs/ioptionspage.h>
|
#include <coreplugin/dialogs/ioptionspage.h>
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
|
#include <coreplugin/messagebox.h>
|
||||||
|
|
||||||
#include <debugger/analyzer/analyzericons.h>
|
#include <debugger/analyzer/analyzericons.h>
|
||||||
#include <debugger/debuggertr.h>
|
#include <debugger/debuggertr.h>
|
||||||
|
|
||||||
|
#include <projectexplorer/devicesupport/idevice.h>
|
||||||
|
#include <projectexplorer/kit.h>
|
||||||
|
#include <projectexplorer/kitinformation.h>
|
||||||
|
#include <projectexplorer/target.h>
|
||||||
|
|
||||||
|
#include <utils/aspects.h>
|
||||||
#include <utils/layoutbuilder.h>
|
#include <utils/layoutbuilder.h>
|
||||||
#include <utils/process.h>
|
#include <utils/process.h>
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QMetaEnum>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QStyledItemDelegate>
|
||||||
|
#include <QTableView>
|
||||||
|
|
||||||
|
using namespace ProjectExplorer;
|
||||||
using namespace Utils;
|
using namespace Utils;
|
||||||
|
|
||||||
|
using namespace PerfProfiler::Internal;
|
||||||
|
|
||||||
namespace PerfProfiler {
|
namespace PerfProfiler {
|
||||||
|
|
||||||
|
class PerfConfigWidget : public Core::IOptionsPageWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PerfConfigWidget(PerfSettings *settings, Target *target);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void apply() final;
|
||||||
|
|
||||||
|
void readTracePoints();
|
||||||
|
void handleProcessDone();
|
||||||
|
|
||||||
|
PerfSettings *m_settings;
|
||||||
|
std::unique_ptr<Utils::Process> m_process;
|
||||||
|
|
||||||
|
QTableView *eventsView;
|
||||||
|
QPushButton *useTracePointsButton;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SettingsDelegate : public QStyledItemDelegate
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SettingsDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {}
|
||||||
|
|
||||||
|
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
|
||||||
|
const QModelIndex &index) const override;
|
||||||
|
|
||||||
|
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
|
||||||
|
void setModelData(QWidget *editor, QAbstractItemModel *model,
|
||||||
|
const QModelIndex &index) const override;
|
||||||
|
|
||||||
|
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
|
||||||
|
const QModelIndex &) const override
|
||||||
|
{
|
||||||
|
editor->setGeometry(option.rect);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
PerfConfigWidget::PerfConfigWidget(PerfSettings *settings, Target *target)
|
||||||
|
: m_settings(settings)
|
||||||
|
{
|
||||||
|
eventsView = new QTableView(this);
|
||||||
|
eventsView->setMinimumSize(QSize(0, 300));
|
||||||
|
eventsView->setEditTriggers(QAbstractItemView::AllEditTriggers);
|
||||||
|
eventsView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
|
eventsView->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||||
|
eventsView->setModel(new PerfConfigEventsModel(m_settings, this));
|
||||||
|
eventsView->setItemDelegate(new SettingsDelegate(this));
|
||||||
|
eventsView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||||||
|
|
||||||
|
useTracePointsButton = new QPushButton(this);
|
||||||
|
useTracePointsButton->setText(Tr::tr("Use Trace Points"));
|
||||||
|
useTracePointsButton->setVisible(target != nullptr);
|
||||||
|
connect(useTracePointsButton, &QPushButton::pressed,
|
||||||
|
this, &PerfConfigWidget::readTracePoints);
|
||||||
|
|
||||||
|
auto addEventButton = new QPushButton(Tr::tr("Add Event"), this);
|
||||||
|
connect(addEventButton, &QPushButton::pressed, this, [this]() {
|
||||||
|
auto model = eventsView->model();
|
||||||
|
model->insertRow(model->rowCount());
|
||||||
|
});
|
||||||
|
|
||||||
|
auto removeEventButton = new QPushButton(Tr::tr("Remove Event"), this);
|
||||||
|
connect(removeEventButton, &QPushButton::pressed, this, [this] {
|
||||||
|
QModelIndex index = eventsView->currentIndex();
|
||||||
|
if (index.isValid())
|
||||||
|
eventsView->model()->removeRow(index.row());
|
||||||
|
});
|
||||||
|
|
||||||
|
auto resetButton = new QPushButton(Tr::tr("Reset"), this);
|
||||||
|
connect(resetButton, &QPushButton::pressed, m_settings, &PerfSettings::resetToDefault);
|
||||||
|
|
||||||
|
using namespace Layouting;
|
||||||
|
Column {
|
||||||
|
Row { st, useTracePointsButton, addEventButton, removeEventButton, resetButton },
|
||||||
|
|
||||||
|
eventsView,
|
||||||
|
|
||||||
|
Grid {
|
||||||
|
m_settings->callgraphMode, m_settings->stackSize, br,
|
||||||
|
m_settings->sampleMode, m_settings->period, br,
|
||||||
|
m_settings->extraArguments,
|
||||||
|
},
|
||||||
|
|
||||||
|
st
|
||||||
|
}.attachTo(this);
|
||||||
|
|
||||||
|
IDevice::ConstPtr device;
|
||||||
|
if (target)
|
||||||
|
device = DeviceKitAspect::device(target->kit());
|
||||||
|
|
||||||
|
if (device.isNull()) {
|
||||||
|
useTracePointsButton->setEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTC_ASSERT(device, return);
|
||||||
|
QTC_CHECK(!m_process || m_process->state() == QProcess::NotRunning);
|
||||||
|
|
||||||
|
m_process.reset(new Process);
|
||||||
|
m_process->setCommand({device->filePath("perf"), {"probe", "-l"}});
|
||||||
|
connect(m_process.get(), &Process::done,
|
||||||
|
this, &PerfConfigWidget::handleProcessDone);
|
||||||
|
|
||||||
|
useTracePointsButton->setEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PerfConfigWidget::apply()
|
||||||
|
{
|
||||||
|
m_settings->writeGlobalSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PerfConfigWidget::readTracePoints()
|
||||||
|
{
|
||||||
|
QMessageBox messageBox;
|
||||||
|
messageBox.setWindowTitle(Tr::tr("Use Trace Points"));
|
||||||
|
messageBox.setIcon(QMessageBox::Question);
|
||||||
|
messageBox.setText(Tr::tr("Replace events with trace points read from the device?"));
|
||||||
|
messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||||
|
if (messageBox.exec() == QMessageBox::Yes) {
|
||||||
|
m_process->start();
|
||||||
|
useTracePointsButton->setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PerfConfigWidget::handleProcessDone()
|
||||||
|
{
|
||||||
|
if (m_process->error() == QProcess::FailedToStart) {
|
||||||
|
Core::AsynchronousMessageBox::warning(
|
||||||
|
Tr::tr("Cannot List Trace Points"),
|
||||||
|
Tr::tr("\"perf probe -l\" failed to start. Is perf installed?"));
|
||||||
|
useTracePointsButton->setEnabled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const QList<QByteArray> lines =
|
||||||
|
m_process->readAllRawStandardOutput().append(m_process->readAllRawStandardError())
|
||||||
|
.split('\n');
|
||||||
|
auto model = eventsView->model();
|
||||||
|
const int previousRows = model->rowCount();
|
||||||
|
QHash<QByteArray, QByteArray> tracePoints;
|
||||||
|
for (const QByteArray &line : lines) {
|
||||||
|
const QByteArray trimmed = line.trimmed();
|
||||||
|
const int space = trimmed.indexOf(' ');
|
||||||
|
if (space < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// If the whole "on ..." string is the same, the trace points are redundant
|
||||||
|
tracePoints[trimmed.mid(space + 1)] = trimmed.left(space);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tracePoints.isEmpty()) {
|
||||||
|
Core::AsynchronousMessageBox::warning(
|
||||||
|
Tr::tr("No Trace Points Found"),
|
||||||
|
Tr::tr("Trace points can be defined with \"perf probe -a\"."));
|
||||||
|
} else {
|
||||||
|
for (const QByteArray &event : std::as_const(tracePoints)) {
|
||||||
|
int row = model->rowCount();
|
||||||
|
model->insertRow(row);
|
||||||
|
model->setData(model->index(row, PerfConfigEventsModel::ColumnEventType),
|
||||||
|
PerfConfigEventsModel::EventTypeCustom);
|
||||||
|
model->setData(model->index(row, PerfConfigEventsModel::ColumnSubType),
|
||||||
|
QString::fromUtf8(event));
|
||||||
|
}
|
||||||
|
model->removeRows(0, previousRows);
|
||||||
|
m_settings->sampleMode.setVolatileValue(1);
|
||||||
|
m_settings->period.setVolatileValue(1);
|
||||||
|
}
|
||||||
|
useTracePointsButton->setEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget *SettingsDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option,
|
||||||
|
const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(option)
|
||||||
|
const int row = index.row();
|
||||||
|
const int column = index.column();
|
||||||
|
const PerfConfigEventsModel *model = qobject_cast<const PerfConfigEventsModel *>(index.model());
|
||||||
|
|
||||||
|
auto getRowEventType = [&]() {
|
||||||
|
return qvariant_cast<PerfConfigEventsModel::EventType>(
|
||||||
|
model->data(model->index(row, PerfConfigEventsModel::ColumnEventType),
|
||||||
|
Qt::EditRole));
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (column) {
|
||||||
|
case PerfConfigEventsModel::ColumnEventType: {
|
||||||
|
QComboBox *editor = new QComboBox(parent);
|
||||||
|
QMetaEnum meta = QMetaEnum::fromType<PerfConfigEventsModel::EventType>();
|
||||||
|
for (int i = 0; i < PerfConfigEventsModel::EventTypeInvalid; ++i) {
|
||||||
|
editor->addItem(QString::fromLatin1(meta.valueToKey(i)).mid(
|
||||||
|
static_cast<int>(strlen("EventType"))).toLower(), i);
|
||||||
|
}
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
case PerfConfigEventsModel::ColumnSubType: {
|
||||||
|
PerfConfigEventsModel::EventType eventType = getRowEventType();
|
||||||
|
switch (eventType) {
|
||||||
|
case PerfConfigEventsModel::EventTypeHardware: {
|
||||||
|
QComboBox *editor = new QComboBox(parent);
|
||||||
|
for (int i = PerfConfigEventsModel::SubTypeEventTypeHardware;
|
||||||
|
i < PerfConfigEventsModel::SubTypeEventTypeSoftware; ++i) {
|
||||||
|
editor->addItem(PerfConfigEventsModel::subTypeString(PerfConfigEventsModel::EventTypeHardware,
|
||||||
|
PerfConfigEventsModel::SubType(i)), i);
|
||||||
|
}
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
case PerfConfigEventsModel::EventTypeSoftware: {
|
||||||
|
QComboBox *editor = new QComboBox(parent);
|
||||||
|
for (int i = PerfConfigEventsModel::SubTypeEventTypeSoftware;
|
||||||
|
i < PerfConfigEventsModel::SubTypeEventTypeCache; ++i) {
|
||||||
|
editor->addItem(PerfConfigEventsModel::subTypeString(PerfConfigEventsModel::EventTypeSoftware,
|
||||||
|
PerfConfigEventsModel::SubType(i)), i);
|
||||||
|
}
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
case PerfConfigEventsModel::EventTypeCache: {
|
||||||
|
QComboBox *editor = new QComboBox(parent);
|
||||||
|
for (int i = PerfConfigEventsModel::SubTypeEventTypeCache;
|
||||||
|
i < PerfConfigEventsModel::SubTypeInvalid; ++i) {
|
||||||
|
editor->addItem(PerfConfigEventsModel::subTypeString(PerfConfigEventsModel::EventTypeCache,
|
||||||
|
PerfConfigEventsModel::SubType(i)), i);
|
||||||
|
}
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
case PerfConfigEventsModel::EventTypeBreakpoint: {
|
||||||
|
QLineEdit *editor = new QLineEdit(parent);
|
||||||
|
editor->setText("0x0000000000000000");
|
||||||
|
editor->setValidator(new QRegularExpressionValidator(
|
||||||
|
QRegularExpression("0x[0-9a-f]{16}"), parent));
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
case PerfConfigEventsModel::EventTypeCustom: {
|
||||||
|
QLineEdit *editor = new QLineEdit(parent);
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
case PerfConfigEventsModel::EventTypeRaw: {
|
||||||
|
QLineEdit *editor = new QLineEdit(parent);
|
||||||
|
editor->setText("r000");
|
||||||
|
editor->setValidator(new QRegularExpressionValidator(
|
||||||
|
QRegularExpression("r[0-9a-f]{3}"), parent));
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
case PerfConfigEventsModel::EventTypeInvalid:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return nullptr; // Will never be reached, but GCC cannot figure this out.
|
||||||
|
}
|
||||||
|
case PerfConfigEventsModel::ColumnOperation: {
|
||||||
|
QComboBox *editor = new QComboBox(parent);
|
||||||
|
PerfConfigEventsModel::EventType eventType = getRowEventType();
|
||||||
|
if (eventType == PerfConfigEventsModel::EventTypeCache) {
|
||||||
|
editor->addItem("load", PerfConfigEventsModel::OperationLoad);
|
||||||
|
editor->addItem("store", PerfConfigEventsModel::OperationStore);
|
||||||
|
editor->addItem("prefetch", PerfConfigEventsModel::OperationPrefetch);
|
||||||
|
} else if (eventType == PerfConfigEventsModel::EventTypeBreakpoint) {
|
||||||
|
editor->addItem("r", PerfConfigEventsModel::OperationLoad);
|
||||||
|
editor->addItem("rw", PerfConfigEventsModel::OperationLoad
|
||||||
|
| PerfConfigEventsModel::OperationStore);
|
||||||
|
editor->addItem("rwx", PerfConfigEventsModel::OperationLoad
|
||||||
|
| PerfConfigEventsModel::OperationStore
|
||||||
|
| PerfConfigEventsModel::OperationExecute);
|
||||||
|
editor->addItem("rx", PerfConfigEventsModel::OperationLoad
|
||||||
|
| PerfConfigEventsModel::OperationExecute);
|
||||||
|
editor->addItem("w", PerfConfigEventsModel::OperationStore);
|
||||||
|
editor->addItem("wx", PerfConfigEventsModel::OperationStore
|
||||||
|
| PerfConfigEventsModel::OperationExecute);
|
||||||
|
editor->addItem("x", PerfConfigEventsModel::OperationExecute);
|
||||||
|
} else {
|
||||||
|
editor->setEnabled(false);
|
||||||
|
}
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
case PerfConfigEventsModel::ColumnResult: {
|
||||||
|
QComboBox *editor = new QComboBox(parent);
|
||||||
|
PerfConfigEventsModel::EventType eventType = getRowEventType();
|
||||||
|
if (eventType != PerfConfigEventsModel::EventTypeCache) {
|
||||||
|
editor->setEnabled(false);
|
||||||
|
} else {
|
||||||
|
editor->addItem("refs", PerfConfigEventsModel::ResultRefs);
|
||||||
|
editor->addItem("misses", PerfConfigEventsModel::ResultMisses);
|
||||||
|
}
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
if (QComboBox *combo = qobject_cast<QComboBox *>(editor)) {
|
||||||
|
QVariant data = index.model()->data(index, Qt::EditRole);
|
||||||
|
for (int i = 0, end = combo->count(); i != end; ++i) {
|
||||||
|
if (combo->itemData(i) == data) {
|
||||||
|
combo->setCurrentIndex(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (QLineEdit *lineedit = qobject_cast<QLineEdit *>(editor)) {
|
||||||
|
lineedit->setText(index.model()->data(index, Qt::DisplayRole).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
|
||||||
|
const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
if (QComboBox *combo = qobject_cast<QComboBox *>(editor)) {
|
||||||
|
model->setData(index, combo->currentData());
|
||||||
|
} else if (QLineEdit *lineedit = qobject_cast<QLineEdit *>(editor)) {
|
||||||
|
QString text = lineedit->text();
|
||||||
|
QVariant type = model->data(model->index(index.row(),
|
||||||
|
PerfConfigEventsModel::ColumnEventType),
|
||||||
|
Qt::EditRole);
|
||||||
|
switch (qvariant_cast<PerfConfigEventsModel::EventType>(type)) {
|
||||||
|
case PerfConfigEventsModel::EventTypeRaw:
|
||||||
|
model->setData(index, text.mid(static_cast<int>(strlen("r"))).toULongLong(nullptr, 16));
|
||||||
|
break;
|
||||||
|
case PerfConfigEventsModel::EventTypeBreakpoint:
|
||||||
|
model->setData(index,
|
||||||
|
text.mid(static_cast<int>(strlen("0x"))).toULongLong(nullptr, 16));
|
||||||
|
break;
|
||||||
|
case PerfConfigEventsModel::EventTypeCustom:
|
||||||
|
model->setData(index, text);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PerfSettingsPage
|
||||||
|
|
||||||
PerfSettings &globalSettings()
|
PerfSettings &globalSettings()
|
||||||
{
|
{
|
||||||
static PerfSettings theSettings(nullptr);
|
static PerfSettings theSettings(nullptr);
|
||||||
@@ -67,9 +418,7 @@ PerfSettings::PerfSettings(ProjectExplorer::Target *target)
|
|||||||
|
|
||||||
setLayouter([this, target] {
|
setLayouter([this, target] {
|
||||||
using namespace Layouting;
|
using namespace Layouting;
|
||||||
auto widget = new Internal::PerfConfigWidget(this);
|
auto widget = new PerfConfigWidget(this, target);
|
||||||
widget->setTracePointsButtonVisible(target != nullptr);
|
|
||||||
widget->setTarget(target);
|
|
||||||
return Column { widget };
|
return Column { widget };
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -137,6 +486,11 @@ void PerfSettings::resetToDefault()
|
|||||||
fromMap(map);
|
fromMap(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QWidget *PerfSettings::createPerfConfigWidget(Target *target)
|
||||||
|
{
|
||||||
|
return new PerfConfigWidget(this, target);
|
||||||
|
}
|
||||||
|
|
||||||
// PerfSettingsPage
|
// PerfSettingsPage
|
||||||
|
|
||||||
class PerfSettingsPage final : public Core::IOptionsPage
|
class PerfSettingsPage final : public Core::IOptionsPage
|
||||||
|
@@ -24,6 +24,8 @@ public:
|
|||||||
|
|
||||||
void resetToDefault();
|
void resetToDefault();
|
||||||
|
|
||||||
|
QWidget *createPerfConfigWidget(ProjectExplorer::Target *target);
|
||||||
|
|
||||||
Utils::IntegerAspect period{this};
|
Utils::IntegerAspect period{this};
|
||||||
Utils::IntegerAspect stackSize{this};
|
Utils::IntegerAspect stackSize{this};
|
||||||
Utils::SelectionAspect sampleMode{this};
|
Utils::SelectionAspect sampleMode{this};
|
||||||
|
Reference in New Issue
Block a user