Skip to content

Commit

Permalink
Group threads per process in event timeline model
Browse files Browse the repository at this point in the history
Fixes: #220
  • Loading branch information
milianw committed Jan 6, 2020
1 parent fbbb13b commit d055dca
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 50 deletions.
5 changes: 5 additions & 0 deletions src/models/data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -345,3 +345,8 @@ Data::ThreadEvents* Data::EventResults::findThread(qint32 pid, qint32 tid)

return nullptr;
}

const Data::ThreadEvents* Data::EventResults::findThread(qint32 pid, qint32 tid) const
{
return const_cast<Data::EventResults*>(this)->findThread(pid, tid);
}
1 change: 1 addition & 0 deletions src/models/data.h
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,7 @@ struct EventResults
qint32 offCpuTimeCostId = -1;

ThreadEvents* findThread(qint32 pid, qint32 tid);
const ThreadEvents* findThread(qint32 pid, qint32 tid) const;

bool operator==(const EventResults& rhs) const
{
Expand Down
152 changes: 111 additions & 41 deletions src/models/eventmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,49 @@
#include <QDebug>
#include <QSet>

static bool operator<(const EventModel::Process &process, qint32 pid)
{
return process.pid < pid;
}

namespace {
enum class Tag : quintptr
enum class Tag : quint8
{
Invalid = 0,
Root = 1,
Overview = 2,
Cpus = 3,
Threads = 4
Processes = 4,
Threads = 5
};

const auto DATATAG_SHIFT = sizeof(Tag) * 8;
const auto DATATAG_UNSHIFT = (sizeof(quintptr) - sizeof(Tag)) * 8;

quintptr combineDataTag(Tag tag, quintptr data)
{
return data << DATATAG_SHIFT | static_cast<quintptr>(tag);
}

Tag dataTag(quintptr internalId)
{
auto ret = (internalId << DATATAG_UNSHIFT) >> DATATAG_UNSHIFT;
if (ret > static_cast<quintptr>(Tag::Threads))
return Tag::Invalid;
return static_cast<Tag>(ret);
}

Tag dataTag(const QModelIndex& idx)
{
if (!idx.isValid() || idx.internalId() == static_cast<quintptr>(Tag::Root)) {
if (!idx.isValid())
return Tag::Root;
} else if (idx.internalId() == static_cast<quintptr>(Tag::Overview)) {
return Tag::Overview;
} else if (idx.internalId() == static_cast<quintptr>(Tag::Cpus)) {
return Tag::Cpus;
} else if (idx.internalId() == static_cast<quintptr>(Tag::Threads)) {
return Tag::Threads;
} else {
return Tag::Invalid;
}
else
return dataTag(idx.internalId());
}

quintptr tagData(quintptr internalId)
{
return internalId >> DATATAG_SHIFT;
}
}

Expand All @@ -79,8 +100,10 @@ int EventModel::rowCount(const QModelIndex& parent) const
case Tag::Cpus:
case Tag::Threads:
break;
case Tag::Processes:
return m_processes.value(parent.row()).threads.size();
case Tag::Overview:
return (parent.row() == 0) ? m_data.cpus.size() : m_data.threads.size();
return (parent.row() == 0) ? m_data.cpus.size() : m_processes.size();
case Tag::Root:
return 2;
};
Expand Down Expand Up @@ -121,9 +144,9 @@ QVariant EventModel::data(const QModelIndex& index, int role) const
} else if (role == MaxCostRole) {
return m_maxCost;
} else if (role == NumProcessesRole) {
return m_numProcesses;
return m_processes.size();
} else if (role == NumThreadsRole) {
return m_numThreads;
return m_data.threads.size();
} else if (role == NumCpusRole) {
return static_cast<uint>(m_data.cpus.size());
} else if (role == TotalCostsRole) {
Expand All @@ -138,7 +161,7 @@ QVariant EventModel::data(const QModelIndex& index, int role) const
return {};
} else if (tag == Tag::Overview) {
if (role == Qt::DisplayRole) {
return index.row() == 0 ? tr("CPUs") : tr("Threads");
return index.row() == 0 ? tr("CPUs") : tr("Processes");
} else if (role == Qt::ToolTipRole) {
if (index.row() == 0) {
return tr("Event timelines for all CPUs. This shows you which, and how many CPUs where leveraged."
Expand All @@ -149,6 +172,51 @@ QVariant EventModel::data(const QModelIndex& index, int role) const
} else if (role == SortRole) {
return index.row();
}
return {};
} else if (tag == Tag::Processes) {
const auto &process = m_processes.value(index.row());
if (role == Qt::DisplayRole)
return tr("%1 (#%2)").arg(process.name, QString::number(process.pid));
else if (role == SortRole)
return process.pid;

if (role == Qt::ToolTipRole) {
QString tooltip = tr("Process %1, pid = %2, num threads = %3\n")
.arg(process.name, QString::number(process.pid), QString::number(process.threads.size()));

quint64 runtime = 0;
quint64 maxRuntime = 0;
quint64 offCpuTime = 0;
quint64 numEvents = 0;
for (const auto tid : process.threads) {
const auto thread = m_data.findThread(process.pid, tid);
Q_ASSERT(thread);
runtime += thread->time.delta();
maxRuntime = std::max(thread->time.delta(), maxRuntime);
offCpuTime += thread->offCpuTime;
numEvents += thread->events.size();
}

const auto totalRuntime = m_time.delta();
tooltip += tr("Runtime: %1 (%2% of total runtime)\n")
.arg(Util::formatTimeString(maxRuntime), Util::formatCostRelative(maxRuntime, totalRuntime));
if (m_totalOffCpuTime > 0) {
const auto onCpuTime = runtime - offCpuTime;
tooltip += tr("On-CPU time: %1 (%2% of combined thread runtime, %3% of total On-CPU time)\n")
.arg(Util::formatTimeString(onCpuTime), Util::formatCostRelative(onCpuTime, runtime),
Util::formatCostRelative(onCpuTime, m_totalOnCpuTime));
tooltip += tr("Off-CPU time: %1 (%2% of combined thread runtime, %3% of total Off-CPU time)\n")
.arg(Util::formatTimeString(offCpuTime),
Util::formatCostRelative(offCpuTime, runtime),
Util::formatCostRelative(offCpuTime, m_totalOffCpuTime));
tooltip += tr("CPUs utilized: %1\n").arg(Util::formatCostRelative(onCpuTime, maxRuntime * 100));
}

tooltip += tr("Number of Events: %1 (%2% of the total)")
.arg(QString::number(numEvents), Util::formatCostRelative(numEvents, m_totalEvents));
return tooltip;
}

return {};
}

Expand All @@ -158,7 +226,11 @@ QVariant EventModel::data(const QModelIndex& index, int role) const
if (tag == Tag::Cpus) {
cpu = &m_data.cpus[index.row()];
} else {
thread = &m_data.threads[index.row()];
Q_ASSERT(tag == Tag::Threads);
const auto process = m_processes.value(tagData(index.internalId()));
const auto tid = process.threads.value(index.row());
thread = m_data.findThread(process.pid, tid);
Q_ASSERT(thread);
}

if (role == ThreadStartRole) {
Expand Down Expand Up @@ -230,24 +302,28 @@ void EventModel::setData(const Data::EventResults& data)
m_data = data;
m_totalEvents = 0;
m_maxCost = 0;
m_numProcesses = 0;
m_numThreads = 0;
m_processes.clear();
m_totalOnCpuTime = 0;
m_totalOffCpuTime = 0;
if (data.threads.isEmpty()) {
m_time = {};
} else {
m_time = data.threads.first().time;
QSet<quint32> processes;
QSet<quint32> threads;
for (const auto& thread : data.threads) {
m_time.start = std::min(thread.time.start, m_time.start);
m_time.end = std::max(thread.time.end, m_time.end);
m_totalOffCpuTime += thread.offCpuTime;
m_totalOnCpuTime += thread.time.delta() - thread.offCpuTime;
m_totalEvents += thread.events.size();
processes.insert(thread.pid);
threads.insert(thread.tid);
auto it = std::lower_bound(m_processes.begin(), m_processes.end(), thread.pid);
if (it == m_processes.end() || it->pid != thread.pid) {
m_processes.insert(it, {thread.pid, {thread.tid}, thread.name});
} else {
it->threads.append(thread.tid);
// prefer process name, if we encountered a thread first
if (thread.pid == thread.tid)
it->name = thread.name;
}

for (const auto& event : thread.events) {
if (event.type != 0) {
Expand All @@ -257,8 +333,6 @@ void EventModel::setData(const Data::EventResults& data)
m_maxCost = std::max(event.cost, m_maxCost);
}
}
m_numProcesses = processes.size();
m_numThreads = threads.size();

// don't show timeline for CPU cores that did not receive any events
auto it = std::remove_if(m_data.cpus.begin(), m_data.cpus.end(),
Expand All @@ -270,7 +344,7 @@ void EventModel::setData(const Data::EventResults& data)

QModelIndex EventModel::index(int row, int column, const QModelIndex& parent) const
{
if (row < 0 || column < 0 || column >= NUM_COLUMNS) {
if (row < 0 || row >= rowCount(parent) || column < 0 || column >= NUM_COLUMNS) {
return {};
}

Expand All @@ -280,22 +354,14 @@ QModelIndex EventModel::index(int row, int column, const QModelIndex& parent) co
case Tag::Threads:
break;
case Tag::Root: // root has the 1st level children: Overview
if (row >= 2) {
return {};
}
return createIndex(row, column, static_cast<quintptr>(Tag::Overview));
case Tag::Overview: // 2nd level children: Cpus and the Threads
if (parent.row() == 0) {
if (row >= m_data.cpus.size()) {
return {};
}
case Tag::Overview: // 2nd level children: Cpus and the Processes
if (parent.row() == 0)
return createIndex(row, column, static_cast<quintptr>(Tag::Cpus));
} else {
if (row >= m_data.threads.size()) {
return {};
}
return createIndex(row, column, static_cast<quintptr>(Tag::Threads));
}
else
return createIndex(row, column, static_cast<quintptr>(Tag::Processes));
case Tag::Processes: // 3rd level children: Threads
return createIndex(row, column, combineDataTag(Tag::Threads, parent.row()));
}

return {};
Expand All @@ -310,8 +376,12 @@ QModelIndex EventModel::parent(const QModelIndex& child) const
break;
case Tag::Cpus:
return createIndex(0, 0, static_cast<quintptr>(Tag::Overview));
case Tag::Threads:
case Tag::Processes:
return createIndex(1, 0, static_cast<quintptr>(Tag::Overview));
case Tag::Threads: {
const auto parentRow = tagData(child.internalId());
return createIndex(parentRow, 0, static_cast<quintptr>(Tag::Processes));
}
}

return {};
Expand Down
16 changes: 14 additions & 2 deletions src/models/eventmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,25 @@ class EventModel : public QAbstractItemModel
using QAbstractItemModel::setData;
void setData(const Data::EventResults& data);

struct Process
{
Process(qint32 pid = Data::INVALID_PID, const QVector<qint32> threads = {}, const QString &name = {})
: pid(pid)
, threads(threads)
, name(name)
{}
qint32 pid;
QVector<qint32> threads;
QString name;
};
private:
Data::EventResults m_data;
QVector<Process> m_processes;
Data::TimeRange m_time;
quint64 m_totalOnCpuTime = 0;
quint64 m_totalOffCpuTime = 0;
quint64 m_totalEvents = 0;
quint64 m_maxCost = 0;
uint m_numProcesses = 0;
uint m_numThreads = 0;
};

Q_DECLARE_TYPEINFO(EventModel::Process, Q_MOVABLE_TYPE);
37 changes: 30 additions & 7 deletions tests/modeltests/tst_models.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,11 @@ private slots:
events.cpus[1].cpuId = 1; // empty
events.cpus[2].cpuId = 2;
const int nonEmptyCpus = 2;
const int processes = 2;

const quint64 endTime = 1000;
const quint64 deltaTime = 10;
events.threads.resize(2);
events.threads.resize(4);
auto& thread1 = events.threads[0];
{
thread1.pid = 1234;
Expand All @@ -224,6 +225,20 @@ private slots:
thread2.time = {deltaTime, endTime - deltaTime};
thread2.name = "asdf";
}
auto& thread3 = events.threads[2];
{
thread3.pid = 5678;
thread3.tid = 5678;
thread3.time = {0, endTime};
thread3.name = "barfoo";
}
auto& thread4 = events.threads[3];
{
thread4.pid = 5678;
thread4.tid = 5679;
thread4.time = {endTime - deltaTime, endTime};
thread4.name = "blub";
}

Data::CostSummary costSummary("cycles", 0, 0, Data::Costs::Unit::Unknown);
auto generateEvent = [&costSummary, &events](quint64 time, quint32 cpuId) -> Data::Event {
Expand Down Expand Up @@ -263,11 +278,11 @@ private slots:
const auto minTime = idx.data(EventModel::MinTimeRole).value<quint64>();
QCOMPARE(minTime, quint64(0));
const auto numProcesses = idx.data(EventModel::NumProcessesRole).value<uint>();
QCOMPARE(numProcesses, 1u);
QCOMPARE(numProcesses, processes);
const auto numThreads = idx.data(EventModel::NumThreadsRole).value<uint>();
QCOMPARE(numThreads, 2u);
QCOMPARE(numThreads, events.threads.size());
const auto numCpus = idx.data(EventModel::NumCpusRole).value<uint>();
QCOMPARE(numCpus, 2u);
QCOMPARE(numCpus, nonEmptyCpus);
const auto maxCost = idx.data(EventModel::MaxCostRole).value<quint64>();
QCOMPARE(maxCost, quint64(10));
const auto totalCost = idx.data(EventModel::TotalCostsRole).value<QVector<Data::CostSummary>>();
Expand All @@ -276,13 +291,21 @@ private slots:

for (int i = 0; i < 2; ++i) {
const auto isCpuIndex = i == 0;
const auto parent = model.index(i, EventModel::ThreadColumn);
auto parent = model.index(i, EventModel::ThreadColumn);
verifyCommonData(parent);
QCOMPARE(parent.data(EventModel::SortRole).value<int>(), i);

const auto numRows = model.rowCount(parent);
auto numRows = model.rowCount(parent);
QCOMPARE(numRows, isCpuIndex ? nonEmptyCpus : processes);

QCOMPARE(numRows, isCpuIndex ? nonEmptyCpus : events.threads.size());
if (!isCpuIndex) {
// let's only look at the first process
parent = model.index(0, EventModel::ThreadColumn, parent);
verifyCommonData(parent);
QCOMPARE(parent.data().toString(), "foobar (#1234)");
numRows = model.rowCount(parent);
QCOMPARE(numRows, 2);
}

for (int j = 0; j < numRows; ++j) {
const auto idx = model.index(j, EventModel::ThreadColumn, parent);
Expand Down

0 comments on commit d055dca

Please sign in to comment.