File: dotgraphgenerator.cpp

package info (click to toggle)
massif-visualizer 0.7.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 5,948 kB
  • sloc: cpp: 4,590; xml: 247; sh: 15; makefile: 9
file content (319 lines) | stat: -rw-r--r-- 9,795 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
/*
   This file is part of Massif Visualizer

   Copyright 2010 Milian Wolff <mail@milianw.de>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) version 3, or any
   later version accepted by the membership of KDE e.V. (or its
   successor approved by the membership of KDE e.V.), which shall
   act as a proxy defined in Section 6 of version 3 of the license.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "dotgraphgenerator.h"

#include "massifdata/filedata.h"
#include "massifdata/snapshotitem.h"
#include "massifdata/treeleafitem.h"
#include "massifdata/util.h"

#include <QTextStream>
#include <QFile>
#include <QColor>
#include <QDebug>

#include <KLocalizedString>


namespace Massif {

struct GraphNode {
    const TreeLeafItem* item;
    // incoming calls + cost
    QHash<GraphNode*, quint64> children;
    // outgoing calls
    QVector<GraphNode*> parents;
    quint64 accumulatedCost;
    bool visited;
    quint32 belowThresholdCount;
    quint64 belowThresholdCost;
};

}

Q_DECLARE_TYPEINFO(Massif::GraphNode, Q_MOVABLE_TYPE);

using namespace Massif;

DotGraphGenerator::DotGraphGenerator(const SnapshotItem* snapshot, const QString& timeUnit, QObject* parent)
    : QThread(parent)
    , m_snapshot(snapshot)
    , m_node(snapshot->heapTree())
    , m_canceled(false)
    , m_timeUnit(timeUnit)
    , m_highestCost(0)
{
    m_file.open();
}

DotGraphGenerator::DotGraphGenerator(const TreeLeafItem* node, const QString& timeUnit, QObject* parent)
    : QThread(parent)
    , m_snapshot(0)
    , m_node(node)
    , m_canceled(false)
    , m_timeUnit(timeUnit)
    , m_highestCost(0)
{
    m_file.open();
}

DotGraphGenerator::~DotGraphGenerator()
{
    qDebug() << "closing generator, file will get removed";
}

void DotGraphGenerator::cancel()
{
    m_canceled = true;
}

QString getLabel(const TreeLeafItem* node)
{
    QByteArray label = prettyLabel(node->label());
    const int lineWidth = 40;
    if (label.length() > lineWidth) {
        int lastPos = 0;
        int lastBreak = 0;
        while (true) {
            lastPos = label.indexOf(',', lastPos);
            if (lastPos == -1) {
                break;
            } else if (lastPos - lastBreak > lineWidth) {
                label.insert(lastPos, "\\n\\ \\ ");
                lastPos = lastPos + 4;
                lastBreak = lastPos;
                continue;
            } else {
                lastPos++;
            }
        }
    }
    return QString::fromUtf8(label);
}

QString getColor(quint64 cost, quint64 maxCost)
{
    Q_ASSERT(cost <= maxCost);
    const double ratio = (double(cost) / maxCost);
    Q_ASSERT(ratio <= 1.0);
    return QColor::fromHsv(120 - ratio * 120, (-((ratio-1) * (ratio-1))) * 255 + 255, 255, 255).name();
//     return QColor::fromHsv(120 - ratio * 120, 255, 255).name();
}

GraphNode* buildGraph(const TreeLeafItem* item, QMultiHash<QByteArray, GraphNode*>& knownNodes,
                      quint64& maxCost, GraphNode* parent = 0)
{
    // merge below-threshold items
    if (parent && item->children().isEmpty()) {
        static QRegExp matchBT(QStringLiteral("in ([0-9]+) places, all below massif's threshold"),
                                                Qt::CaseSensitive, QRegExp::RegExp2);
        if (matchBT.indexIn(QString::fromLatin1(item->label())) != -1) {
            parent->belowThresholdCost += item->cost();
            parent->belowThresholdCount += matchBT.cap(1).toInt();
        }
        return 0;
    }
    GraphNode* node = knownNodes.value(item->label(), 0);
    if (!node) {
        node = new GraphNode;
        knownNodes.insert(item->label(), node);
        node->item = item;
        node->accumulatedCost = 0;
        node->visited = false;
        node->belowThresholdCost = 0;
        node->belowThresholdCount = 0;
    }

    if (parent && !node->parents.contains(parent)) {
        node->parents << parent;
    }

    node->accumulatedCost += item->cost();

    if (node->accumulatedCost > maxCost) {
        maxCost = node->accumulatedCost;
    }

    foreach(TreeLeafItem* child, item->children()) {
        GraphNode* childNode = buildGraph(child, knownNodes, maxCost, node);
        if (!childNode) {
            // was below-threshold item
            continue;
        }
        QMultiHash< GraphNode*, quint64 >::iterator it = node->children.find(childNode);
        if (it != node->children.end()) {
            it.value() += child->cost();
        } else {
            node->children.insert(childNode, child->cost());
        }
    }

    return node;
}

void DotGraphGenerator::run()
{
    if (!m_file.isOpen()) {
        qWarning() << "could not create temp file for writing Dot-graph";
        return;
    }

    if (m_canceled) {
        return;
    }

    qDebug() << "creating new dot file in" << m_file.fileName();
    QTextStream out(&m_file);

    out << "digraph callgraph {\n"
           "rankdir = BT;\n";
    if (m_canceled) {
        return;
    }

    QString parentId;
    if (m_snapshot) {
        // also show some info about the selected snapshot
        parentId = QString::number((qint64) m_snapshot);
        const QString label = i18n("snapshot #%1 (taken at %2%4)\\nheap cost: %3",
                                m_snapshot->number(), m_snapshot->time(), prettyCost(m_snapshot->cost()),
                                m_timeUnit);
        out << '"' << parentId << "\" [shape=box,label=\"" << label << "\",fillcolor=white];\n";

        m_maxCost = m_snapshot->cost();
    } else if (m_node) {
        const TreeLeafItem* topMost = m_node;
        while (topMost->parent()) {
            topMost = topMost->parent();
        }
        m_maxCost = topMost->cost();
    }

    if (m_node) {
        QMultiHash<QByteArray, GraphNode*> nodes;
        GraphNode* root = buildGraph(m_node, nodes, m_maxCost);
        m_highestCost = 0;
        nodeToDot(root, out, parentId, 0);
        qDeleteAll(nodes);
    }
    out << "}\n";
    m_file.flush();
}

void DotGraphGenerator::nodeToDot(GraphNode* node, QTextStream& out, const QString& parentId, quint64 cost)
{
    if (m_canceled) {
        return;
    }

    const QString nodeId = QString::number((qint64) node);
    // write edge with annotated cost
    if (!parentId.isEmpty()) {
        // edge
        out << '"' << nodeId << "\" -> \"" << parentId << '"';
        if (cost) {
            out << " [label = \"" << prettyCost(cost) << "\"]";
        }
        out << ";\n";
    }

    if (node->visited) {
        // don't visit children again - the edge is all we need
        return;
    }
    node->visited = true;

    const bool isRoot = m_snapshot && m_snapshot->heapTree() == node->item;

    // first item we find will be the most cost-intensive one
    ///TODO this should take accumulated cost into account!
    if (m_highestCost < node->accumulatedCost && !isRoot) {
        m_costlyGraphvizId = nodeId;
        m_highestCost = node->accumulatedCost;
    }


    QString label = getLabel(node->item);
    // group nodes with same cost but different label
    // but only if they don't have any other outgoing calls, i.e. parents.size() = 1
    bool wasGrouped = false;
    while (node && node->children.count() == 1)
    {
        GraphNode* child = node->children.begin().key();
        if (child->accumulatedCost != node->accumulatedCost || node->parents.size() != 1 || child->belowThresholdCount ) {
            break;
        }
        if (m_canceled) {
            return;
        }
        node = child;

        label += QLatin1String(" | ") + QString::fromUtf8(prettyLabel(node->item->label()));
        wasGrouped = true;
    }

    QString shape;
    if (wasGrouped) {
        label = QLatin1Char('{') + label + QLatin1Char('}');
        // <...> would be an id, escape it
        label = label.replace(QLatin1Char('<'), QLatin1String("\\<"));
        label = label.replace(QLatin1Char('>'), QLatin1String("\\>"));
        shape = QStringLiteral("record");
    } else {
        shape = QStringLiteral("box");
    }

    const QString color = isRoot ? QStringLiteral("white") : getColor(node->accumulatedCost, m_maxCost);
    out << '"' << nodeId << "\" [shape=" << shape << ",label=\"" << label << "\",fillcolor=\"" << color << "\"];\n";
    if (!node) {
        return;
    }

    QMultiHash< GraphNode*, quint64 >::const_iterator it = node->children.constBegin();
    while(it != node->children.constEnd()) {
        if (m_canceled) {
            return;
        }
        nodeToDot(it.key(), out, nodeId, it.value());
        ++it;
    }
    // handle below-threshold
    if (node->belowThresholdCount) {
        // node
        const QString btLabel = i18np("in one place below threshold", "in %1 places, all below threshold", node->belowThresholdCount);
        out << '"' << nodeId << "-bt\" [shape=box,label=\"" << btLabel
            << "\",fillcolor=\"" << getColor(node->belowThresholdCost, m_maxCost) << "\"];\n";
        // edge
        out << '"' << nodeId << "-bt\" -> \"" << nodeId << "\" [label =\"" << prettyCost(node->belowThresholdCost) << "\"];\n";
    }
}

QString DotGraphGenerator::outputFile() const
{
    return m_file.fileName();
}

QString DotGraphGenerator::mostCostIntensiveGraphvizId() const
{
    return m_costlyGraphvizId;
}