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 "dotgraphgenerator.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 <QTextStream>
0031 #include <QFile>
0032 #include <QColor>
0033 #include <QDebug>
0034 
0035 #include <KLocalizedString>
0036 
0037 
0038 namespace Massif {
0039 
0040 struct GraphNode {
0041     const TreeLeafItem* item;
0042     // incoming calls + cost
0043     QHash<GraphNode*, quint64> children;
0044     // outgoing calls
0045     QVector<GraphNode*> parents;
0046     quint64 accumulatedCost;
0047     bool visited;
0048     quint32 belowThresholdCount;
0049     quint64 belowThresholdCost;
0050 };
0051 
0052 }
0053 
0054 Q_DECLARE_TYPEINFO(Massif::GraphNode, Q_MOVABLE_TYPE);
0055 
0056 using namespace Massif;
0057 
0058 DotGraphGenerator::DotGraphGenerator(const SnapshotItem* snapshot, const QString& timeUnit, QObject* parent)
0059     : QThread(parent)
0060     , m_snapshot(snapshot)
0061     , m_node(snapshot->heapTree())
0062     , m_canceled(false)
0063     , m_timeUnit(timeUnit)
0064     , m_highestCost(0)
0065 {
0066     m_file.open();
0067 }
0068 
0069 DotGraphGenerator::DotGraphGenerator(const TreeLeafItem* node, const QString& timeUnit, QObject* parent)
0070     : QThread(parent)
0071     , m_snapshot(0)
0072     , m_node(node)
0073     , m_canceled(false)
0074     , m_timeUnit(timeUnit)
0075     , m_highestCost(0)
0076 {
0077     m_file.open();
0078 }
0079 
0080 DotGraphGenerator::~DotGraphGenerator()
0081 {
0082     qDebug() << "closing generator, file will get removed";
0083 }
0084 
0085 void DotGraphGenerator::cancel()
0086 {
0087     m_canceled = true;
0088 }
0089 
0090 QString getLabel(const TreeLeafItem* node)
0091 {
0092     QByteArray label = prettyLabel(node->label());
0093     const int lineWidth = 40;
0094     if (label.length() > lineWidth) {
0095         int lastPos = 0;
0096         int lastBreak = 0;
0097         while (true) {
0098             lastPos = label.indexOf(',', lastPos);
0099             if (lastPos == -1) {
0100                 break;
0101             } else if (lastPos - lastBreak > lineWidth) {
0102                 label.insert(lastPos, "\\n\\ \\ ");
0103                 lastPos = lastPos + 4;
0104                 lastBreak = lastPos;
0105                 continue;
0106             } else {
0107                 lastPos++;
0108             }
0109         }
0110     }
0111     return QString::fromUtf8(label);
0112 }
0113 
0114 QString getColor(quint64 cost, quint64 maxCost)
0115 {
0116     Q_ASSERT(cost <= maxCost);
0117     const double ratio = (double(cost) / maxCost);
0118     Q_ASSERT(ratio <= 1.0);
0119     return QColor::fromHsv(120 - ratio * 120, (-((ratio-1) * (ratio-1))) * 255 + 255, 255, 255).name();
0120 //     return QColor::fromHsv(120 - ratio * 120, 255, 255).name();
0121 }
0122 
0123 GraphNode* buildGraph(const TreeLeafItem* item, QMultiHash<QByteArray, GraphNode*>& knownNodes,
0124                       quint64& maxCost, GraphNode* parent = 0)
0125 {
0126     // merge below-threshold items
0127     if (parent && item->children().isEmpty()) {
0128         static QRegExp matchBT(QStringLiteral("in ([0-9]+) places, all below massif's threshold"),
0129                                                 Qt::CaseSensitive, QRegExp::RegExp2);
0130         if (matchBT.indexIn(QString::fromLatin1(item->label())) != -1) {
0131             parent->belowThresholdCost += item->cost();
0132             parent->belowThresholdCount += matchBT.cap(1).toInt();
0133         }
0134         return 0;
0135     }
0136     GraphNode* node = knownNodes.value(item->label(), 0);
0137     if (!node) {
0138         node = new GraphNode;
0139         knownNodes.insert(item->label(), node);
0140         node->item = item;
0141         node->accumulatedCost = 0;
0142         node->visited = false;
0143         node->belowThresholdCost = 0;
0144         node->belowThresholdCount = 0;
0145     }
0146 
0147     if (parent && !node->parents.contains(parent)) {
0148         node->parents << parent;
0149     }
0150 
0151     node->accumulatedCost += item->cost();
0152 
0153     if (node->accumulatedCost > maxCost) {
0154         maxCost = node->accumulatedCost;
0155     }
0156 
0157     foreach(TreeLeafItem* child, item->children()) {
0158         GraphNode* childNode = buildGraph(child, knownNodes, maxCost, node);
0159         if (!childNode) {
0160             // was below-threshold item
0161             continue;
0162         }
0163         QMultiHash< GraphNode*, quint64 >::iterator it = node->children.find(childNode);
0164         if (it != node->children.end()) {
0165             it.value() += child->cost();
0166         } else {
0167             node->children.insert(childNode, child->cost());
0168         }
0169     }
0170 
0171     return node;
0172 }
0173 
0174 void DotGraphGenerator::run()
0175 {
0176     if (!m_file.isOpen()) {
0177         qWarning() << "could not create temp file for writing Dot-graph";
0178         return;
0179     }
0180 
0181     if (m_canceled) {
0182         return;
0183     }
0184 
0185     qDebug() << "creating new dot file in" << m_file.fileName();
0186     QTextStream out(&m_file);
0187 
0188     out << "digraph callgraph {\n"
0189            "rankdir = BT;\n";
0190     if (m_canceled) {
0191         return;
0192     }
0193 
0194     QString parentId;
0195     if (m_snapshot) {
0196         // also show some info about the selected snapshot
0197         parentId = QString::number((qint64) m_snapshot);
0198         const QString label = i18n("snapshot #%1 (taken at %2%4)\\nheap cost: %3",
0199                                 m_snapshot->number(), m_snapshot->time(), prettyCost(m_snapshot->cost()),
0200                                 m_timeUnit);
0201         out << '"' << parentId << "\" [shape=box,label=\"" << label << "\",fillcolor=white];\n";
0202 
0203         m_maxCost = m_snapshot->cost();
0204     } else if (m_node) {
0205         const TreeLeafItem* topMost = m_node;
0206         while (topMost->parent()) {
0207             topMost = topMost->parent();
0208         }
0209         m_maxCost = topMost->cost();
0210     }
0211 
0212     if (m_node) {
0213         QMultiHash<QByteArray, GraphNode*> nodes;
0214         GraphNode* root = buildGraph(m_node, nodes, m_maxCost);
0215         m_highestCost = 0;
0216         nodeToDot(root, out, parentId, 0);
0217         qDeleteAll(nodes);
0218     }
0219     out << "}\n";
0220     m_file.flush();
0221 }
0222 
0223 void DotGraphGenerator::nodeToDot(GraphNode* node, QTextStream& out, const QString& parentId, quint64 cost)
0224 {
0225     if (m_canceled) {
0226         return;
0227     }
0228 
0229     const QString nodeId = QString::number((qint64) node);
0230     // write edge with annotated cost
0231     if (!parentId.isEmpty()) {
0232         // edge
0233         out << '"' << nodeId << "\" -> \"" << parentId << '"';
0234         if (cost) {
0235             out << " [label = \"" << prettyCost(cost) << "\"]";
0236         }
0237         out << ";\n";
0238     }
0239 
0240     if (node->visited) {
0241         // don't visit children again - the edge is all we need
0242         return;
0243     }
0244     node->visited = true;
0245 
0246     const bool isRoot = m_snapshot && m_snapshot->heapTree() == node->item;
0247 
0248     // first item we find will be the most cost-intensive one
0249     ///TODO this should take accumulated cost into account!
0250     if (m_highestCost < node->accumulatedCost && !isRoot) {
0251         m_costlyGraphvizId = nodeId;
0252         m_highestCost = node->accumulatedCost;
0253     }
0254 
0255 
0256     QString label = getLabel(node->item);
0257     // group nodes with same cost but different label
0258     // but only if they don't have any other outgoing calls, i.e. parents.size() = 1
0259     bool wasGrouped = false;
0260     while (node && node->children.count() == 1)
0261     {
0262         GraphNode* child = node->children.begin().key();
0263         if (child->accumulatedCost != node->accumulatedCost || node->parents.size() != 1 || child->belowThresholdCount ) {
0264             break;
0265         }
0266         if (m_canceled) {
0267             return;
0268         }
0269         node = child;
0270 
0271         label += QLatin1String(" | ") + QString::fromUtf8(prettyLabel(node->item->label()));
0272         wasGrouped = true;
0273     }
0274 
0275     QString shape;
0276     if (wasGrouped) {
0277         label = QLatin1Char('{') + label + QLatin1Char('}');
0278         // <...> would be an id, escape it
0279         label = label.replace(QLatin1Char('<'), QLatin1String("\\<"));
0280         label = label.replace(QLatin1Char('>'), QLatin1String("\\>"));
0281         shape = QStringLiteral("record");
0282     } else {
0283         shape = QStringLiteral("box");
0284     }
0285 
0286     const QString color = isRoot ? QStringLiteral("white") : getColor(node->accumulatedCost, m_maxCost);
0287     out << '"' << nodeId << "\" [shape=" << shape << ",label=\"" << label << "\",fillcolor=\"" << color << "\"];\n";
0288     if (!node) {
0289         return;
0290     }
0291 
0292     QMultiHash< GraphNode*, quint64 >::const_iterator it = node->children.constBegin();
0293     while(it != node->children.constEnd()) {
0294         if (m_canceled) {
0295             return;
0296         }
0297         nodeToDot(it.key(), out, nodeId, it.value());
0298         ++it;
0299     }
0300     // handle below-threshold
0301     if (node->belowThresholdCount) {
0302         // node
0303         const QString btLabel = i18np("in one place below threshold", "in %1 places, all below threshold", node->belowThresholdCount);
0304         out << '"' << nodeId << "-bt\" [shape=box,label=\"" << btLabel
0305             << "\",fillcolor=\"" << getColor(node->belowThresholdCost, m_maxCost) << "\"];\n";
0306         // edge
0307         out << '"' << nodeId << "-bt\" -> \"" << nodeId << "\" [label =\"" << prettyCost(node->belowThresholdCost) << "\"];\n";
0308     }
0309 }
0310 
0311 QString DotGraphGenerator::outputFile() const
0312 {
0313     return m_file.fileName();
0314 }
0315 
0316 QString DotGraphGenerator::mostCostIntensiveGraphvizId() const
0317 {
0318     return m_costlyGraphvizId;
0319 }