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 }