diff --git a/app/documentwidget.cpp b/app/documentwidget.cpp index 0011a79..9cc1969 100644 --- a/app/documentwidget.cpp +++ b/app/documentwidget.cpp @@ -1,612 +1,611 @@ /* This file is part of Massif Visualizer Copyright 2010 Milian Wolff Copyright 2013 Arnold Dumas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 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 14 of version 3 of the license. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "documentwidget.h" #include #include "KDChartChart" #include "KDChartGridAttributes" #include "KDChartHeaderFooter" #include "KDChartCartesianCoordinatePlane" #include "KDChartLegend" #include "KDChartDataValueAttributes" #include "KDChartBackgroundAttributes" #include "KDChartPlotter" #include "massifdata/filedata.h" #include "massifdata/parser.h" #include "massifdata/parseworker.h" #include "massifdata/snapshotitem.h" #include "massifdata/treeleafitem.h" #include "massifdata/util.h" #include "visualizer/totalcostmodel.h" #include "visualizer/detailedcostmodel.h" #include "visualizer/datatreemodel.h" #include "visualizer/filtereddatatreemodel.h" #include "visualizer/dotgraphgenerator.h" #include #include #include #include #include #include // forward include not available until later KDE versions... #include #include -#include #include #include #include #include #include #include #include #ifdef HAVE_KGRAPHVIEWER #include #endif using namespace Massif; using namespace KDChart; static void markPeak(Plotter* p, const QModelIndex& peak, quint64 cost, const QPen& foreground) { DataValueAttributes dataAttributes = p->dataValueAttributes(peak); dataAttributes.setDataLabel(prettyCost(cost)); dataAttributes.setVisible(true); MarkerAttributes a = dataAttributes.markerAttributes(); a.setMarkerSize(QSizeF(2, 2)); a.setPen(foreground); a.setMarkerStyle(MarkerAttributes::MarkerCircle); a.setVisible(true); dataAttributes.setMarkerAttributes(a); TextAttributes txtAttrs = dataAttributes.textAttributes(); txtAttrs.setPen(foreground); txtAttrs.setFontSize(Measure(12)); dataAttributes.setTextAttributes(txtAttrs); BackgroundAttributes bkgAtt = dataAttributes.backgroundAttributes(); QBrush brush = p->model()->data(peak, DatasetBrushRole).value(); QColor c = brush.color(); c.setAlpha(127); brush.setColor(c); bkgAtt.setBrush(brush); bkgAtt.setVisible(true); dataAttributes.setBackgroundAttributes(bkgAtt); p->setDataValueAttributes(peak, dataAttributes); } DocumentWidget::DocumentWidget(QWidget* parent) : QWidget(parent), m_chart(new Chart(this)) , m_header(new QLabel(this)) , m_totalDiagram(0) , m_totalCostModel(new TotalCostModel(m_chart)) , m_detailedDiagram(0) , m_detailedCostModel(new DetailedCostModel(m_chart)) , m_legend(new Legend(m_chart)) , m_dataTreeModel(new DataTreeModel(m_chart)) , m_dataTreeFilterModel(new FilteredDataTreeModel(m_dataTreeModel)) , m_data(0) , m_stackedWidget(new QStackedWidget(this)) , m_errorMessage(0) , m_loadingMessage(0) , m_loadingProgressBar(0) , m_stopParserButton(0) , m_isLoaded(false) #ifdef HAVE_KGRAPHVIEWER , m_graphViewerPart(0) , m_graphViewer(0) , m_dotGenerator(0) , m_displayTabWidget(0) #endif { // HACK: otherwise the legend becomes _really_ large and might even crash X... // to visualize the issue, try: m_chart->setMaximumSize(QSize(10000, 10000)); m_chart->setMaximumSize(qApp->desktop()->size()); m_chart->setContextMenuPolicy(Qt::CustomContextMenu); m_legend->setPosition(Position(KDChartEnums::PositionFloating)); m_legend->setTitleText(""); m_legend->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); m_legend->setSortOrder(Qt::DescendingOrder); m_chart->addLegend(m_legend); //NOTE: this has to be set _after_ the legend was added to the chart... TextAttributes att = m_legend->textAttributes(); att.setAutoShrink(true); att.setFontSize(Measure(12)); QFont font("monospace"); font.setStyleHint(QFont::TypeWriter); att.setFont(font); m_legend->setTextAttributes(att); m_legend->setTextAlignment(Qt::AlignLeft); m_legend->hide(); // Set m_stackedWidget as the main widget. setLayout(new QVBoxLayout(this)); layout()->addWidget(m_stackedWidget); QWidget* memoryConsumptionWidget = new QWidget; memoryConsumptionWidget->setLayout(new QVBoxLayout(memoryConsumptionWidget)); memoryConsumptionWidget->layout()->addWidget(m_header); memoryConsumptionWidget->layout()->addWidget(m_chart); #ifdef HAVE_KGRAPHVIEWER static KPluginFactory *factory = KPluginLoader("kgraphviewerpart").factory(); if (factory) { m_graphViewerPart = factory->create("kgraphviewerpart", this); if (m_graphViewerPart) { m_displayTabWidget = new QTabWidget(m_stackedWidget); m_displayTabWidget->setTabPosition(QTabWidget::South); m_displayTabWidget->addTab(memoryConsumptionWidget, i18n("&Evolution of Memory Consumption")); m_graphViewer = qobject_cast< KGraphViewer::KGraphViewerInterface* >(m_graphViewerPart); QWidget* dotGraphWidget = new QWidget(m_displayTabWidget); dotGraphWidget->setLayout(new QVBoxLayout); dotGraphWidget->layout()->addWidget(m_graphViewerPart->widget()); m_displayTabWidget->addTab(dotGraphWidget, i18n("&Detailed Snapshot Analysis")); m_stackedWidget->addWidget(m_displayTabWidget); connect(m_graphViewerPart, SIGNAL(graphLoaded()), this, SLOT(slotGraphLoaded())); connect(m_displayTabWidget, SIGNAL(currentChanged(int)), this, SLOT(slotTabChanged(int))); slotTabChanged(m_displayTabWidget->currentIndex()); } } if (!m_graphViewerPart) { m_stackedWidget->addWidget(memoryConsumptionWidget); } #else m_stackedWidget->addWidget(memoryConsumptionWidget); #endif // Second widget : loadingPage QWidget* loadingPage = new QWidget(m_stackedWidget); QVBoxLayout* verticalLayout = new QVBoxLayout(loadingPage); QSpacerItem* upperSpacerItem = new QSpacerItem(20, 247, QSizePolicy::Minimum, QSizePolicy::Expanding); verticalLayout->addItem(upperSpacerItem); m_loadingMessage = new QLabel(loadingPage); m_loadingMessage->setText(i18n("loading...")); m_loadingMessage->setAlignment(Qt::AlignCenter); verticalLayout->addWidget(m_loadingMessage); m_loadingProgressBar = new QProgressBar(loadingPage); m_loadingProgressBar->setValue(24); m_loadingProgressBar->setRange(0, 0); verticalLayout->addWidget(m_loadingProgressBar); QWidget* stopParserWidget = new QWidget(loadingPage); stopParserWidget->setLayoutDirection(Qt::LeftToRight); QHBoxLayout* stopParserWidgetLayout = new QHBoxLayout(stopParserWidget); m_stopParserButton = new QToolButton(stopParserWidget); m_stopParserButton->setObjectName(QString::fromUtf8("stopParsing")); m_stopParserButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); m_stopParserButton->setIcon(KIcon("process-stop")); m_stopParserButton->setIconSize(QSize(48, 48)); connect(m_stopParserButton, SIGNAL(clicked()), this, SIGNAL(stopParser())); stopParserWidgetLayout->addWidget(m_stopParserButton); verticalLayout->addWidget(stopParserWidget); QSpacerItem* bottomSpacerItem = new QSpacerItem(20, 230, QSizePolicy::Minimum, QSizePolicy::Expanding); verticalLayout->addItem(bottomSpacerItem); m_stackedWidget->addWidget(loadingPage); // By default we show the loadingPage. m_stackedWidget->setCurrentIndex(1); } DocumentWidget::~DocumentWidget() { if (m_data) { #ifdef HAVE_KGRAPHVIEWER if (m_dotGenerator) { if (m_dotGenerator->isRunning()) { disconnect(m_dotGenerator.data(), 0, this, 0); connect(m_dotGenerator.data(), SIGNAL(finished()), m_dotGenerator.data(), SLOT(deleteLater())); m_dotGenerator->cancel(); m_dotGenerator.take(); } m_dotGenerator.reset(); } if (m_graphViewer) { m_graphViewerPart->closeUrl(); } m_lastDotItem.first = 0; m_lastDotItem.second = 0; #endif m_chart->replaceCoordinatePlane(new CartesianCoordinatePlane); m_legend->removeDiagrams(); foreach(CartesianAxis* axis, m_detailedDiagram->axes()) { m_detailedDiagram->takeAxis(axis); delete axis; } m_detailedDiagram->deleteLater(); m_detailedDiagram = 0; foreach(CartesianAxis* axis, m_totalDiagram->axes()) { m_totalDiagram->takeAxis(axis); delete axis; } m_totalDiagram->deleteLater(); m_totalDiagram = 0; m_dataTreeModel->setSource(0); m_dataTreeFilterModel->setFilter(""); m_detailedCostModel->setSource(0); m_totalCostModel->setSource(0); delete m_data; m_data = 0; m_file.clear(); } } QUrl DocumentWidget::file() const { return m_file; } FileData* DocumentWidget::data() const { return m_data; } Chart* DocumentWidget::chart() const { return m_chart; } Plotter* DocumentWidget::totalDiagram() const { return m_totalDiagram; } TotalCostModel* DocumentWidget::totalCostModel() const { return m_totalCostModel; } Plotter* DocumentWidget::detailedDiagram() const { return m_detailedDiagram; } DetailedCostModel* DocumentWidget::detailedCostModel() const { return m_detailedCostModel; } DataTreeModel* DocumentWidget::dataTreeModel() const { return m_dataTreeModel; } FilteredDataTreeModel* DocumentWidget::dataTreeFilterModel() const { return m_dataTreeFilterModel; } #ifdef HAVE_KGRAPHVIEWER KGraphViewer::KGraphViewerInterface* DocumentWidget::graphViewer() { return m_graphViewer; } void DocumentWidget::focusExpensiveGraphNode() { Q_ASSERT(m_graphViewer); Q_ASSERT(m_dotGenerator); m_graphViewer->centerOnNode(m_dotGenerator->mostCostIntensiveGraphvizId()); } int DocumentWidget::currentIndex() { if (!m_displayTabWidget) { // happens when kgraphviewer part is not available at runtime return 0; } return m_displayTabWidget->currentIndex(); } #endif bool DocumentWidget::isLoaded() const { return m_isLoaded; } void DocumentWidget::parserFinished(const QUrl &file, FileData* data) { Q_ASSERT(data->peak()); // give the progress bar one last chance to update QApplication::processEvents(); - kDebug() << "loaded massif file:" << file; + qDebug() << "loaded massif file:" << file; qDebug() << "description:" << data->description(); qDebug() << "command:" << data->cmd(); qDebug() << "time unit:" << data->timeUnit(); qDebug() << "snapshots:" << data->snapshots().size(); qDebug() << "peak: snapshot #" << data->peak()->number() << "after" << QString("%1%2").arg(data->peak()->time()).arg(data->timeUnit()); qDebug() << "peak cost:" << prettyCost(data->peak()->memHeap()) << " heap" << prettyCost(data->peak()->memHeapExtra()) << " heap extra" << prettyCost(data->peak()->memStacks()) << " stacks"; m_data = data; m_file = file; #ifdef HAVE_KGRAPHVIEWER if (m_graphViewer) { showDotGraph(QPair(0, m_data->peak())); } #endif //BEGIN KDChart KColorScheme scheme(QPalette::Active, KColorScheme::Window); QPen foreground(scheme.foreground().color()); //Begin Legend BackgroundAttributes bkgAtt = m_legend->backgroundAttributes(); QColor background = scheme.background(KColorScheme::AlternateBackground).color(); background.setAlpha(200); bkgAtt.setBrush(QBrush(background)); bkgAtt.setVisible(true); m_legend->setBackgroundAttributes(bkgAtt); TextAttributes txtAttrs = m_legend->textAttributes(); txtAttrs.setPen(foreground); m_legend->setTextAttributes(txtAttrs); m_header->setAlignment(Qt::AlignCenter); updateHeader(); //BEGIN TotalDiagram m_totalDiagram = new Plotter; m_totalDiagram->setAntiAliasing(true); CartesianAxis* bottomAxis = new CartesianAxis(m_totalDiagram); TextAttributes axisTextAttributes = bottomAxis->textAttributes(); axisTextAttributes.setPen(foreground); axisTextAttributes.setFontSize(Measure(10)); bottomAxis->setTextAttributes(axisTextAttributes); TextAttributes axisTitleTextAttributes = bottomAxis->titleTextAttributes(); axisTitleTextAttributes.setPen(foreground); axisTitleTextAttributes.setFontSize(Measure(12)); bottomAxis->setTitleTextAttributes(axisTitleTextAttributes); bottomAxis->setTitleText(i18n("time in %1", m_data->timeUnit())); bottomAxis->setPosition ( CartesianAxis::Bottom ); m_totalDiagram->addAxis(bottomAxis); CartesianAxis* rightAxis = new CartesianAxis(m_totalDiagram); rightAxis->setTextAttributes(axisTextAttributes); rightAxis->setTitleTextAttributes(axisTitleTextAttributes); rightAxis->setTitleText(i18n("memory heap size in kilobytes")); rightAxis->setPosition ( CartesianAxis::Right ); m_totalDiagram->addAxis(rightAxis); m_totalCostModel->setSource(m_data); m_totalDiagram->setModel(m_totalCostModel); CartesianCoordinatePlane* coordinatePlane = dynamic_cast(m_chart->coordinatePlane()); Q_ASSERT(coordinatePlane); coordinatePlane->addDiagram(m_totalDiagram); GridAttributes gridAttributes = coordinatePlane->gridAttributes(Qt::Horizontal); gridAttributes.setAdjustBoundsToGrid(false, false); coordinatePlane->setGridAttributes(Qt::Horizontal, gridAttributes); m_legend->addDiagram(m_totalDiagram); m_detailedDiagram = new Plotter; m_detailedDiagram->setAntiAliasing(true); m_detailedDiagram->setType(Plotter::Stacked); m_detailedCostModel->setSource(m_data); m_detailedDiagram->setModel(m_detailedCostModel); updatePeaks(); coordinatePlane->addDiagram(m_detailedDiagram); m_legend->addDiagram(m_detailedDiagram); m_legend->show(); m_dataTreeModel->setSource(m_data); m_isLoaded = true; // Switch to the display page and notify that everything is setup. m_stackedWidget->setCurrentIndex(0); emit loadingFinished(); } void DocumentWidget::setDetailedDiagramHidden(bool hidden) { m_detailedDiagram->setHidden(hidden); } void DocumentWidget::setDetailedDiagramVisible(bool visible) { m_detailedDiagram->setVisible(visible); } void DocumentWidget::setTotalDiagramHidden(bool hidden) { m_totalDiagram->setHidden(hidden); } void DocumentWidget::setTotalDiagramVisible(bool visible) { m_totalDiagram->setVisible(visible); } void DocumentWidget::setProgress(int value) { m_loadingProgressBar->setValue(value); } void DocumentWidget::setRange(int minimum, int maximum) { m_loadingProgressBar->setRange(minimum, maximum); } void DocumentWidget::setLoadingMessage(const QString& message) { m_loadingMessage->setText(message); } void DocumentWidget::showError(const QString& title, const QString& error) { if (!m_errorMessage) { m_errorMessage = new KMessageWidget(m_stackedWidget); m_stackedWidget->addWidget(m_errorMessage); m_errorMessage->setWordWrap(true); m_errorMessage->setMessageType(KMessageWidget::Error); m_errorMessage->setCloseButtonVisible(false); } m_errorMessage->setText(QString("%1

%2

").arg(title).arg(error)); m_stackedWidget->setCurrentWidget(m_errorMessage); } void DocumentWidget::updateHeader() { const QString app = m_data->cmd().split(' ', QString::SkipEmptyParts).first(); m_header->setText(QString("%1
%2") .arg(i18n("Memory consumption of %1", app)) .arg(i18n("Peak of %1 at snapshot #%2", prettyCost(m_data->peak()->cost()), m_data->peak()->number())) ); m_header->setToolTip(i18n("Command: %1\nValgrind Options: %2", m_data->cmd(), m_data->description())); } void DocumentWidget::updatePeaks() { KColorScheme scheme(QPalette::Active, KColorScheme::Window); QPen foreground(scheme.foreground().color()); if (m_data->peak()) { const QModelIndex peak = m_totalCostModel->peak(); Q_ASSERT(peak.isValid()); markPeak(m_totalDiagram, peak, m_data->peak()->cost(), foreground); } updateDetailedPeaks(); } void DocumentWidget::updateDetailedPeaks() { KColorScheme scheme(QPalette::Active, KColorScheme::Window); QPen foreground(scheme.foreground().color()); QMap< QModelIndex, TreeLeafItem* > peaks = m_detailedCostModel->peaks(); QMap< QModelIndex, TreeLeafItem* >::const_iterator it = peaks.constBegin(); while (it != peaks.constEnd()) { const QModelIndex peak = it.key(); Q_ASSERT(peak.isValid()); markPeak(m_detailedDiagram, peak, it.value()->cost(), foreground); ++it; } } #ifdef HAVE_KGRAPHVIEWER void DocumentWidget::slotTabChanged(int index) { emit tabChanged(index); if (index == 1) { // if we parsed a dot graph we might want to show it now showDotGraph(); } } void DocumentWidget::showDotGraph(const QPair& item) { if (item == m_lastDotItem) { return; } m_lastDotItem = item; Q_ASSERT(m_graphViewer); - kDebug() << "new dot graph requested" << item; + qDebug() << "new dot graph requested" << item; if (m_dotGenerator) { - kDebug() << "existing generator is running:" << m_dotGenerator->isRunning(); + qDebug() << "existing generator is running:" << m_dotGenerator->isRunning(); if (m_dotGenerator->isRunning()) { disconnect(m_dotGenerator.data(), 0, this, 0); connect(m_dotGenerator.data(), SIGNAL(finished()), m_dotGenerator.data(), SLOT(deleteLater())); m_dotGenerator->cancel(); m_dotGenerator.take(); } m_dotGenerator.reset(); } if (!item.first && !item.second) { return; } if (item.second) { m_dotGenerator.reset(new DotGraphGenerator(item.second, m_data->timeUnit(), this)); } else { m_dotGenerator.reset(new DotGraphGenerator(item.first, m_data->timeUnit(), this)); } m_dotGenerator->start(); connect(m_dotGenerator.data(), SIGNAL(finished()), this, SLOT(showDotGraph())); } void DocumentWidget::showDotGraph() { if (!m_dotGenerator || !m_graphViewerPart || !m_graphViewerPart->widget()->isVisible()) { return; } - kDebug() << "show dot graph in output file" << m_dotGenerator->outputFile(); + qDebug() << "show dot graph in output file" << m_dotGenerator->outputFile(); if (!m_dotGenerator->outputFile().isEmpty() && m_graphViewerPart->url() != QUrl(m_dotGenerator->outputFile())) { m_graphViewerPart->openUrl(QUrl(m_dotGenerator->outputFile())); } } void DocumentWidget::slotGraphLoaded() { Q_ASSERT(m_graphViewer); if (!m_dotGenerator) { return; } m_graphViewer->setZoomFactor(0.75); m_graphViewer->setPannerPosition(KGraphViewer::KGraphViewerInterface::BottomRight); m_graphViewer->setPannerEnabled(true); m_graphViewer->centerOnNode(m_dotGenerator->mostCostIntensiveGraphvizId()); } #endif diff --git a/visualizer/dotgraphgenerator.cpp b/visualizer/dotgraphgenerator.cpp index a1685cb..0da3c2e 100644 --- a/visualizer/dotgraphgenerator.cpp +++ b/visualizer/dotgraphgenerator.cpp @@ -1,320 +1,318 @@ /* This file is part of Massif Visualizer Copyright 2010 Milian Wolff 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 . */ #include "dotgraphgenerator.h" #include "massifdata/filedata.h" #include "massifdata/snapshotitem.h" #include "massifdata/treeleafitem.h" #include "massifdata/util.h" #include #include #include #include #include -#include - namespace Massif { struct GraphNode { const TreeLeafItem* item; // incoming calls + cost QHash children; // outgoing calls QVector 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() { - kDebug() << "closing generator, file will get removed"; + qDebug() << "closing generator, file will get removed"; } void DotGraphGenerator::cancel() { m_canceled = true; } QString getLabel(const TreeLeafItem* node) { QString 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 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& knownNodes, quint64& maxCost, GraphNode* parent = 0) { // merge below-threshold items if (parent && item->children().isEmpty()) { static QRegExp matchBT("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()) { - kWarning() << "could not create temp file for writing Dot-graph"; + qWarning() << "could not create temp file for writing Dot-graph"; return; } if (m_canceled) { return; } - kDebug() << "creating new dot file in" << m_file.fileName(); + 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 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 += " | " + prettyLabel(node->item->label()); wasGrouped = true; } QString shape; if (wasGrouped) { label = "{" + label + "}"; // <...> would be an id, escape it label = label.replace('<', "\\<"); label = label.replace('>', "\\>"); shape = "record"; } else { shape = "box"; } const QString color = isRoot ? "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; }