File indexing completed on 2024-05-19 05:44:26

0001 /*
0002     SPDX-FileCopyrightText: 2015-2017 Milian Wolff <mail@milianw.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #include "treemodel.h"
0008 
0009 #include <QDebug>
0010 #include <QTextStream>
0011 
0012 #include <KLocalizedString>
0013 
0014 #include <cmath>
0015 
0016 #include "resultdata.h"
0017 #include "util.h"
0018 
0019 namespace {
0020 
0021 int indexOf(const RowData* row, const QVector<RowData>& siblings)
0022 {
0023     Q_ASSERT(siblings.data() <= row);
0024     Q_ASSERT(siblings.data() + siblings.size() > row);
0025     return row - siblings.data();
0026 }
0027 
0028 const RowData* rowAt(const QVector<RowData>& rows, int row)
0029 {
0030     Q_ASSERT(rows.size() > row);
0031     return rows.data() + row;
0032 }
0033 
0034 /// @return the parent row containing @p index
0035 const RowData* toParentRow(const QModelIndex& index)
0036 {
0037     return static_cast<const RowData*>(index.internalPointer());
0038 }
0039 }
0040 
0041 TreeModel::TreeModel(QObject* parent)
0042     : QAbstractItemModel(parent)
0043 {
0044     qRegisterMetaType<TreeData>();
0045 }
0046 
0047 TreeModel::~TreeModel()
0048 {
0049 }
0050 
0051 QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const
0052 {
0053     if (orientation != Qt::Horizontal || section < 0 || section >= NUM_COLUMNS) {
0054         return {};
0055     }
0056     if (role == Qt::InitialSortOrderRole) {
0057         if (section == AllocationsColumn || section == PeakColumn || section == LeakedColumn
0058             || section == TemporaryColumn) {
0059             return Qt::DescendingOrder;
0060         }
0061     }
0062     if (role == Qt::DisplayRole) {
0063         switch (static_cast<Columns>(section)) {
0064         case AllocationsColumn:
0065             return i18n("Allocations");
0066         case TemporaryColumn:
0067             return i18n("Temporary");
0068         case PeakColumn:
0069             return i18n("Peak");
0070         case LeakedColumn:
0071             return i18n("Leaked");
0072         case LocationColumn:
0073             return i18n("Location");
0074         case NUM_COLUMNS:
0075             break;
0076         }
0077     } else if (role == Qt::ToolTipRole) {
0078         switch (static_cast<Columns>(section)) {
0079         case AllocationsColumn:
0080             return i18n("<qt>The number of times an allocation function was called "
0081                         "from this location.</qt>");
0082         case TemporaryColumn:
0083             return i18n("<qt>The number of temporary allocations. These allocations "
0084                         "are directly followed by a free "
0085                         "without any other allocations in-between.</qt>");
0086         case PeakColumn:
0087             return i18n("<qt>The contributions from a given location to the maximum heap "
0088                         "memory consumption in bytes. This takes deallocations "
0089                         "into account.</qt>");
0090         case LeakedColumn:
0091             return i18n("<qt>The bytes allocated at this location that have not been "
0092                         "deallocated.</qt>");
0093         case LocationColumn:
0094             return i18n("<qt>The location from which an allocation function was "
0095                         "called. Function symbol and file "
0096                         "information "
0097                         "may be unknown when debug information was missing when "
0098                         "heaptrack was run.</qt>");
0099         case NUM_COLUMNS:
0100             break;
0101         }
0102     }
0103     return {};
0104 }
0105 
0106 QVariant TreeModel::data(const QModelIndex& index, int role) const
0107 {
0108     if (index.row() < 0 || index.column() < 0 || index.column() > NUM_COLUMNS) {
0109         return {};
0110     }
0111 
0112     const auto row = (role == MaxCostRole) ? &m_maxCost : toRow(index);
0113 
0114     if (role == Qt::DisplayRole || role == SortRole || role == MaxCostRole) {
0115         switch (static_cast<Columns>(index.column())) {
0116         case AllocationsColumn:
0117             if (role == SortRole || role == MaxCostRole) {
0118                 return static_cast<qint64>(abs(row->cost.allocations));
0119             }
0120             return static_cast<qint64>(row->cost.allocations);
0121         case TemporaryColumn:
0122             if (role == SortRole || role == MaxCostRole) {
0123                 return static_cast<qint64>(abs(row->cost.temporary));
0124             }
0125             return static_cast<qint64>(row->cost.temporary);
0126         case PeakColumn:
0127             if (role == SortRole || role == MaxCostRole) {
0128                 return static_cast<qint64>(abs(row->cost.peak));
0129             } else {
0130                 return Util::formatBytes(row->cost.peak);
0131             }
0132         case LeakedColumn:
0133             if (role == SortRole || role == MaxCostRole) {
0134                 return static_cast<qint64>(abs(row->cost.leaked));
0135             } else {
0136                 return Util::formatBytes(row->cost.leaked);
0137             }
0138         case LocationColumn:
0139             return Util::toString(row->symbol, *m_data.resultData, Util::Short);
0140         case NUM_COLUMNS:
0141             break;
0142         }
0143     } else if (role == Qt::ToolTipRole) {
0144         auto toStr = [this](StringIndex stringId) { return m_data.resultData->string(stringId); };
0145         QString tooltip;
0146         QTextStream stream(&tooltip);
0147         stream << "<qt><pre style='font-family:monospace;'>";
0148         const auto module = toStr(row->symbol.moduleId);
0149         stream << i18nc("1: function, 2: module, 3: module path", "%1\n  in %2 (%3)",
0150                         toStr(row->symbol.functionId).toHtmlEscaped(), Util::basename(module).toHtmlEscaped(),
0151                         module.toHtmlEscaped());
0152         stream << '\n';
0153         stream << '\n';
0154         const auto peakFraction = Util::formatCostRelative(row->cost.peak, m_maxCost.cost.peak);
0155         const auto leakedFraction = Util::formatCostRelative(row->cost.leaked, m_maxCost.cost.leaked);
0156         const auto allocationsFraction = Util::formatCostRelative(row->cost.allocations, m_maxCost.cost.allocations);
0157         const auto temporaryFraction = Util::formatCostRelative(row->cost.temporary, row->cost.allocations);
0158         const auto temporaryFractionTotal = Util::formatCostRelative(row->cost.temporary, m_maxCost.cost.temporary);
0159         stream << i18n("peak contribution: %1 (%2% of total)\n", Util::formatBytes(row->cost.peak), peakFraction);
0160         stream << i18n("leaked: %1 (%2% of total)\n", Util::formatBytes(row->cost.leaked), leakedFraction);
0161         stream << i18n("allocations: %1 (%2% of total)\n", row->cost.allocations, allocationsFraction);
0162         stream << i18n("temporary: %1 (%2% of allocations, %3% of total)\n", row->cost.temporary, temporaryFraction,
0163                        temporaryFractionTotal);
0164         if (!row->children.isEmpty()) {
0165             auto child = row;
0166             int max = 5;
0167             if (child->children.count() == 1) {
0168                 stream << '\n' << i18n("backtrace:") << '\n';
0169             }
0170             while (child->children.count() == 1 && max-- > 0) {
0171                 stream << "\n";
0172                 const auto module = toStr(child->symbol.moduleId);
0173                 stream << i18nc("1: function, 2: module, 3: module path", "%1\n  in %2 (%3)",
0174                                 toStr(child->symbol.functionId).toHtmlEscaped(), Util::basename(module).toHtmlEscaped(),
0175                                 module.toHtmlEscaped());
0176                 child = child->children.data();
0177             }
0178             if (child->children.count() > 1) {
0179                 stream << "\n";
0180                 stream << i18np("called from one location", "called from %1 locations", child->children.count());
0181             }
0182         }
0183         stream << "</pre></qt>";
0184         return tooltip;
0185     } else if (role == SymbolRole) {
0186         return QVariant::fromValue(row->symbol);
0187     } else if (role == ResultDataRole) {
0188         return QVariant::fromValue(m_data.resultData.get());
0189     }
0190     return {};
0191 }
0192 
0193 QModelIndex TreeModel::index(int row, int column, const QModelIndex& parent) const
0194 {
0195     if (row < 0 || column < 0 || column >= NUM_COLUMNS || row >= rowCount(parent)) {
0196         return QModelIndex();
0197     }
0198     return createIndex(row, column, const_cast<void*>(reinterpret_cast<const void*>(toRow(parent))));
0199 }
0200 
0201 QModelIndex TreeModel::parent(const QModelIndex& child) const
0202 {
0203     if (!child.isValid()) {
0204         return {};
0205     }
0206     const auto parent = toParentRow(child);
0207     if (!parent) {
0208         return {};
0209     }
0210     return createIndex(rowOf(parent), 0, const_cast<void*>(reinterpret_cast<const void*>(parent->parent)));
0211 }
0212 
0213 int TreeModel::rowCount(const QModelIndex& parent) const
0214 {
0215     if (!parent.isValid()) {
0216         return m_data.rows.size();
0217     } else if (parent.column() != 0) {
0218         return 0;
0219     }
0220     auto row = toRow(parent);
0221     Q_ASSERT(row);
0222     return row->children.size();
0223 }
0224 
0225 int TreeModel::columnCount(const QModelIndex& /*parent*/) const
0226 {
0227     return NUM_COLUMNS;
0228 }
0229 
0230 void TreeModel::resetData(const TreeData& data)
0231 {
0232     Q_ASSERT(data.resultData);
0233     beginResetModel();
0234     m_data = data;
0235     endResetModel();
0236 }
0237 
0238 void TreeModel::setSummary(const SummaryData& data)
0239 {
0240     beginResetModel();
0241     m_maxCost.cost = data.cost;
0242     endResetModel();
0243 }
0244 
0245 void TreeModel::clearData()
0246 {
0247     beginResetModel();
0248     m_data = {};
0249     m_maxCost = {};
0250     endResetModel();
0251 }
0252 
0253 const RowData* TreeModel::toRow(const QModelIndex& index) const
0254 {
0255     if (!index.isValid()) {
0256         return nullptr;
0257     }
0258     if (const auto parent = toParentRow(index)) {
0259         return rowAt(parent->children, index.row());
0260     } else {
0261         return rowAt(m_data.rows, index.row());
0262     }
0263 }
0264 
0265 int TreeModel::rowOf(const RowData* row) const
0266 {
0267     if (auto parent = row->parent) {
0268         return indexOf(row, parent->children);
0269     } else {
0270         return indexOf(row, m_data.rows);
0271     }
0272 }
0273 
0274 #include "moc_treemodel.cpp"