File indexing completed on 2024-04-28 17:02:22

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 <QtGui/QColor>
0035 #include <QtGui/QPen>
0036 #include <QtGui/QBrush>
0037 
0038 #include <QtCore/QMultiMap>
0039 #include <QtCore/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(0), 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 = 0;
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 = 0;
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(0, 0);
0338     }
0339     if (idx.row() == 0) {
0340         return ModelItem(0, 0);
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, 0);
0348             }
0349         }
0350     }
0351     return ModelItem(0, 0);
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 }