Skip to content

Commit ab1f876

Browse files
committed
feat: Show multiple costs in timelinewidget
Showing only one cost is fine if we only show a hardware event, but since we now support tracepoints and some come in an enter/exit pair it requires us to rework the timeline delegate. This patch makes the event source combobox multi select and allows to select multiple event sources.
1 parent 5cab9a2 commit ab1f876

File tree

7 files changed

+95
-23
lines changed

7 files changed

+95
-23
lines changed

src/models/eventmodelproxy.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@ EventModelProxy::EventModelProxy(QObject* parent)
2121

2222
EventModelProxy::~EventModelProxy() = default;
2323

24+
void EventModelProxy::showCostId(qint32 costId)
25+
{
26+
m_hiddenCostIds.remove(costId);
27+
invalidate();
28+
}
29+
30+
void EventModelProxy::hideCostId(qint32 costId)
31+
{
32+
m_hiddenCostIds.insert(costId);
33+
invalidate();
34+
}
35+
2436
bool EventModelProxy::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
2537
{
2638
// index is invalid -> we are at the root node
@@ -36,7 +48,7 @@ bool EventModelProxy::filterAcceptsRow(int source_row, const QModelIndex& source
3648
.data(EventModel::EventsRole)
3749
.value<Data::Events>();
3850

39-
if (data.empty()) {
51+
if (data.empty() || m_hiddenCostIds.contains(data[0].type)) {
4052
return false;
4153
}
4254

src/models/eventmodelproxy.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#pragma once
99

10+
#include <QSet>
1011
#include <QSortFilterProxyModel>
1112

1213
class EventModelProxy : public QSortFilterProxyModel
@@ -16,7 +17,13 @@ class EventModelProxy : public QSortFilterProxyModel
1617
explicit EventModelProxy(QObject* parent = nullptr);
1718
~EventModelProxy() override;
1819

20+
void showCostId(qint32 costId);
21+
void hideCostId(qint32 costId);
22+
1923
protected:
2024
bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override;
2125
bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override;
26+
27+
private:
28+
QSet<qint32> m_hiddenCostIds;
2229
};

src/models/timelinedelegate.cpp

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,6 @@ void TimeLineDelegate::paint(QPainter* painter, const QStyleOptionViewItem& opti
161161
const auto results = index.data(EventModel::EventResultsRole).value<Data::EventResults>();
162162
const auto offCpuCostId = results.offCpuTimeCostId;
163163
const auto lostEventCostId = results.lostEventCostId;
164-
const auto tracepointEventCostId = results.tracepointEventCostId;
165164
const bool is_alternate = option.features & QStyleOptionViewItem::Alternate;
166165
const auto& palette = option.palette;
167166

@@ -231,10 +230,6 @@ void TimeLineDelegate::paint(QPainter* painter, const QStyleOptionViewItem& opti
231230
// see also: https://www.spinics.net/lists/linux-perf-users/msg03486.html
232231
for (const auto& event : data.events) {
233232
const auto isLostEvent = event.type == lostEventCostId;
234-
const auto isTracepointEvent = event.type == tracepointEventCostId;
235-
if (event.type != m_eventType && !isLostEvent && !isTracepointEvent) {
236-
continue;
237-
}
238233

239234
const auto x = data.mapTimeToX(event.time);
240235
if (x < TimeLineData::padding || x >= data.w) {
@@ -334,14 +329,22 @@ bool TimeLineDelegate::helpEvent(QHelpEvent* event, QAbstractItemView* view, con
334329
Util::formatTimeString(found.totalCost),
335330
Util::formatTimeString(found.maxCost)));
336331
} else if (found.numSamples > 0) {
337-
QToolTip::showText(event->globalPos(),
338-
tr("time: %1\n%5 samples: %2\ntotal sample cost: %3\nmax sample cost: %4")
339-
.arg(formattedTime, QString::number(found.numSamples),
340-
Util::formatCost(found.totalCost), Util::formatCost(found.maxCost),
341-
totalCosts.value(found.type).label));
332+
if (m_eventType == results.tracepointEventCostId) {
333+
// currently tracepoint cost is saying nothig, so don't show it
334+
QToolTip::showText(
335+
event->globalPos(),
336+
tr("time: %1\n%3 samples: %2")
337+
.arg(formattedTime, QString::number(found.numSamples), results.tracepoints[index.row()].name));
338+
339+
} else {
340+
QToolTip::showText(event->globalPos(),
341+
tr("time: %1\n%5 samples: %2\ntotal sample cost: %3\nmax sample cost: %4")
342+
.arg(formattedTime, QString::number(found.numSamples),
343+
Util::formatCost(found.totalCost), Util::formatCost(found.maxCost),
344+
totalCosts.value(found.type).label));
345+
}
342346
} else {
343-
QToolTip::showText(event->globalPos(),
344-
tr("time: %1 (no %2 samples)").arg(formattedTime, totalCosts.value(m_eventType).label));
347+
QToolTip::showText(event->globalPos(), tr("time: %1 (no samples)").arg(formattedTime));
345348
}
346349
return true;
347350
}
@@ -390,6 +393,12 @@ bool TimeLineDelegate::eventFilter(QObject* watched, QEvent* event)
390393

391394
const auto time = data.mapXToTime(pos.x() - visualRect.left() - TimeLineData::padding);
392395
const auto start = findEvent(data.events.constBegin(), data.events.constEnd(), time);
396+
397+
// we can show multiple events in one row so we need to dynamically figure out which costId is needed
398+
auto hoveringEntry = std::find_if(start, data.events.cend(),
399+
[time](const Data::Event& event) { return event.time >= time; });
400+
setEventType(hoveringEntry != data.events.cend() ? hoveringEntry->type : 0);
401+
393402
auto findSamples = [&](int costType, bool contains) {
394403
bool foundAny = false;
395404
data.findSamples(hoverX, costType, results.lostEventCostId, contains, start,

src/models/timelinedelegate.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ class TimeLineDelegate : public QStyledItemDelegate
5959
bool helpEvent(QHelpEvent* event, QAbstractItemView* view, const QStyleOptionViewItem& option,
6060
const QModelIndex& index) override;
6161

62-
void setEventType(int type);
6362
void setSelectedStacks(const QSet<qint32>& selectedStacks);
6463

6564
signals:
@@ -71,6 +70,7 @@ class TimeLineDelegate : public QStyledItemDelegate
7170
bool eventFilter(QObject* watched, QEvent* event) override;
7271

7372
private:
73+
void setEventType(int type);
7474
void updateView();
7575
void updateZoomState();
7676

src/resultsutil.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#include <QTimer>
2020
#include <QTreeView>
2121

22+
#include <QStandardItemModel>
23+
2224
#include "models/costdelegate.h"
2325
#include "models/data.h"
2426
#include "models/filterandzoomstack.h"
@@ -219,6 +221,36 @@ void fillEventSourceComboBox(QComboBox* combo, const Data::Costs& costs, const Q
219221
}
220222
}
221223

224+
void fillEventSourceComboBoxMultiSelect(QComboBox* combo, const Data::Costs& costs, const QString& /*tooltipTemplate*/)
225+
{
226+
// restore selection if possible
227+
const auto oldData = combo->currentData();
228+
229+
combo->clear();
230+
231+
auto model = new QStandardItemModel(costs.numTypes(), 1, combo);
232+
int itemCounter = 0;
233+
for (int costId = 0, c = costs.numTypes(); costId < c; costId++) {
234+
if (!costs.totalCost(costId)) {
235+
continue;
236+
}
237+
238+
auto item = new QStandardItem(costs.typeName(costId));
239+
item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
240+
item->setData(Qt::Checked, Qt::CheckStateRole);
241+
item->setData(costId, Qt::UserRole + 1);
242+
model->setItem(itemCounter, item);
243+
itemCounter++;
244+
}
245+
model->setRowCount(itemCounter);
246+
combo->setModel(model);
247+
248+
const auto index = combo->findData(oldData);
249+
if (index != -1) {
250+
combo->setCurrentIndex(index);
251+
}
252+
}
253+
222254
void setupResultsAggregation(QComboBox* costAggregationComboBox)
223255
{
224256
struct AggregationType

src/resultsutil.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ void hideEmptyColumns(const Data::Costs& costs, QTreeView* view, int numBaseColu
100100
void hideTracepointColumns(const Data::Costs& costs, QTreeView* view, int numBaseColumns);
101101

102102
void fillEventSourceComboBox(QComboBox* combo, const Data::Costs& costs, const QString& tooltipTemplate);
103+
void fillEventSourceComboBoxMultiSelect(QComboBox* combo, const Data::Costs& costs, const QString& tooltipTemplate);
103104

104105
void setupResultsAggregation(QComboBox* costAggregationComboBox);
105106
}

src/timelinewidget.cpp

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
#include "parsers/perf/perfparser.h"
1818

1919
#include <QLabel>
20+
#include <QListView>
2021
#include <QPointer>
2122
#include <QProgressBar>
2223
#include <QSortFilterProxyModel>
24+
#include <QStandardItemModel>
2325
#include <QVBoxLayout>
2426

2527
#include <KLocalizedString>
@@ -82,9 +84,24 @@ TimeLineWidget::TimeLineWidget(PerfParser* parser, QMenu* filterMenu, FilterAndZ
8284
connect(timeLineProxy, &QAbstractItemModel::rowsInserted, this, [this]() { ui->timeLineView->expandToDepth(1); });
8385
connect(timeLineProxy, &QAbstractItemModel::modelReset, this, [this]() { ui->timeLineView->expandToDepth(1); });
8486

85-
connect(m_parser, &PerfParser::bottomUpDataAvailable, this, [this](const Data::BottomUpResults& data) {
86-
ResultsUtil::fillEventSourceComboBox(ui->timeLineEventSource, data.costs, tr("Show timeline for %1 events."));
87-
});
87+
connect(m_parser, &PerfParser::bottomUpDataAvailable, this,
88+
[this, timeLineProxy](const Data::BottomUpResults& data) {
89+
ResultsUtil::fillEventSourceComboBoxMultiSelect(ui->timeLineEventSource, data.costs,
90+
tr("Show timeline for %1 events."));
91+
92+
auto model = qobject_cast<QStandardItemModel*>(ui->timeLineEventSource->model());
93+
connect(ui->timeLineEventSource->model(), &QStandardItemModel::dataChanged, model,
94+
[timeLineProxy](const QModelIndex& topLeft, const QModelIndex& /*bottomRight*/,
95+
const QVector<int>& /*roles*/) {
96+
auto checkState = topLeft.data(Qt::CheckStateRole).value<Qt::CheckState>();
97+
98+
if (checkState == Qt::CheckState::Checked) {
99+
timeLineProxy->showCostId(topLeft.data(Qt::UserRole + 1).toUInt());
100+
} else {
101+
timeLineProxy->hideCostId(topLeft.data(Qt::UserRole + 1).toUInt());
102+
}
103+
});
104+
});
88105

89106
connect(m_parser, &PerfParser::eventsAvailable, this, [this, eventModel](const Data::EventResults& data) {
90107
eventModel->setData(data);
@@ -101,12 +118,6 @@ TimeLineWidget::TimeLineWidget(PerfParser* parser, QMenu* filterMenu, FilterAndZ
101118
}
102119
});
103120

104-
connect(ui->timeLineEventSource, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
105-
[this](int index) {
106-
const auto typeId = ui->timeLineEventSource->itemData(index).toInt();
107-
m_timeLineDelegate->setEventType(typeId);
108-
});
109-
110121
connect(m_timeLineDelegate, &TimeLineDelegate::addToFavorites, this,
111122
[eventModel](const QModelIndex& index) { eventModel->addToFavorites(index); });
112123
connect(m_timeLineDelegate, &TimeLineDelegate::removeFromFavorites, this,

0 commit comments

Comments
 (0)