Skip to content

Commit

Permalink
Introduce a new "By File" cost aggregation tab
Browse files Browse the repository at this point in the history
Here we look at the debug information to attribute costs to
individual source files. As such, this is similar to the caller/callee
view, but just on a per-file basis.

This is useful for complex projects where you would like to get
an overview on individual subsystems, where we usually have multiple
symbols grouped in one file.

Fixes: #665
  • Loading branch information
milianw committed Oct 22, 2024
1 parent 0d2b2bf commit eb3b7b2
Show file tree
Hide file tree
Showing 18 changed files with 642 additions and 53 deletions.
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ set(HOTSPOT_SRCS
resultsbottomuppage.cpp
resultsflamegraphpage.cpp
resultscallercalleepage.cpp
resultsbyfilepage.cpp
resultsdisassemblypage.cpp
resultsutil.cpp
costheaderview.cpp
Expand Down Expand Up @@ -66,6 +67,7 @@ set(HOTSPOT_SRCS
resultsbottomuppage.ui
resultsflamegraphpage.ui
resultscallercalleepage.ui
resultsbyfilepage.ui
resultsdisassemblypage.ui
timelinewidget.ui
unwindsettingspage.ui
Expand Down
1 change: 1 addition & 0 deletions src/models/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ add_library(
../util.cpp
callercalleemodel.cpp
callercalleeproxy.cpp
byfilemodel.cpp
codedelegate.cpp
costdelegate.cpp
data.cpp
Expand Down
113 changes: 113 additions & 0 deletions src/models/byfilemodel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
SPDX-FileCopyrightText: Milian Wolff <[email protected]>
SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company, [email protected]
SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "byfilemodel.h"
#include "../util.h"

#include <QDebug>

ByFileModel::ByFileModel(QObject* parent)
: HashModel(parent)
{
}

ByFileModel::~ByFileModel() = default;

void ByFileModel::setResults(const Data::ByFileResults& results)
{
m_results = results;
setRows(results.entries);
}

QVariant ByFileModel::headerCell(int column, int role) const
{
if (role == Qt::DisplayRole) {
switch (column) {
case File:
return tr("File");
}
column -= NUM_BASE_COLUMNS;
if (column < m_results.selfCosts.numTypes()) {
return tr("%1 (self)").arg(m_results.selfCosts.typeName(column));
}
column -= m_results.selfCosts.numTypes();
return tr("%1 (incl.)").arg(m_results.inclusiveCosts.typeName(column));
} else if (role == Qt::ToolTipRole) {
switch (column) {
case File:
return tr("The name of the file. May be empty when debug information is missing.");
}

column -= NUM_BASE_COLUMNS;
if (column < m_results.selfCosts.numTypes()) {
return tr("The aggregated sample costs directly attributed to this file.");
}
return tr("The aggregated sample costs attributed to this file, both directly and indirectly. This includes "
"the costs of all functions called by this file plus its self cost.");
}

return {};
}

QVariant ByFileModel::cell(int column, int role, const QString& file, const Data::ByFileEntry& entry) const
{
if (role == FileRole) {
return QVariant::fromValue(file);
} else if (role == SortRole) {
switch (column) {
case File:
return Util::formatString(file);
}
column -= NUM_BASE_COLUMNS;
if (column < m_results.selfCosts.numTypes()) {
return m_results.selfCosts.cost(column, entry.id);
}
column -= m_results.selfCosts.numTypes();
return m_results.inclusiveCosts.cost(column, entry.id);
} else if (role == TotalCostRole && column >= NUM_BASE_COLUMNS) {
column -= NUM_BASE_COLUMNS;
if (column < m_results.selfCosts.numTypes()) {
return m_results.selfCosts.totalCost(column);
}

column -= m_results.selfCosts.numTypes();
return m_results.inclusiveCosts.totalCost(column);
} else if (role == Qt::DisplayRole) {
switch (column) {
case File:
return Util::formatString(file);
}
column -= NUM_BASE_COLUMNS;
if (column < m_results.selfCosts.numTypes()) {
return Util::formatCostRelative(m_results.selfCosts.cost(column, entry.id),
m_results.selfCosts.totalCost(column), true);
}
column -= m_results.selfCosts.numTypes();
return Util::formatCostRelative(m_results.inclusiveCosts.cost(column, entry.id),
m_results.inclusiveCosts.totalCost(column), true);
} else if (role == SourceMapRole) {
return QVariant::fromValue(entry.sourceMap);
} else if (role == SelfCostsRole) {
return QVariant::fromValue(m_results.selfCosts);
} else if (role == InclusiveCostsRole) {
return QVariant::fromValue(m_results.inclusiveCosts);
} else if (role == Qt::ToolTipRole) {
return Util::formatFileTooltip(entry.id, file, m_results.selfCosts, m_results.inclusiveCosts);
}

return {};
}

QModelIndex ByFileModel::indexForFile(const QString& file) const
{
return indexForKey(file);
}

int ByFileModel::numColumns() const
{
return NUM_BASE_COLUMNS + m_results.inclusiveCosts.numTypes() + m_results.selfCosts.numTypes();
}
51 changes: 51 additions & 0 deletions src/models/byfilemodel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
SPDX-FileCopyrightText: Milian Wolff <[email protected]>
SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company, [email protected]
SPDX-License-Identifier: GPL-2.0-or-later
*/

#pragma once

#include <QAbstractItemModel>

#include "data.h"
#include "hashmodel.h"

class ByFileModel : public HashModel<Data::ByFileEntryMap, ByFileModel>
{
Q_OBJECT
public:
explicit ByFileModel(QObject* parent = nullptr);
~ByFileModel();

void setResults(const Data::ByFileResults& results);

enum Columns
{
File = 0,
};
enum
{
NUM_BASE_COLUMNS = File + 1,
InitialSortColumn = File + 1 // the first cost column
};

enum Roles
{
SortRole = Qt::UserRole,
TotalCostRole,
SelfCostsRole,
InclusiveCostsRole,
SourceMapRole,
FileRole,
};

QVariant headerCell(int column, int role) const final override;
QVariant cell(int column, int role, const QString& file, const Data::ByFileEntry& entry) const final override;
int numColumns() const final override;
QModelIndex indexForFile(const QString& file) const;

private:
Data::ByFileResults m_results;
};
7 changes: 7 additions & 0 deletions src/models/callercalleeproxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ bool match(const QSortFilterProxyModel* proxy, const Data::FileLine& fileLine)

return matchImpl(pattern, fileLine.file);
}

bool match(const QSortFilterProxyModel* proxy, const QString& file)
{
const auto pattern = proxy->filterRegularExpression();

return matchImpl(pattern, file);
}
}

SourceMapProxy::SourceMapProxy(QObject* parent)
Expand Down
1 change: 1 addition & 0 deletions src/models/callercalleeproxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ struct FileLine;
namespace CallerCalleeProxyDetail {
bool match(const QSortFilterProxyModel* proxy, const Data::Symbol& symbol);
bool match(const QSortFilterProxyModel* proxy, const Data::FileLine& fileLine);
bool match(const QSortFilterProxyModel* proxy, const QString& file);
}

class SourceMapModel;
Expand Down
57 changes: 49 additions & 8 deletions src/models/data.h
Original file line number Diff line number Diff line change
Expand Up @@ -667,20 +667,25 @@ struct LocationCost
using SourceLocationCostMap = QHash<FileLine, LocationCost>;
using OffsetLocationCostMap = QHash<quint64, LocationCost>;

inline LocationCost& source(SourceLocationCostMap& sourceMap, const FileLine& fileLine, int numTypes)
{
auto it = sourceMap.find(fileLine);
if (it == sourceMap.end()) {
it = sourceMap.insert(fileLine, {numTypes});
} else if (it->inclusiveCost.size() < static_cast<size_t>(numTypes)) {
it->inclusiveCost.resize(numTypes);
it->selfCost.resize(numTypes);
}
return *it;
}

struct CallerCalleeEntry
{
quint32 id = 0;

LocationCost& source(const FileLine& fileLine, int numTypes)
{
auto it = sourceMap.find(fileLine);
if (it == sourceMap.end()) {
it = sourceMap.insert(fileLine, {numTypes});
} else if (it->inclusiveCost.size() < static_cast<size_t>(numTypes)) {
it->inclusiveCost.resize(numTypes);
it->selfCost.resize(numTypes);
}
return *it;
return Data::source(sourceMap, fileLine, numTypes);
}

ItemCost& callee(const Symbol& symbol, int numTypes)
Expand Down Expand Up @@ -748,6 +753,36 @@ struct CallerCalleeResults

void callerCalleesFromBottomUpData(const BottomUpResults& data, CallerCalleeResults* results);

struct ByFileEntry
{
quint32 id = 0;

LocationCost& source(const FileLine& fileLine, int numTypes)
{
return Data::source(sourceMap, fileLine, numTypes);
}
// source map for this file, i.e. locations mapped to associated costs
SourceLocationCostMap sourceMap;
};

using ByFileEntryMap = QHash<QString, ByFileEntry>;
struct ByFileResults
{
ByFileEntryMap entries;
Costs selfCosts;
Costs inclusiveCosts;

ByFileEntry& entry(const QString& file)
{
auto fileIt = entries.find(file);
if (fileIt == entries.end()) {
fileIt = entries.insert(file, {});
fileIt->id = entries.size() - 1;
}
return *fileIt;
}
};

const constexpr auto INVALID_CPU_ID = std::numeric_limits<quint32>::max();
const constexpr int INVALID_TID = -1;
const constexpr int INVALID_PID = -1;
Expand Down Expand Up @@ -1008,6 +1043,9 @@ Q_DECLARE_TYPEINFO(Data::TopDown, Q_MOVABLE_TYPE);
Q_DECLARE_METATYPE(Data::CallerCalleeEntry)
Q_DECLARE_TYPEINFO(Data::CallerCalleeEntry, Q_MOVABLE_TYPE);

Q_DECLARE_METATYPE(Data::ByFileEntry)
Q_DECLARE_TYPEINFO(Data::ByFileEntry, Q_MOVABLE_TYPE);

Q_DECLARE_METATYPE(Data::BottomUpResults)
Q_DECLARE_TYPEINFO(Data::BottomUpResults, Q_MOVABLE_TYPE);

Expand All @@ -1020,6 +1058,9 @@ Q_DECLARE_TYPEINFO(Data::PerLibraryResults, Q_MOVABLE_TYPE);
Q_DECLARE_METATYPE(Data::CallerCalleeResults)
Q_DECLARE_TYPEINFO(Data::CallerCalleeResults, Q_MOVABLE_TYPE);

Q_DECLARE_METATYPE(Data::ByFileResults)
Q_DECLARE_TYPEINFO(Data::ByFileResults, Q_MOVABLE_TYPE);

Q_DECLARE_METATYPE(Data::Event)
Q_DECLARE_TYPEINFO(Data::Event, Q_MOVABLE_TYPE);

Expand Down
Loading

0 comments on commit eb3b7b2

Please sign in to comment.