File indexing completed on 2024-05-05 05:47:27
0001 /* 0002 This file is part of Massif Visualizer 0003 0004 Copyright 2010 Milian Wolff <mail@milianw.de> 0005 0006 This library is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU Lesser General Public 0008 License as published by the Free Software Foundation; either 0009 version 2.1 of the License, or (at your option) version 3, or any 0010 later version accepted by the membership of KDE e.V. (or its 0011 successor approved by the membership of KDE e.V.), which shall 0012 act as a proxy defined in Section 6 of version 3 of the license. 0013 0014 This library is distributed in the hope that it will be useful, 0015 but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0017 Lesser General Public License for more details. 0018 0019 You should have received a copy of the GNU Lesser General Public 0020 License along with this library. If not, see <http://www.gnu.org/licenses/>. 0021 */ 0022 0023 #include "detailedcostmodel.h" 0024 0025 #include "massifdata/filedata.h" 0026 #include "massifdata/snapshotitem.h" 0027 #include "massifdata/treeleafitem.h" 0028 #include "massifdata/util.h" 0029 0030 #include "KChartGlobal" 0031 #include "KChartDataValueAttributes" 0032 #include "KChartLineAttributes" 0033 0034 #include <QColor> 0035 #include <QPen> 0036 #include <QBrush> 0037 0038 #include <QMultiMap> 0039 #include <qalgorithms.h> 0040 0041 #include <KLocalizedString> 0042 0043 using namespace Massif; 0044 0045 namespace { 0046 0047 QVariant colorForColumn(int column, int columnCount, int role) 0048 { 0049 QColor c = QColor::fromHsv(double(column + 1) / columnCount * 255, 255, 255); 0050 if (role == KChart::DatasetBrushRole) { 0051 return QBrush(c); 0052 } else { 0053 return QPen(c); 0054 } 0055 } 0056 0057 } 0058 0059 DetailedCostModel::DetailedCostModel(QObject* parent) 0060 : QAbstractTableModel(parent), m_data(nullptr), m_maxDatasetCount(10) 0061 { 0062 } 0063 0064 DetailedCostModel::~DetailedCostModel() 0065 { 0066 } 0067 0068 void DetailedCostModel::setSource(const FileData* data) 0069 { 0070 if (m_data) { 0071 beginRemoveRows(QModelIndex(), 0, rowCount() - 1); 0072 m_data = nullptr; 0073 m_columns.clear(); 0074 m_rows.clear(); 0075 m_nodes.clear(); 0076 m_peaks.clear(); 0077 endRemoveRows(); 0078 } 0079 if (data) { 0080 // get top cost points: 0081 // we traverse the detailed heap trees until the first fork 0082 QMultiMap<int, QByteArray> sortColumnMap; 0083 foreach (const SnapshotItem* snapshot, data->snapshots()) { 0084 if (snapshot->heapTree()) { 0085 QVector<const TreeLeafItem*> nodes; 0086 foreach (const TreeLeafItem* node, snapshot->heapTree()->children()) { 0087 if (isBelowThreshold(node->label())) { 0088 continue; 0089 } 0090 // find interesting node, i.e. until first fork 0091 const TreeLeafItem* firstNode = node; 0092 while (node->children().size() == 1 && node->children().at(0)->cost() == node->cost()) { 0093 node = node->children().at(0); 0094 } 0095 if (node->children().isEmpty()) { 0096 // when we traverse the tree down until the end (i.e. no forks), 0097 // we end up in main() most probably, and that's uninteresting 0098 node = firstNode; 0099 } 0100 if (!sortColumnMap.values().contains(node->label())) { 0101 sortColumnMap.insert(node->cost(), node->label()); 0102 m_peaks[node->label()] = qMakePair(node, snapshot); 0103 } else { 0104 quint64 cost = sortColumnMap.key(node->label()); 0105 if (node->cost() > cost) { 0106 sortColumnMap.remove(cost, node->label()); 0107 cost = node->cost(); 0108 sortColumnMap.insert(cost, node->label()); 0109 m_peaks[node->label()].first = node; 0110 m_peaks[node->label()].second = snapshot; 0111 } 0112 } 0113 nodes << node; 0114 } 0115 m_rows << snapshot; 0116 m_nodes[snapshot] = nodes; 0117 } 0118 } 0119 // ugh, yet another bad usage of QList in Qt's API 0120 m_columns = sortColumnMap.values().toVector(); 0121 QAlgorithmsPrivate::qReverse(m_columns.begin(), m_columns.end()); 0122 0123 if (m_rows.isEmpty()) { 0124 return; 0125 } 0126 // +1 for the offset (+0 would be m_rows.size() -1) 0127 beginInsertRows(QModelIndex(), 0, m_rows.size()); 0128 m_data = data; 0129 endInsertRows(); 0130 } 0131 } 0132 0133 void DetailedCostModel::setMaximumDatasetCount(int count) 0134 { 0135 Q_ASSERT(count >= 0); 0136 const int currentCols = qMin(m_columns.size(), m_maxDatasetCount); 0137 const int newCols = qMin(m_columns.size(), count); 0138 if (currentCols == newCols) { 0139 return; 0140 } 0141 if (newCols < currentCols) { 0142 beginRemoveColumns(QModelIndex(), newCols * 2, currentCols * 2 - 1); 0143 } else { 0144 beginInsertColumns(QModelIndex(), currentCols * 2, newCols * 2 - 1); 0145 } 0146 m_maxDatasetCount = count; 0147 if (newCols < currentCols) { 0148 endRemoveColumns(); 0149 } else { 0150 endInsertColumns(); 0151 } 0152 Q_ASSERT(columnCount() == newCols * 2); 0153 } 0154 0155 int DetailedCostModel::maximumDatasetCount() const 0156 { 0157 return m_maxDatasetCount; 0158 } 0159 0160 QVariant DetailedCostModel::data(const QModelIndex& index, int role) const 0161 { 0162 // FIXME kdchart queries (-1, -1) for empty models 0163 if ( index.row() == -1 || index.column() == -1 ) { 0164 // qWarning() << "DetailedCostModel::data: FIXME fix kdchart views to not query model data for invalid indices!"; 0165 return QVariant(); 0166 } 0167 0168 Q_ASSERT(index.row() >= 0 && index.row() < rowCount(index.parent())); 0169 Q_ASSERT(index.column() >= 0 && index.column() < columnCount(index.parent())); 0170 Q_ASSERT(m_data); 0171 Q_ASSERT(!index.parent().isValid()); 0172 0173 if ( role == KChart::LineAttributesRole ) { 0174 static KChart::LineAttributes attributes; 0175 attributes.setDisplayArea(true); 0176 if (index == m_selection) { 0177 attributes.setTransparency(255); 0178 } else if (index.column() == m_selection.column()) { 0179 attributes.setTransparency(152); 0180 } else { 0181 attributes.setTransparency(127); 0182 } 0183 return QVariant::fromValue(attributes); 0184 } 0185 0186 if (role == KChart::DatasetBrushRole || role == KChart::DatasetPenRole) { 0187 return colorForColumn(index.column(), columnCount(), role); 0188 } 0189 0190 if ( role != Qt::DisplayRole && role != Qt::ToolTipRole ) { 0191 return QVariant(); 0192 } 0193 0194 if (index.row() == 0) { 0195 if (role == Qt::ToolTipRole) { 0196 return QVariant(); 0197 } else { 0198 if (index.column() % 2 == 0) { 0199 // get x-coordinate of the last snapshot with cost below 0.1% of peak cost 0200 QVector< SnapshotItem* >::const_iterator it = m_data->snapshots().constBegin(); 0201 double time = 0; 0202 while (it != m_data->snapshots().constEnd() && (*it)->cost() < m_data->peak()->cost() * 0.001) { 0203 time = (*it)->time(); 0204 ++it; 0205 } 0206 return time; 0207 } else { 0208 // cost to 0 0209 return 0; 0210 } 0211 } 0212 } 0213 0214 const SnapshotItem* snapshot; 0215 if (role == Qt::ToolTipRole) { 0216 // hack: the ToolTip will only be queried by KChart and that one uses the 0217 // left index, but we want it to query the right one 0218 Q_ASSERT(index.row() < m_rows.size()); 0219 snapshot = m_rows.at(index.row()); 0220 } else { 0221 snapshot = m_rows.at(index.row() - 1); 0222 } 0223 0224 if (index.column() % 2 == 0 && role != Qt::ToolTipRole) { 0225 return snapshot->time(); 0226 } else { 0227 const TreeLeafItem* node = nullptr; 0228 const QByteArray needle = m_columns.at(index.column() / 2); 0229 foreach(const TreeLeafItem* n, m_nodes[snapshot]) { 0230 if (n->label() == needle) { 0231 node = n; 0232 break; 0233 } 0234 } 0235 if (role == Qt::ToolTipRole) { 0236 return tooltipForTreeLeaf(node, snapshot, needle); 0237 } else { 0238 return node ? node->cost() : 0; 0239 } 0240 } 0241 } 0242 0243 QVariant DetailedCostModel::headerData(int section, Qt::Orientation orientation, int role) const 0244 { 0245 if (orientation == Qt::Horizontal && section % 2 == 0 && section < columnCount()) { 0246 if (role == KChart::DatasetBrushRole || role == KChart::DatasetPenRole) { 0247 return colorForColumn(section, columnCount(), role); 0248 } else if (role == Qt::DisplayRole ) { 0249 // only show name without memory address or location 0250 QByteArray label = prettyLabel(m_columns.at(section / 2)); 0251 if (label.indexOf("???") != -1) { 0252 return label; 0253 } 0254 int endPos = label.lastIndexOf('('); 0255 if (endPos == -1) { 0256 return label; 0257 } 0258 label = label.mid(0, endPos - 1); 0259 const int maxLen = 40; 0260 if (label.length() > maxLen) { 0261 label.resize(maxLen - 3); 0262 label.append("..."); 0263 } 0264 return label; 0265 } 0266 } 0267 return QAbstractItemModel::headerData(section, orientation, role); 0268 } 0269 0270 int DetailedCostModel::columnCount(const QModelIndex&) const 0271 { 0272 return qMin(m_maxDatasetCount, m_columns.size()) * 2; 0273 } 0274 0275 int DetailedCostModel::rowCount(const QModelIndex& parent) const 0276 { 0277 if (!m_data) { 0278 return 0; 0279 } 0280 0281 if (parent.isValid()) { 0282 return 0; 0283 } else { 0284 // +1 to get a zero row first 0285 return m_rows.count() + 1; 0286 } 0287 } 0288 0289 DetailedCostModel::Peaks DetailedCostModel::peaks() const 0290 { 0291 Peaks peaks; 0292 QHash<QByteArray, ModelItem>::const_iterator it = m_peaks.constBegin(); 0293 while (it != m_peaks.end()) { 0294 int row = m_rows.indexOf(it->second); 0295 Q_ASSERT(row >= 0); 0296 int column = m_columns.indexOf(it->first->label()); 0297 if (column >= m_maxDatasetCount || column == -1) { 0298 ++it; 0299 continue; 0300 } 0301 Q_ASSERT(column >= 0); 0302 peaks[index(row + 1, column*2)] = it->first; 0303 ++it; 0304 } 0305 Q_ASSERT(peaks.size() == qMin(m_maxDatasetCount, m_columns.size())); 0306 return peaks; 0307 } 0308 0309 QModelIndex DetailedCostModel::indexForSnapshot(const SnapshotItem* snapshot) const 0310 { 0311 int row = m_rows.indexOf(snapshot); 0312 if (row == -1) { 0313 return QModelIndex(); 0314 } 0315 return index(row + 1, 0); 0316 } 0317 0318 QModelIndex DetailedCostModel::indexForTreeLeaf(const TreeLeafItem* node) const 0319 { 0320 int column = m_columns.indexOf(node->label()); 0321 if (column == -1 || column >= m_maxDatasetCount) { 0322 return QModelIndex(); 0323 } 0324 Nodes::const_iterator it = m_nodes.constBegin(); 0325 while (it != m_nodes.constEnd()) { 0326 if (it->contains(node)) { 0327 return index(m_rows.indexOf(it.key()), column * 2); 0328 } 0329 ++it; 0330 } 0331 return QModelIndex(); 0332 } 0333 0334 ModelItem DetailedCostModel::itemForIndex(const QModelIndex& idx) const 0335 { 0336 if (!idx.isValid() || idx.parent().isValid() || idx.row() > rowCount() || idx.column() > columnCount()) { 0337 return ModelItem(nullptr, nullptr); 0338 } 0339 if (idx.row() == 0) { 0340 return ModelItem(nullptr, nullptr); 0341 } 0342 const QByteArray needle = m_columns.at(idx.column() / 2); 0343 for (int i = 1; i < 3 && idx.row() - i >= 0; ++i) { 0344 const SnapshotItem* snapshot = m_rows.at(idx.row() - i); 0345 foreach(const TreeLeafItem* n, m_nodes[snapshot]) { 0346 if (n->label() == needle) { 0347 return ModelItem(n, nullptr); 0348 } 0349 } 0350 } 0351 return ModelItem(nullptr, nullptr); 0352 } 0353 0354 QModelIndex DetailedCostModel::indexForItem(const ModelItem& item) const 0355 { 0356 if (!item.first && !item.second) { 0357 return QModelIndex(); 0358 } 0359 Q_ASSERT((item.first && !item.second) || (!item.first && item.second)); 0360 if (item.first) { 0361 return indexForTreeLeaf(item.first); 0362 } else { 0363 return indexForSnapshot(item.second); 0364 } 0365 } 0366 0367 void DetailedCostModel::setSelection(const QModelIndex& index) 0368 { 0369 m_selection = index; 0370 } 0371 0372 void DetailedCostModel::hideFunction(const TreeLeafItem* node) 0373 { 0374 beginResetModel(); 0375 Nodes::iterator it = m_nodes.begin(); 0376 while (it != m_nodes.end()) { 0377 QVector< const TreeLeafItem* >::iterator it2 = it.value().begin(); 0378 while (it2 != it.value().end()) { 0379 if ((*it2)->label() == node->label()) { 0380 it2 = it.value().erase(it2); 0381 } else { 0382 ++it2; 0383 } 0384 } 0385 ++it; 0386 } 0387 int idx = m_columns.indexOf(node->label()); 0388 if (idx != -1) { 0389 m_columns.remove(idx); 0390 } 0391 endResetModel(); 0392 } 0393 0394 void DetailedCostModel::hideOtherFunctions(const TreeLeafItem* node) 0395 { 0396 beginResetModel(); 0397 m_columns.clear(); 0398 m_columns << node->label(); 0399 0400 Nodes::iterator it = m_nodes.begin(); 0401 const Nodes::iterator end = m_nodes.end(); 0402 while (it != end) { 0403 QVector< const TreeLeafItem* >::iterator it2 = it.value().begin(); 0404 while (it2 != it.value().end()) { 0405 if ((*it2)->label() != node->label()) { 0406 it2 = it.value().erase(it2); 0407 } else { 0408 ++it2; 0409 } 0410 } 0411 ++it; 0412 } 0413 0414 endResetModel(); 0415 }