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"