diff --git a/app/allocatorstab.h b/app/allocatorstab.h index 635ba0a..37b27ce 100644 --- a/app/allocatorstab.h +++ b/app/allocatorstab.h @@ -1,57 +1,57 @@ /* This file is part of Massif Visualizer Copyright 2014 Milian Wolff 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 . */ #ifndef ALLOCATORSTAB_H #define ALLOCATORSTAB_H #include "documenttabinterface.h" class QTreeView; class QSortFilterProxyModel; class QModelIndex; namespace Massif { class AllocatorsModel; } class AllocatorsTab : public DocumentTabInterface { Q_OBJECT public: - explicit AllocatorsTab(const Massif::FileData* data, KXMLGUIClient* guiParent, QWidget* parent = 0); + explicit AllocatorsTab(const Massif::FileData* data, KXMLGUIClient* guiParent, QWidget* parent = nullptr); ~AllocatorsTab() override; void selectModelItem(const Massif::ModelItem& item) override; void settingsChanged() override; private Q_SLOTS: void selectionChanged(const QModelIndex& current, const QModelIndex& previous); void customContextMenuRequested(const QPoint& pos); private: Massif::AllocatorsModel* m_model; QSortFilterProxyModel* m_proxy; QTreeView* m_view; }; #endif // ALLOCATORSTAB_H diff --git a/app/callgraphtab.cpp b/app/callgraphtab.cpp index 562c3f4..6ddc3f7 100644 --- a/app/callgraphtab.cpp +++ b/app/callgraphtab.cpp @@ -1,193 +1,193 @@ /* This file is part of Massif Visualizer Copyright 2014 Milian Wolff 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 "callgraphtab.h" #include "visualizer/dotgraphgenerator.h" #include "massifdata/filedata.h" #include #include #include #include #include #include #include #include using namespace Massif; CallGraphTab::CallGraphTab(const FileData* data, KParts::ReadOnlyPart* graphViewerPart, KXMLGUIClient* guiParent, QWidget* parent) : DocumentTabInterface(data, guiParent, parent) , m_graphViewerPart(graphViewerPart) , m_graphViewer(qobject_cast(m_graphViewerPart)) - , m_dotGenerator(0) - , m_zoomIn(0) - , m_zoomOut(0) - , m_focusExpensive(0) + , m_dotGenerator(nullptr) + , m_zoomIn(nullptr) + , m_zoomOut(nullptr) + , m_focusExpensive(nullptr) { setXMLFile(QStringLiteral("callgraphtabui.rc"), true); setupActions(); Q_ASSERT(m_graphViewer); setLayout(new QVBoxLayout); layout()->addWidget(m_graphViewerPart->widget()); connect(m_graphViewerPart, SIGNAL(graphLoaded()), this, SLOT(slotGraphLoaded())); - showDotGraph(ModelItem(0, data->peak())); + showDotGraph(ModelItem(nullptr, data->peak())); } CallGraphTab::~CallGraphTab() { if (m_dotGenerator) { if (m_dotGenerator->isRunning()) { - disconnect(m_dotGenerator.data(), 0, this, 0); + disconnect(m_dotGenerator.data(), nullptr, this, nullptr); 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; + m_lastDotItem.first = nullptr; + m_lastDotItem.second = nullptr; } void CallGraphTab::setupActions() { m_zoomIn = KStandardAction::zoomIn(this, SLOT(zoomIn()), actionCollection()); actionCollection()->addAction(QStringLiteral("zoomIn"), m_zoomIn); m_zoomOut = KStandardAction::zoomOut(this, SLOT(zoomOut()), actionCollection()); actionCollection()->addAction(QStringLiteral("zoomOut"), m_zoomOut); m_focusExpensive = new QAction(QIcon::fromTheme(QStringLiteral("flag-red")), i18n("Focus most expensive node"), actionCollection()); connect(m_focusExpensive, SIGNAL(triggered()), this, SLOT(focusExpensiveGraphNode())); actionCollection()->addAction(QStringLiteral("focusExpensive"), m_focusExpensive); } void CallGraphTab::settingsChanged() { } void CallGraphTab::focusExpensiveGraphNode() { Q_ASSERT(m_graphViewer); Q_ASSERT(m_dotGenerator); m_graphViewer->centerOnNode(m_dotGenerator->mostCostIntensiveGraphvizId()); } void CallGraphTab::showDotGraph(const ModelItem& item) { m_nextDotItem = item; if (item == m_lastDotItem && m_graphViewerPart->url().isValid()) { return; } if (!isVisible()) { return; } m_lastDotItem = item; Q_ASSERT(m_graphViewer); qDebug() << "new dot graph requested" << item; if (m_dotGenerator) { qDebug() << "existing generator is running:" << m_dotGenerator->isRunning(); if (m_dotGenerator->isRunning()) { - disconnect(m_dotGenerator.data(), 0, this, 0); + disconnect(m_dotGenerator.data(), nullptr, this, nullptr); 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 CallGraphTab::setVisible(bool visible) { QWidget::setVisible(visible); if (visible) { showDotGraph(m_nextDotItem); } } void CallGraphTab::showDotGraph() { if (!m_dotGenerator || !m_graphViewerPart || !isVisible()) { return; } qDebug() << "show dot graph in output file" << m_dotGenerator->outputFile(); const auto url = QUrl::fromLocalFile(m_dotGenerator->outputFile()); if (url.isValid() && m_graphViewerPart->url() != url) { m_graphViewerPart->openUrl(url); } } void CallGraphTab::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()); } void CallGraphTab::zoomIn() { m_graphViewer->zoomIn(); } void CallGraphTab::zoomOut() { m_graphViewer->zoomOut(); } void CallGraphTab::selectModelItem(const ModelItem& item) { showDotGraph(item); } diff --git a/app/callgraphtab.h b/app/callgraphtab.h index fa68d56..d96be1d 100644 --- a/app/callgraphtab.h +++ b/app/callgraphtab.h @@ -1,84 +1,84 @@ /* This file is part of Massif Visualizer Copyright 2014 Milian Wolff 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 . */ #ifndef CALLGRAPHTAB_H #define CALLGRAPHTAB_H #include "documenttabinterface.h" #include class QAction; namespace KGraphViewer { class KGraphViewerInterface; } namespace Massif { class DotGraphGenerator; } namespace KParts { class ReadOnlyPart; } class CallGraphTab : public DocumentTabInterface { Q_OBJECT public: CallGraphTab(const Massif::FileData* data, KParts::ReadOnlyPart* graphViewerPart, - KXMLGUIClient* guiParent, QWidget* parent = 0); + KXMLGUIClient* guiParent, QWidget* parent = nullptr); ~CallGraphTab() override; void showDotGraph(const Massif::ModelItem& item); void settingsChanged() override; void selectModelItem(const Massif::ModelItem& item) override; void setVisible(bool visible) override; public slots: void showDotGraph(); private slots: void slotGraphLoaded(); void zoomIn(); void zoomOut(); void focusExpensiveGraphNode(); private: void setupActions(); private: KParts::ReadOnlyPart* m_graphViewerPart; KGraphViewer::KGraphViewerInterface* m_graphViewer; QScopedPointer m_dotGenerator; Massif::ModelItem m_lastDotItem; Massif::ModelItem m_nextDotItem; QAction* m_zoomIn; QAction* m_zoomOut; QAction* m_focusExpensive; }; #endif // CALLGRAPHTAB_H diff --git a/app/charttab.cpp b/app/charttab.cpp index 1c3c05f..a2c2ed2 100644 --- a/app/charttab.cpp +++ b/app/charttab.cpp @@ -1,619 +1,619 @@ /* This file is part of Massif Visualizer Copyright 2014 Milian Wolff 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 "charttab.h" #include "KChartChart" #include "KChartGridAttributes" #include "KChartHeaderFooter" #include "KChartCartesianCoordinatePlane" #include "KChartPlotter" #include "KChartLegend" #include "KChartDataValueAttributes" #include "KChartBackgroundAttributes" #include #include "visualizer/totalcostmodel.h" #include "visualizer/detailedcostmodel.h" #include "visualizer/datatreemodel.h" #include "visualizer/filtereddatatreemodel.h" #include "massifdata/util.h" #include "massifdata/snapshotitem.h" #include "massifdata/treeleafitem.h" #include "massifdata/filedata.h" #include "massif-visualizer-settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KChart; using namespace Massif; namespace { class TimeAxis : public CartesianAxis { Q_OBJECT public: - explicit TimeAxis(AbstractCartesianDiagram* diagram = 0) + explicit TimeAxis(AbstractCartesianDiagram* diagram = nullptr) : CartesianAxis(diagram) {} const QString customizedLabel(const QString& label) const override { // squeeze large numbers here // TODO: when the unit is 'b' also use prettyCost() here return QString::number(label.toDouble()); } }; class SizeAxis : public CartesianAxis { Q_OBJECT public: - explicit SizeAxis(AbstractCartesianDiagram* diagram = 0) + explicit SizeAxis(AbstractCartesianDiagram* diagram = nullptr) : CartesianAxis(diagram) {} const QString customizedLabel(const QString& label) const override { // TODO: change distance between labels to 1024 and simply use prettyCost() here KFormat format(QLocale::system()); return format.formatByteSize(label.toDouble(), 1, KFormat::MetricBinaryDialect); } }; void markPeak(Plotter* p, const QModelIndex& peak, quint64 cost, const KColorScheme& scheme) { QBrush brush = p->model()->data(peak, DatasetBrushRole).value(); QColor outline = brush.color(); QColor foreground = scheme.foreground().color(); QBrush background = scheme.background(); DataValueAttributes dataAttributes = p->dataValueAttributes(peak); dataAttributes.setDataLabel(prettyCost(cost)); dataAttributes.setVisible(true); dataAttributes.setShowRepetitiveDataLabels(true); dataAttributes.setShowOverlappingDataLabels(false); FrameAttributes frameAttrs = dataAttributes.frameAttributes(); QPen framePen(outline); framePen.setWidth(2); frameAttrs.setPen(framePen); frameAttrs.setVisible(true); dataAttributes.setFrameAttributes(frameAttrs); MarkerAttributes a = dataAttributes.markerAttributes(); a.setMarkerSize(QSizeF(7, 7)); a.setPen(outline); a.setMarkerStyle(KChart::MarkerAttributes::MarkerDiamond); a.setVisible(true); dataAttributes.setMarkerAttributes(a); TextAttributes txtAttrs = dataAttributes.textAttributes(); txtAttrs.setPen(foreground); txtAttrs.setFontSize(Measure(12)); dataAttributes.setTextAttributes(txtAttrs); BackgroundAttributes bkgAtt = dataAttributes.backgroundAttributes(); bkgAtt.setBrush(background); bkgAtt.setVisible(true); dataAttributes.setBackgroundAttributes(bkgAtt); p->setDataValueAttributes(peak, dataAttributes); } } ChartTab::ChartTab(const FileData* data, KXMLGUIClient* guiParent, QWidget* parent) : DocumentTabInterface(data, guiParent, parent) , m_chart(new Chart(this)) , m_header(new QLabel(this)) - , m_totalDiagram(0) + , m_totalDiagram(nullptr) , m_totalCostModel(new TotalCostModel(m_chart)) - , m_detailedDiagram(0) + , m_detailedDiagram(nullptr) , m_detailedCostModel(new DetailedCostModel(m_chart)) , m_legend(new Legend(m_chart)) - , m_print(0) - , m_saveAs(0) - , m_toggleTotal(0) - , m_toggleDetailed(0) - , m_hideFunction(0) - , m_hideOtherFunctions(0) + , m_print(nullptr) + , m_saveAs(nullptr) + , m_toggleTotal(nullptr) + , m_toggleDetailed(nullptr) + , m_hideFunction(nullptr) + , m_hideOtherFunctions(nullptr) , m_box(new QSpinBox(this)) , m_settingSelection(false) { setXMLFile(QStringLiteral("charttabui.rc"), true); setupActions(); setLayout(new QVBoxLayout(this)); setupGui(); } ChartTab::~ChartTab() { } void ChartTab::setupActions() { m_print = KStandardAction::print(this, SLOT(showPrintPreviewDialog()), actionCollection()); actionCollection()->addAction(QStringLiteral("file_print"), m_print); m_saveAs = KStandardAction::saveAs(this, SLOT(saveCurrentDocument()), actionCollection()); actionCollection()->addAction(QStringLiteral("file_save_as"), m_saveAs); m_toggleTotal = new QAction(QIcon::fromTheme(QStringLiteral("office-chart-area")), i18n("Toggle total cost graph"), actionCollection()); m_toggleTotal->setCheckable(true); m_toggleTotal->setChecked(true); connect(m_toggleTotal, &QAction::toggled, this, &ChartTab::showTotalGraph); actionCollection()->addAction(QStringLiteral("toggle_total"), m_toggleTotal); m_toggleDetailed = new QAction(QIcon::fromTheme(QStringLiteral("office-chart-area-stacked")), i18n("Toggle detailed cost graph"), actionCollection()); m_toggleDetailed->setCheckable(true); m_toggleDetailed->setChecked(true); connect(m_toggleDetailed, &QAction::toggled, this, &ChartTab::showDetailedGraph); actionCollection()->addAction(QStringLiteral("toggle_detailed"), m_toggleDetailed); QWidgetAction* stackNumAction = new QWidgetAction(actionCollection()); actionCollection()->addAction(QStringLiteral("stackNum"), stackNumAction); stackNumAction->setText(i18n("Stacked diagrams")); QWidget *stackNumWidget = new QWidget; QHBoxLayout* stackNumLayout = new QHBoxLayout; stackNumLayout->addWidget(new QLabel(i18n("Stacked diagrams:"))); m_box->setMinimum(0); m_box->setMaximum(50); m_box->setValue(10); connect(m_box, static_cast(&QSpinBox::valueChanged), this, &ChartTab::setStackNum); stackNumLayout->addWidget(m_box); stackNumWidget->setLayout(stackNumLayout); stackNumAction->setDefaultWidget(stackNumWidget); m_hideFunction = new QAction(i18n("hide function"), this); connect(m_hideFunction, &QAction::triggered, this, &ChartTab::slotHideFunction); m_hideOtherFunctions = new QAction(i18n("hide other functions"), this); connect(m_hideOtherFunctions, &QAction::triggered, this, &ChartTab::slotHideOtherFunctions); } void ChartTab::setupGui() { layout()->addWidget(m_header); layout()->addWidget(m_chart); // 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()); // for axis labels to fit m_chart->setGlobalLeadingRight(10); m_chart->setGlobalLeadingLeft(10); m_chart->setGlobalLeadingTop(20); m_chart->setContextMenuPolicy(Qt::CustomContextMenu); updateLegendPosition(); m_legend->setTitleText(QString()); m_legend->setSortOrder(Qt::DescendingOrder); m_chart->addLegend(m_legend); //NOTE: this has to be set _after_ the legend was added to the chart... updateLegendFont(); m_legend->setTextAlignment(Qt::AlignLeft); m_legend->hide(); connect(m_chart, &Chart::customContextMenuRequested, this, &ChartTab::chartContextMenuRequested); //BEGIN KChart 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(this); m_totalDiagram->setAntiAliasing(true); CartesianAxis* bottomAxis = new TimeAxis(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 SizeAxis(m_totalDiagram); rightAxis->setTextAttributes(axisTextAttributes); rightAxis->setTitleTextAttributes(axisTitleTextAttributes); rightAxis->setTitleText(i18n("memory heap size")); 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(KChart::Plotter::Stacked); m_detailedCostModel->setSource(m_data); m_detailedDiagram->setModel(m_detailedCostModel); updatePeaks(); m_chart->coordinatePlane()->addDiagram(m_detailedDiagram); m_legend->addDiagram(m_detailedDiagram); m_legend->show(); m_box->setValue(m_detailedCostModel->maximumDatasetCount()); connect(m_totalDiagram, &Plotter::clicked, this, &ChartTab::totalItemClicked); connect(m_detailedDiagram, &Plotter::clicked, this, &ChartTab::detailedItemClicked); } void ChartTab::settingsChanged() { updateHeader(); updatePeaks(); updateLegendPosition(); updateLegendFont(); } void ChartTab::setDetailedDiagramHidden(bool hidden) { m_detailedDiagram->setHidden(hidden); } void ChartTab::setDetailedDiagramVisible(bool visible) { m_detailedDiagram->setVisible(visible); } void ChartTab::setTotalDiagramHidden(bool hidden) { m_totalDiagram->setHidden(hidden); } void ChartTab::setTotalDiagramVisible(bool visible) { m_totalDiagram->setVisible(visible); } void ChartTab::updatePeaks() { KColorScheme scheme(QPalette::Active, KColorScheme::Window); if (m_data->peak()) { const QModelIndex peak = m_totalCostModel->peak(); Q_ASSERT(peak.isValid()); markPeak(m_totalDiagram, peak, m_data->peak()->cost(), scheme); } updateDetailedPeaks(); } void ChartTab::updateLegendPosition() { KChartEnums::PositionValue pos; switch (Settings::self()->legendPosition()) { case Settings::EnumLegendPosition::North: pos = KChartEnums::PositionNorth; break; case Settings::EnumLegendPosition::South: pos = KChartEnums::PositionSouth; break; case Settings::EnumLegendPosition::East: pos = KChartEnums::PositionEast; break; case Settings::EnumLegendPosition::West: pos = KChartEnums::PositionWest; break; case Settings::EnumLegendPosition::Floating: pos = KChartEnums::PositionFloating; break; default: pos = KChartEnums::PositionFloating; qDebug() << "invalid legend position"; } m_legend->setPosition(Position(pos)); Qt::Alignment align; switch (Settings::self()->legendAlignment()) { case Settings::EnumLegendAlignment::Left: align = Qt::AlignLeft; break; case Settings::EnumLegendAlignment::Center: align = Qt::AlignHCenter | Qt::AlignVCenter; break; case Settings::EnumLegendAlignment::Right: align = Qt::AlignRight; break; case Settings::EnumLegendAlignment::Top: align = Qt::AlignTop; break; case Settings::EnumLegendAlignment::Bottom: align = Qt::AlignBottom; break; default: align = Qt::AlignHCenter | Qt::AlignVCenter; qDebug() << "invalid legend alignmemnt"; } // do something reasonable since top,bottom have no effect // when used with north,south, same for left,right used with // east,west if ((((pos == KChartEnums::PositionNorth) || (pos == KChartEnums::PositionSouth)) && ((align == Qt::AlignTop) || (align == Qt::AlignBottom))) || (((pos == KChartEnums::PositionEast) || (pos == KChartEnums::PositionWest)) && ((align == Qt::AlignLeft) || (align == Qt::AlignRight)))) { align = Qt::AlignHCenter | Qt::AlignVCenter; } m_legend->setAlignment(align); } void ChartTab::updateLegendFont() { TextAttributes att = m_legend->textAttributes(); att.setAutoShrink(true); att.setFontSize(Measure(Settings::self()->legendFontSize())); QFont font(QStringLiteral("monospace")); font.setStyleHint(QFont::TypeWriter); att.setFont(font); m_legend->setTextAttributes(att); } void ChartTab::updateDetailedPeaks() { KColorScheme scheme(QPalette::Active, KColorScheme::Window); const DetailedCostModel::Peaks& peaks = m_detailedCostModel->peaks(); DetailedCostModel::Peaks::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(), scheme); ++it; } } void ChartTab::updateHeader() { const QString app = m_data->cmd().split(QLatin1Char(' '), QString::SkipEmptyParts).first(); m_header->setText(QString::fromLatin1("%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 ChartTab::saveCurrentDocument() { const auto saveFilename = QFileDialog::getSaveFileName(this, i18n("Save Current Visualization"), QString(), i18n("Images (*.png *.jpg *.tiff *.svg)")); if (!saveFilename.isEmpty()) { // TODO: implement a dialog to expose more options to the user. // for example we could expose dpi, size, and various format // dependent options such as compressions settings. // Vector graphic format if (QFileInfo(saveFilename).suffix().compare(QLatin1String("svg")) == 0) { QSvgGenerator generator; generator.setFileName(saveFilename); generator.setSize(m_chart->size()); generator.setViewBox(m_chart->rect()); QPainter painter; painter.begin(&generator); m_chart->paint(&painter, m_chart->rect()); painter.end(); } // Other format else if (!QPixmap::grabWidget(m_chart).save(saveFilename)) { KMessageBox::sorry(this, i18n("Error, failed to save the image to %1.", saveFilename)); } } } void ChartTab::showPrintPreviewDialog() { QPrinter printer; QPrintPreviewDialog *ppd = new QPrintPreviewDialog(&printer, this); ppd->setAttribute(Qt::WA_DeleteOnClose); connect(ppd, &QPrintPreviewDialog::paintRequested, this, &ChartTab::printFile); ppd->setWindowTitle(i18n("Massif Chart Print Preview")); ppd->resize(800, 600); ppd->exec(); } void ChartTab::printFile(QPrinter *printer) { QPainter painter; painter.begin(printer); m_chart->paint(&painter, printer->pageRect()); painter.end(); } void ChartTab::showDetailedGraph(bool show) { m_detailedDiagram->setHidden(!show); m_toggleDetailed->setChecked(show); m_chart->update(); } void ChartTab::showTotalGraph(bool show) { m_totalDiagram->setHidden(!show); m_toggleTotal->setChecked(show); m_chart->update(); } void ChartTab::slotHideFunction() { m_detailedCostModel->hideFunction(m_hideFunction->data().value()); } void ChartTab::slotHideOtherFunctions() { m_detailedCostModel->hideOtherFunctions(m_hideOtherFunctions->data().value()); } void ChartTab::chartContextMenuRequested(const QPoint& pos) { const QPoint dPos = m_detailedDiagram->mapFromGlobal(m_chart->mapToGlobal(pos)); const QModelIndex idx = m_detailedDiagram->indexAt(dPos); if (!idx.isValid()) { return; } // hack: the ToolTip will only be queried by KChart and that one uses the // left index, but we want it to query the right one const QModelIndex _idx = m_detailedCostModel->index(idx.row() + 1, idx.column(), idx.parent()); ModelItem item = m_detailedCostModel->itemForIndex(_idx); if (!item.first) { return; } QMenu menu; m_hideFunction->setData(QVariant::fromValue(item.first)); menu.addAction(m_hideFunction); m_hideOtherFunctions->setData(QVariant::fromValue(item.first)); menu.addAction(m_hideOtherFunctions); menu.addSeparator(); emit contextMenuRequested(item, &menu); menu.exec(m_detailedDiagram->mapToGlobal(dPos)); } void ChartTab::setStackNum(int num) { m_detailedCostModel->setMaximumDatasetCount(num); updatePeaks(); } void ChartTab::detailedItemClicked(const QModelIndex& idx) { m_detailedCostModel->setSelection(idx); m_totalCostModel->setSelection(QModelIndex()); m_chart->update(); // hack: the ToolTip will only be queried by KChart and that one uses the // left index, but we want it to query the right one m_settingSelection = true; const QModelIndex _idx = m_detailedCostModel->index(idx.row() + 1, idx.column(), idx.parent()); emit modelItemSelected(m_detailedCostModel->itemForIndex(_idx)); m_settingSelection = false; } void ChartTab::totalItemClicked(const QModelIndex& idx) { const QModelIndex _idx = m_totalCostModel->index(idx.row() + 1, idx.column(), idx.parent()); m_totalCostModel->setSelection(_idx); m_detailedCostModel->setSelection(QModelIndex()); m_chart->update(); m_settingSelection = true; emit modelItemSelected(m_totalCostModel->itemForIndex(_idx)); m_settingSelection = false; } void ChartTab::selectModelItem(const ModelItem& item) { if (m_settingSelection) { return; } if (item.first) { m_detailedCostModel->setSelection(m_detailedCostModel->indexForItem(item)); m_totalCostModel->setSelection(QModelIndex()); } else { m_totalCostModel->setSelection(m_totalCostModel->indexForItem(item)); m_detailedCostModel->setSelection(QModelIndex()); } m_chart->update(); } #include "charttab.moc" diff --git a/app/charttab.h b/app/charttab.h index ff0a01f..e0e0f82 100644 --- a/app/charttab.h +++ b/app/charttab.h @@ -1,123 +1,123 @@ /* This file is part of Massif Visualizer Copyright 2014 Milian Wolff 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 . */ #ifndef CHARTTAB_H #define CHARTTAB_H #include "documenttabinterface.h" #include #include class QAction; class QLabel; class QModelIndex; namespace KChart { class Chart; class HeaderFooter; class Plotter; class CartesianAxis; class Legend; class BarDiagram; } namespace Massif { class FileData; class TotalCostModel; class DetailedCostModel; class DataTreeModel; class FilteredDataTreeModel; } class ChartTab : public DocumentTabInterface { Q_OBJECT public: ChartTab(const Massif::FileData* data, - KXMLGUIClient* guiParent, QWidget* parent = 0); + KXMLGUIClient* guiParent, QWidget* parent = nullptr); ~ChartTab() override; void settingsChanged() override; void selectModelItem(const Massif::ModelItem& item) override; private: void setupGui(); void setupActions(); void updateHeader(); void updatePeaks(); void updateLegendPosition(); void updateLegendFont(); void updateDetailedPeaks(); void printFile(QPrinter *printer); private Q_SLOTS: void setDetailedDiagramHidden(bool hidden); void setDetailedDiagramVisible(bool visible); void setTotalDiagramHidden(bool hidden); void setTotalDiagramVisible(bool visible); void saveCurrentDocument(); void showPrintPreviewDialog(); void showDetailedGraph(bool show); void showTotalGraph(bool show); void setStackNum(int num); void chartContextMenuRequested(const QPoint &pos); void slotHideFunction(); void slotHideOtherFunctions(); void detailedItemClicked(const QModelIndex& item); void totalItemClicked(const QModelIndex& item); private: KChart::Chart* m_chart; QLabel* m_header; KChart::Plotter* m_totalDiagram; Massif::TotalCostModel* m_totalCostModel; KChart::Plotter* m_detailedDiagram; Massif::DetailedCostModel* m_detailedCostModel; KChart::Legend* m_legend; QAction* m_print; QAction* m_saveAs; QAction* m_toggleTotal; QAction* m_toggleDetailed; QAction* m_hideFunction; QAction* m_hideOtherFunctions; QSpinBox* m_box; bool m_settingSelection; }; #endif // CHARTTAB_H diff --git a/app/documenttabinterface.h b/app/documenttabinterface.h index ac70ab9..48523ca 100644 --- a/app/documenttabinterface.h +++ b/app/documenttabinterface.h @@ -1,59 +1,59 @@ /* This file is part of Massif Visualizer Copyright 2014 Milian Wolff 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 . */ #ifndef DOCUMENTTABINTERFACE_H #define DOCUMENTTABINTERFACE_H #include #include #include "visualizer/modelitem.h" class QMenu; namespace Massif { class FileData; } class DocumentTabInterface : public QWidget, public KXMLGUIClient { Q_OBJECT public: explicit DocumentTabInterface(const Massif::FileData* data, - KXMLGUIClient* guiParent, QWidget* parent = 0); + KXMLGUIClient* guiParent, QWidget* parent = nullptr); ~DocumentTabInterface() override; virtual void settingsChanged() = 0; public Q_SLOTS: virtual void selectModelItem(const Massif::ModelItem& item) = 0; Q_SIGNALS: void modelItemSelected(const Massif::ModelItem& item); void contextMenuRequested(const Massif::ModelItem& item, QMenu* menu); protected: const Massif::FileData* const m_data; }; #endif // DOCUMENTTABINTERFACE_H diff --git a/app/documentwidget.cpp b/app/documentwidget.cpp index 072a5a7..59c65a8 100644 --- a/app/documentwidget.cpp +++ b/app/documentwidget.cpp @@ -1,306 +1,306 @@ /* 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 "massifdata/filedata.h" #include "massifdata/parser.h" #include "massifdata/parseworker.h" #include "massifdata/snapshotitem.h" #include "massifdata/treeleafitem.h" #include "massifdata/util.h" #include #include #include // forward include not available until later KDE versions... #include #include #include #include #include #include #include #include #include #include #include "charttab.h" #include "allocatorstab.h" #ifdef HAVE_KGRAPHVIEWER #include "callgraphtab.h" #include #include #include #endif using namespace Massif; DocumentWidget::DocumentWidget(const QUrl& file, const QStringList& customAllocators, KXMLGUIClient* guiParent, QWidget* parent) : QWidget(parent) , KXMLGUIClient(guiParent) - , m_data(0) + , m_data(nullptr) , m_parseWorker(new ParseWorker) , m_file(file) - , m_currentTab(0) + , m_currentTab(nullptr) , m_stackedWidget(new QStackedWidget(this)) , m_tabs(new QTabWidget(m_stackedWidget)) - , m_errorMessage(0) - , m_loadingMessage(0) - , m_loadingProgressBar(0) - , m_stopParserButton(0) + , m_errorMessage(nullptr) + , m_loadingMessage(nullptr) + , m_loadingProgressBar(nullptr) + , m_stopParserButton(nullptr) , m_isLoaded(false) { connect(m_parseWorker, &ParseWorker::finished, this, &DocumentWidget::parserFinished); connect(m_parseWorker, &ParseWorker::error, this, &DocumentWidget::showError); connect(m_parseWorker, &ParseWorker::progressRange, this, &DocumentWidget::setRange); connect(m_parseWorker, &ParseWorker::progress, this, &DocumentWidget::setProgress); // Create dedicated thread for this document. // TODO: use ThreadWeaver QThread* thread = new QThread(this); thread->start(); m_parseWorker->moveToThread(thread); m_parseWorker->parse(file, customAllocators); setXMLFile(QStringLiteral("documentwidgetui.rc"), true); // Set m_stackedWidget as the main widget. setLayout(new QVBoxLayout(this)); layout()->addWidget(m_stackedWidget); m_tabs->setTabPosition(QTabWidget::South); m_stackedWidget->addWidget(m_tabs); // 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 file %1...", file.toString())); 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(QStringLiteral("stopParsing")); m_stopParserButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); m_stopParserButton->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); m_stopParserButton->setIconSize(QSize(48, 48)); connect(m_stopParserButton, &QToolButton::clicked, this, &DocumentWidget::requestClose); 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() { stopParser(); if (m_data) { delete m_data; - m_data = 0; + m_data = nullptr; m_file.clear(); } } FileData* DocumentWidget::data() const { return m_data; } QUrl DocumentWidget::file() const { return m_file; } bool DocumentWidget::isLoaded() const { return m_isLoaded; } void DocumentWidget::settingsChanged() { for (int i = 0; i < m_tabs->count(); ++i) { static_cast(m_tabs->widget(i))->settingsChanged(); } } void DocumentWidget::parserFinished(const QUrl& file, FileData* data) { Q_ASSERT(data->peak()); 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" << data->peak()->time() << 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; m_tabs->addTab(new ChartTab(m_data, this, this), QIcon::fromTheme(QStringLiteral("office-chart-area-stacked")), i18n("Memory Chart")); #ifdef HAVE_KGRAPHVIEWER static KPluginFactory *factory = KPluginLoader(QStringLiteral("kgraphviewerpart")).factory(); if (factory) { KParts::ReadOnlyPart* part = factory->create(QString(), this); if (part) { m_tabs->addTab(new CallGraphTab(m_data, part, this, this), QIcon::fromTheme(QStringLiteral("kgraphviewer")), i18n("Callgraph")); } } #endif m_tabs->addTab(new AllocatorsTab(m_data, this, this), QIcon::fromTheme(QStringLiteral("view-list-text")), i18n("Allocators")); for (int i = 0; i < m_tabs->count(); ++i) { DocumentTabInterface* tab = static_cast(m_tabs->widget(i)); connect(tab, &DocumentTabInterface::modelItemSelected, this, &DocumentWidget::modelItemSelected); connect(tab, &DocumentTabInterface::contextMenuRequested, this, &DocumentWidget::contextMenuRequested); } m_tabs->setCurrentIndex(0); connect(m_tabs, &QTabWidget::currentChanged, this, &DocumentWidget::slotTabChanged); slotTabChanged(0); m_isLoaded = true; // Switch to the display page and notify that everything is setup. m_stackedWidget->setCurrentIndex(0); emit loadingFinished(); } void DocumentWidget::addGuiActions(KXMLGUIFactory* factory) { factory->addClient(this); // ensure only the current tab's client is in the factory // otherwise the actions from the other tabs are visible const int current = m_tabs->currentIndex(); for (int i = 0; i < m_tabs->count(); ++i) { if (i != current) { factory->removeClient(static_cast(m_tabs->widget(i))); } } } void DocumentWidget::clearGuiActions(KXMLGUIFactory* factory) { factory->removeClient(this); } void DocumentWidget::slotTabChanged(int tab) { if (!factory()) { return; } if (m_currentTab) { factory()->removeClient(m_currentTab); } if (tab >= 0 && tab < m_tabs->count()) { m_currentTab = static_cast(m_tabs->widget(tab)); factory()->addClient(m_currentTab); } } void DocumentWidget::selectModelItem(const ModelItem& item) { for (int i = 0; i < m_tabs->count(); ++i) { static_cast(m_tabs->widget(i))->selectModelItem(item); } } void DocumentWidget::setProgress(int value) { m_loadingProgressBar->setValue(value); } void DocumentWidget::setRange(int minimum, int maximum) { m_loadingProgressBar->setRange(minimum, maximum); } 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::fromLatin1("%1

%2

").arg(title).arg(error)); m_stackedWidget->setCurrentWidget(m_errorMessage); } void DocumentWidget::stopParser() { if (!m_parseWorker) { return; } QThread* thread = m_parseWorker->thread(); m_parseWorker->stop(); m_parseWorker->deleteLater(); - m_parseWorker = 0; + m_parseWorker = nullptr; thread->quit(); thread->wait(); } diff --git a/app/documentwidget.h b/app/documentwidget.h index 1fec604..dc3d935 100644 --- a/app/documentwidget.h +++ b/app/documentwidget.h @@ -1,105 +1,105 @@ /* 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 . */ #ifndef DOCUMENTWIDGET_H #define DOCUMENTWIDGET_H #include #include #include #include #include "visualizer/modelitem.h" class DocumentTabInterface; class QMenu; class QLabel; class QProgressBar; class QToolButton; class QStackedWidget; class QTabWidget; class KMessageWidget; namespace Massif { class FileData; class ParseWorker; } class DocumentWidget : public QWidget, public KXMLGUIClient { Q_OBJECT public: explicit DocumentWidget(const QUrl &file, const QStringList& customAllocators, - KXMLGUIClient* guiParent, QWidget* parent = 0); + KXMLGUIClient* guiParent, QWidget* parent = nullptr); ~DocumentWidget() override; Massif::FileData* data() const; QUrl file() const; bool isLoaded() const; void settingsChanged(); void addGuiActions(KXMLGUIFactory* factory); void clearGuiActions(KXMLGUIFactory* factory); void selectModelItem(const Massif::ModelItem& item); Q_SIGNALS: void loadingFinished(); void modelItemSelected(const Massif::ModelItem& item); void contextMenuRequested(const Massif::ModelItem& item, QMenu* menu); void requestClose(); private Q_SLOTS: void stopParser(); void parserFinished(const QUrl& file, Massif::FileData* data); void setProgress(int value); void setRange(int minimum, int maximum); void showError(const QString& title, const QString& error); void slotTabChanged(int tab); private: Massif::FileData* m_data; Massif::ParseWorker* m_parseWorker; QUrl m_file; DocumentTabInterface* m_currentTab; QStackedWidget* m_stackedWidget; QTabWidget* m_tabs; KMessageWidget* m_errorMessage; QLabel* m_loadingMessage; QProgressBar* m_loadingProgressBar; QToolButton* m_stopParserButton; bool m_isLoaded; }; #endif // DOCUMENTWIDGET_H diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp index 90f15eb..5d550cd 100644 --- a/app/mainwindow.cpp +++ b/app/mainwindow.cpp @@ -1,523 +1,523 @@ /* 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 "mainwindow.h" #include "massifdata/filedata.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 "massif-visualizer-settings.h" #include "configdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_KGRAPHVIEWER #include #endif using namespace Massif; // Helper function static KConfigGroup allocatorConfig() { return KSharedConfig::openConfig()->group("Allocators"); } MainWindow::MainWindow(QWidget* parent, Qt::WindowFlags f) : KParts::MainWindow(parent, f) - , m_recentFiles(0) - , m_close(0) + , m_recentFiles(nullptr) + , m_close(nullptr) , m_allocatorModel(new QStringListModel(this)) - , m_newAllocator(0) - , m_removeAllocator(0) - , m_shortenTemplates(0) - , m_selectPeak(0) - , m_currentDocument(0) + , m_newAllocator(nullptr) + , m_removeAllocator(nullptr) + , m_shortenTemplates(nullptr) + , m_selectPeak(nullptr) + , m_currentDocument(nullptr) , m_dataTreeModel(new DataTreeModel(this)) , m_dataTreeFilterModel(new FilteredDataTreeModel(m_dataTreeModel)) , m_settingSelection(false) { ui.setupUi(this); //BEGIN KGraphViewer bool haveGraphViewer = false; // NOTE: just check if kgraphviewer is available at runtime. // The former logic has been moved to DocumentWidget constructor. #ifdef HAVE_KGRAPHVIEWER KPluginFactory *factory = KPluginLoader(QStringLiteral("kgraphviewerpart")).factory(); if (factory) { KParts::ReadOnlyPart* readOnlyPart = factory->create(QString(), this); if (readOnlyPart) { readOnlyPart->widget()->hide(); haveGraphViewer = true; } } #endif if (!haveGraphViewer) { // cleanup UI when we installed with kgraphviewer but it's not available at runtime KToolBar* callgraphToolbar = toolBar(QStringLiteral("callgraphToolBar")); removeToolBar(callgraphToolbar); delete callgraphToolbar; } //END KGraphViewer ui.documents->setMovable(true); ui.documents->setTabsClosable(true); connect(ui.documents, &QTabWidget::currentChanged, this, &MainWindow::documentChanged); connect(ui.documents, &QTabWidget::tabCloseRequested, this, &MainWindow::closeFileTab); //BEGIN custom allocators tabifyDockWidget(ui.allocatorDock, ui.dataTreeDock); ui.allocatorView->setModel(m_allocatorModel); int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize); ui.dockMenuBar->setIconSize(QSize(iconSize, iconSize)); ui.dockMenuBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); ui.dockMenuBar->setFloatable(false); ui.dockMenuBar->setMovable(false); KConfigGroup cfg = allocatorConfig(); m_allocatorModel->setStringList(cfg.entryMap().values()); connect(m_allocatorModel, &QStringListModel::modelReset, this, &MainWindow::allocatorsChanged); connect(m_allocatorModel, &QStringListModel::dataChanged, this, &MainWindow::allocatorsChanged); connect(ui.dataTreeView, &QTreeView::customContextMenuRequested, this, &MainWindow::dataTreeContextMenuRequested); ui.dataTreeView->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui.allocatorView, &QTreeView::customContextMenuRequested, this, &MainWindow::allocatorViewContextMenuRequested); ui.allocatorView->setContextMenuPolicy(Qt::CustomContextMenu); //END custom allocators setupActions(); setupGUI(StandardWindowOptions(Default ^ StatusBar)); statusBar()->hide(); ui.dataTreeView->setModel(m_dataTreeFilterModel); connect(ui.filterDataTree, &KLineEdit::textChanged, m_dataTreeFilterModel, &FilteredDataTreeModel::setFilter); connect(ui.dataTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &MainWindow::treeSelectionChanged); // open page ui.stackedWidget->setCurrentWidget(ui.openPage); } MainWindow::~MainWindow() { while (ui.documents->count()) { closeCurrentFile(); } m_recentFiles->saveEntries(KSharedConfig::openConfig()->group( QString() )); } void MainWindow::setupActions() { QAction* openFile = KStandardAction::open(this, SLOT(openFile()), actionCollection()); m_recentFiles = KStandardAction::openRecent(this, SLOT(openFile(QUrl)), actionCollection()); m_recentFiles->loadEntries(KSharedConfig::openConfig()->group( QString() )); QAction* reload = KStandardAction::redisplay(this, SLOT(reloadCurrentFile()), actionCollection()); actionCollection()->addAction(QStringLiteral("file_reload"), reload); reload->setEnabled(false); m_close = KStandardAction::close(this, SLOT(closeCurrentFile()), actionCollection()); m_close->setEnabled(false); KStandardAction::quit(qApp, SLOT(closeAllWindows()), actionCollection()); KStandardAction::preferences(this, SLOT(preferences()), actionCollection()); m_shortenTemplates = new QAction(QIcon::fromTheme(QStringLiteral("shortentemplates")), i18n("Shorten Templates"), actionCollection()); m_shortenTemplates->setCheckable(true); m_shortenTemplates->setChecked(Settings::shortenTemplates()); connect(m_shortenTemplates, &QAction::toggled, this, &MainWindow::slotShortenTemplates); actionCollection()->addAction(QStringLiteral("shorten_templates"), m_shortenTemplates); m_selectPeak = new QAction(QIcon::fromTheme(QStringLiteral("flag-red")), i18n("Select peak snapshot"), actionCollection()); connect(m_selectPeak, &QAction::triggered, this, &MainWindow::selectPeakSnapshot); actionCollection()->addAction(QStringLiteral("selectPeak"), m_selectPeak); m_selectPeak->setEnabled(false); //BEGIN custom allocators m_newAllocator = new QAction(QIcon::fromTheme(QStringLiteral("list-add")), i18n("add"), ui.allocatorDock); m_newAllocator->setToolTip(i18n("add custom allocator")); connect(m_newAllocator, &QAction::triggered, this, &MainWindow::slotNewAllocator); ui.dockMenuBar->addAction(m_newAllocator); m_removeAllocator = new QAction(QIcon::fromTheme(QStringLiteral("list-remove")), i18n("remove"), ui.allocatorDock); m_newAllocator->setToolTip(i18n("remove selected allocator")); connect(m_removeAllocator, &QAction::triggered, this, &MainWindow::slotRemoveAllocator); connect(ui.allocatorView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::allocatorSelectionChanged); m_removeAllocator->setEnabled(false); ui.dockMenuBar->addAction(m_removeAllocator); m_markCustomAllocator = new QAction(i18n("mark as custom allocator"), ui.allocatorDock); connect(m_markCustomAllocator, &QAction::triggered, this, &MainWindow::slotMarkCustomAllocator, Qt::QueuedConnection); //END custom allocators //dock actions actionCollection()->addAction(QStringLiteral("toggleDataTree"), ui.dataTreeDock->toggleViewAction()); actionCollection()->addAction(QStringLiteral("toggleAllocators"), ui.allocatorDock->toggleViewAction()); //open page actions ui.openFile->setDefaultAction(openFile); ui.openFile->setText(i18n("Open Massif Data File")); ui.openFile->setIconSize(QSize(48, 48)); } void MainWindow::preferences() { if (ConfigDialog::isShown()) { return; } ConfigDialog* dlg = new ConfigDialog(this); connect(dlg, &ConfigDialog::settingsChanged, this, &MainWindow::settingsChanged); dlg->show(); } void MainWindow::settingsChanged() { if (Settings::self()->shortenTemplates() != m_shortenTemplates->isChecked()) { m_shortenTemplates->setChecked(Settings::self()->shortenTemplates()); } Settings::self()->save(); if (m_currentDocument) { m_currentDocument->settingsChanged(); } ui.dataTreeView->viewport()->update(); } void MainWindow::openFile() { const QList files = QFileDialog::getOpenFileUrls(this, i18n("Open Massif Output File"), QUrl(), i18n("Massif data files (massif.out.*)")); foreach (const QUrl& file, files) { openFile(file); } } void MainWindow::reloadCurrentFile() { if (m_currentDocument->file().isValid()) { openFile(QUrl(m_currentDocument->file())); } } void MainWindow::openFile(const QUrl& file) { Q_ASSERT(file.isValid()); // Is file already opened ? int indexToInsert = -1; for (int i = 0; i < ui.documents->count(); ++i) { if (qobject_cast(ui.documents->widget(i))->file() == file) { indexToInsert = i; break; } } DocumentWidget* documentWidget = new DocumentWidget(file, m_allocatorModel->stringList(), this, this); if (indexToInsert != -1) { // Remove existing instance of the file. ui.documents->setCurrentIndex(indexToInsert); closeCurrentFile(); // Insert the new tab at the correct position. ui.documents->insertTab(indexToInsert, documentWidget, file.fileName()); ui.documents->setCurrentIndex(indexToInsert); } else { const int idx = ui.documents->addTab(documentWidget, file.fileName()); ui.documents->setCurrentIndex(idx); } connect(documentWidget, &DocumentWidget::loadingFinished, this, &MainWindow::documentChanged); connect(documentWidget, &DocumentWidget::requestClose, this, &MainWindow::closeRequested); m_recentFiles->addUrl(file); ui.stackedWidget->setCurrentWidget(ui.displayPage); } void MainWindow::treeSelectionChanged(const QModelIndex& now, const QModelIndex& before) { if (!m_currentDocument || m_settingSelection || now == before) { return; } m_settingSelection = true; const ModelItem& item = now.data(DataTreeModel::ModelItemRole).value(); m_currentDocument->selectModelItem(item); m_settingSelection = false; } void MainWindow::modelItemSelected(const ModelItem& item) { if (!m_currentDocument || m_settingSelection) { return; } m_settingSelection = true; const QModelIndex& newIndex = m_dataTreeFilterModel->mapFromSource( m_dataTreeModel->indexForItem(item) ); ui.dataTreeView->selectionModel()->clearSelection(); ui.dataTreeView->selectionModel()->setCurrentIndex(newIndex, QItemSelectionModel::Select | QItemSelectionModel::Rows); ui.dataTreeView->scrollTo(ui.dataTreeView->selectionModel()->currentIndex()); m_currentDocument->selectModelItem(item); m_settingSelection = false; } void MainWindow::selectPeakSnapshot() { ui.dataTreeView->selectionModel()->clearSelection(); ui.dataTreeView->selectionModel()->setCurrentIndex( m_dataTreeFilterModel->mapFromSource( m_dataTreeModel->indexForSnapshot(m_currentDocument->data()->peak())), QItemSelectionModel::Select | QItemSelectionModel::Rows ); } void MainWindow::closeCurrentFile() { closeFileTab(ui.documents->currentIndex()); } void MainWindow::closeRequested() { DocumentWidget* widget = qobject_cast(sender()); Q_ASSERT(widget); closeFileTab(ui.documents->indexOf(widget)); } void MainWindow::closeFileTab(int idx) { Q_ASSERT(idx != -1); ui.documents->widget(idx)->deleteLater(); ui.documents->removeTab(idx); } void MainWindow::allocatorsChanged() { KConfigGroup cfg = allocatorConfig(); cfg.deleteGroup(); int i = 0; foreach(const QString& allocator, m_allocatorModel->stringList()) { if (allocator.isEmpty()) { m_allocatorModel->removeRow(i); continue; } cfg.writeEntry(QString::number(i++), allocator); } cfg.sync(); if (m_currentDocument) { reloadCurrentFile(); } } void MainWindow::allocatorSelectionChanged() { m_removeAllocator->setEnabled(ui.allocatorView->selectionModel()->hasSelection()); } void MainWindow::slotNewAllocator() { QString allocator = QInputDialog::getText(this, i18n("Add Custom Allocator"), i18n("allocator:")); if (allocator.isEmpty()) { return; } if (!m_allocatorModel->stringList().contains(allocator)) { m_allocatorModel->setStringList(m_allocatorModel->stringList() << allocator); } } void MainWindow::slotRemoveAllocator() { Q_ASSERT(ui.allocatorView->selectionModel()->hasSelection()); foreach(const QModelIndex& idx, ui.allocatorView->selectionModel()->selectedRows()) { m_allocatorModel->removeRow(idx.row(), idx.parent()); } allocatorsChanged(); } void MainWindow::slotMarkCustomAllocator() { const QString allocator = m_markCustomAllocator->data().toString(); Q_ASSERT(!allocator.isEmpty()); if (!m_allocatorModel->stringList().contains(allocator)) { m_allocatorModel->setStringList(m_allocatorModel->stringList() << allocator); } } void MainWindow::allocatorViewContextMenuRequested(const QPoint& pos) { const QModelIndex idx = ui.allocatorView->indexAt(pos); QMenu menu; menu.addAction(m_newAllocator); if (idx.isValid()) { menu.addAction(m_removeAllocator); } menu.exec(ui.allocatorView->mapToGlobal(pos)); } void MainWindow::dataTreeContextMenuRequested(const QPoint& pos) { const QModelIndex idx = ui.dataTreeView->indexAt(pos); const TreeLeafItem* item = idx.data(DataTreeModel::TreeItemRole).value(); if (!item) { return; } QMenu menu; - contextMenuRequested(ModelItem(item, 0), &menu); + contextMenuRequested(ModelItem(item, nullptr), &menu); menu.exec(ui.dataTreeView->mapToGlobal(pos)); } void MainWindow::contextMenuRequested(const ModelItem& item, QMenu* menu) { if (!item.first) { // only handle context menu on tree-leaf items for now return; } QByteArray func = functionInLabel(item.first->label()); if (func.length() > 40) { func.resize(40); func.append("..."); } menu->setTitle(QString::fromUtf8(func)); m_markCustomAllocator->setData(item.first->label()); menu->addAction(m_markCustomAllocator); } void MainWindow::slotShortenTemplates(bool shorten) { if (shorten == Settings::self()->shortenTemplates()) { return; } Settings::self()->setShortenTemplates(shorten); settingsChanged(); } void MainWindow::updateWindowTitle() { if (m_currentDocument && m_currentDocument->isLoaded()) { setWindowTitle(i18n("Evaluation of %1 (%2)", m_currentDocument->data()->cmd(), m_currentDocument->file().fileName())); } else { setWindowTitle(QString()); } } void MainWindow::documentChanged() { // required to prevent GUI flickering when changing documents // the changing actions in the toolbar are really flickering bad otherwise setUpdatesEnabled(false); if (m_currentDocument) { - m_dataTreeModel->setSource(0); + m_dataTreeModel->setSource(nullptr); m_dataTreeFilterModel->setFilter(QString()); m_currentDocument->clearGuiActions(guiFactory()); disconnect(m_currentDocument, &DocumentWidget::modelItemSelected, this, &MainWindow::modelItemSelected); disconnect(m_currentDocument, &DocumentWidget::contextMenuRequested, this, &MainWindow::contextMenuRequested); } m_currentDocument = qobject_cast(ui.documents->currentWidget()); updateWindowTitle(); actionCollection()->action(QStringLiteral("file_reload"))->setEnabled(m_currentDocument && m_currentDocument->isLoaded()); m_close->setEnabled(m_currentDocument); m_selectPeak->setEnabled(m_currentDocument && m_currentDocument->isLoaded()); if (!m_currentDocument) { ui.stackedWidget->setCurrentWidget(ui.openPage); setUpdatesEnabled(true); return; } else { m_dataTreeModel->setSource(m_currentDocument->data()); m_currentDocument->addGuiActions(guiFactory()); connect(m_currentDocument, &DocumentWidget::modelItemSelected, this, &MainWindow::modelItemSelected); connect(m_currentDocument, &DocumentWidget::contextMenuRequested, this, &MainWindow::contextMenuRequested); } setUpdatesEnabled(true); } diff --git a/app/mainwindow.h b/app/mainwindow.h index c491299..cb93909 100644 --- a/app/mainwindow.h +++ b/app/mainwindow.h @@ -1,145 +1,145 @@ /* 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 . */ #ifndef MASSIF_MAINWINDOW_H #define MASSIF_MAINWINDOW_H #include #include #include "ui_mainwindow.h" #include "documentwidget.h" class QAction; class QSpinBox; class QStringListModel; namespace KChart { class Chart; class HeaderFooter; class Plotter; class CartesianAxis; class Legend; class BarDiagram; } class KRecentFilesAction; #ifdef HAVE_KGRAPHVIEWER namespace KGraphViewer { class KGraphViewerInterface; } #endif namespace Massif { class FilteredDataTreeModel; class DataTreeModel; class SnapshotItem; class TreeLeafItem; class MainWindow : public KParts::MainWindow { Q_OBJECT public: - explicit MainWindow(QWidget* parent = 0, Qt::WindowFlags f = 0); + explicit MainWindow(QWidget* parent = nullptr, Qt::WindowFlags f = {}); ~MainWindow() override; void setupActions(); public Q_SLOTS: /** * Open a dialog to pick a massif output file(s) to display. */ void openFile(); /** * Opens @p file as massif output file and visualize it. */ void openFile(const QUrl& file); /** * reload currently opened file */ void reloadCurrentFile(); /** * Close currently opened file. */ void closeCurrentFile(); private Q_SLOTS: void closeRequested(); void closeFileTab(int idx); void preferences(); void settingsChanged(); void treeSelectionChanged(const QModelIndex& now, const QModelIndex& before); void selectPeakSnapshot(); void modelItemSelected(const Massif::ModelItem& item); void contextMenuRequested(const Massif::ModelItem& item, QMenu* menu); void documentChanged(); void allocatorsChanged(); void allocatorSelectionChanged(); void dataTreeContextMenuRequested(const QPoint &pos); void slotNewAllocator(); void slotRemoveAllocator(); /// operates on data of @c m_markCustomAllocator void slotMarkCustomAllocator(); void allocatorViewContextMenuRequested(const QPoint &pos); void slotShortenTemplates(bool); private: void updateWindowTitle(); // Helper Ui::MainWindow ui; KRecentFilesAction* m_recentFiles; QAction* m_close; QStringListModel* m_allocatorModel; QAction* m_newAllocator; QAction* m_removeAllocator; QAction* m_markCustomAllocator; QAction* m_shortenTemplates; QAction* m_selectPeak; DocumentWidget* m_currentDocument; Massif::DataTreeModel* m_dataTreeModel; Massif::FilteredDataTreeModel* m_dataTreeFilterModel; bool m_settingSelection; }; } #endif // MASSIF_MAINWINDOW_H diff --git a/massifdata/filedata.cpp b/massifdata/filedata.cpp index 4f6c750..46cee2d 100644 --- a/massifdata/filedata.cpp +++ b/massifdata/filedata.cpp @@ -1,86 +1,86 @@ /* 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 "filedata.h" #include "snapshotitem.h" using namespace Massif; -FileData::FileData(QObject* parent) : QObject(parent), m_peak(0) +FileData::FileData(QObject* parent) : QObject(parent), m_peak(nullptr) { } FileData::~FileData() { qDeleteAll(m_snapshots); } void FileData::setCmd(const QString& cmd) { m_cmd = cmd; } QString FileData::cmd() const { return m_cmd; } void FileData::setDescription(const QString& description) { m_description = description; } QString FileData::description() const { return m_description; } void FileData::setTimeUnit(const QString& unit) { m_timeUnit = unit; } QString FileData::timeUnit() const { return m_timeUnit; } void FileData::addSnapshot(SnapshotItem* snapshot) { m_snapshots << snapshot; } QVector< SnapshotItem* > FileData::snapshots() const { return m_snapshots; } void FileData::setPeak(SnapshotItem* snapshot) { Q_ASSERT(m_snapshots.contains(snapshot)); m_peak = snapshot; } SnapshotItem* FileData::peak() const { return m_peak; } diff --git a/massifdata/filedata.h b/massifdata/filedata.h index 6ff62d6..c535558 100644 --- a/massifdata/filedata.h +++ b/massifdata/filedata.h @@ -1,99 +1,99 @@ /* 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 . */ #ifndef MASSIF_FILEDATA_H #define MASSIF_FILEDATA_H #include #include namespace Massif { class SnapshotItem; /** * This structure holds all information that can be extracted from a massif output file. */ class FileData : public QObject { Q_OBJECT public: - explicit FileData(QObject* parent = 0); + explicit FileData(QObject* parent = nullptr); ~FileData() override; /** * Set the @p cmd that was profiled with massif. */ void setCmd(const QString& cmd); /** * @return Command that was profiled with massif. */ QString cmd() const; /** * Set a @p description. */ void setDescription(const QString& description); /** * @return Description for this massif run. */ QString description() const; /** * Set the @p unit that times are measured in. */ void setTimeUnit(const QString& unit); /** * @return The unit that times are measured in. */ QString timeUnit() const; /** * Adds @p snapshot to this dataset and takes ownership. */ void addSnapshot(SnapshotItem* snapshot); /** * @return List of all snapshots that make up this dataset. */ QVector snapshots() const; /** * Marks @p snapshot as peak of this dataset. * The snapshot should already been added to the dataset. */ void setPeak(SnapshotItem* snapshot); /** * @return The peak snapshot in this dataset. */ SnapshotItem* peak() const; private: QString m_cmd; QString m_description; QString m_timeUnit; QVector m_snapshots; SnapshotItem* m_peak; }; } #endif // MASSIF_FILEDATA_H diff --git a/massifdata/parser.cpp b/massifdata/parser.cpp index be6ee22..ddd6bfe 100644 --- a/massifdata/parser.cpp +++ b/massifdata/parser.cpp @@ -1,99 +1,99 @@ /* 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 "parser.h" #include "filedata.h" #include "parserprivate.h" #include "snapshotitem.h" #include using namespace Massif; Parser::Parser() : m_errorLine(-1) { } Parser::~Parser() { } FileData* Parser::parse(QIODevice* file, const QStringList& customAllocators, QAtomicInt* shouldStop) { Q_ASSERT(file->isOpen()); Q_ASSERT(file->isReadable()); QScopedPointer data(new FileData); ParserPrivate p(this, file, data.data(), customAllocators, shouldStop); if (p.error()) { m_errorLine = p.errorLine(); m_errorLineString = p.errorLineString(); - return 0; + return nullptr; } else { m_errorLine = -1; m_errorLineString.clear(); } // when a massif run gets terminated (^C) the snapshot data might be wrong, // hence just always ensure we pick the proper peak ourselves if (!data->snapshots().isEmpty()) { foreach ( SnapshotItem* snapshot, data->snapshots() ) { if (!snapshot->heapTree()) { // peak should have detailed info continue; } if (!data->peak() || snapshot->cost() > data->peak()->cost()) { data->setPeak(snapshot); } } // still not found? pick any other snapshot if (!data->peak()) { foreach( SnapshotItem* snapshot, data->snapshots() ) { if (!data->peak() || snapshot->cost() > data->peak()->cost()) { data->setPeak(snapshot); } } } } // peak might still be zero if we have no snapshots, should be handled in the UI then return data.take(); } int Parser::errorLine() const { return m_errorLine; } QByteArray Parser::errorLineString() const { return m_errorLineString; } void Parser::setProgress(int value) { emit progress(value); } diff --git a/massifdata/parser.h b/massifdata/parser.h index cde875a..e66224a 100644 --- a/massifdata/parser.h +++ b/massifdata/parser.h @@ -1,84 +1,84 @@ /* 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 . */ #ifndef MASSIF_PARSER_H #define MASSIF_PARSER_H #include #include #include class QIODevice; namespace Massif { class FileData; /** * This class parses a Massif output file and stores it's information. */ class Parser : public QObject { Q_OBJECT public: Parser(); ~Parser() override; /** * Parse @p file and return a FileData structure representing the data. * * @p customAllocators list of wildcard patterns used to find custom allocators * @p shouldStop if supplied, this is checked periodically. If the atomic * evaluates to true, parser stops and returns 0. * * @return Data or null if file could not be parsed. * * @note The caller has to delete the data afterwards. */ FileData* parse(QIODevice* file, const QStringList& customAllocators = QStringList(), - QAtomicInt* shouldStop = 0); + QAtomicInt* shouldStop = nullptr); /** * Returns the number of the line which could not be parsed or -1 if no error occurred. */ int errorLine() const; /** * Returns the line which could not be parsed. */ QByteArray errorLineString() const; void setProgress(int value); Q_SIGNALS: void progress(int value); private: int m_errorLine; QByteArray m_errorLineString; }; } #endif // MASSIF_PARSER_H diff --git a/massifdata/parserprivate.cpp b/massifdata/parserprivate.cpp index a770667..d5f9ee5 100644 --- a/massifdata/parserprivate.cpp +++ b/massifdata/parserprivate.cpp @@ -1,448 +1,448 @@ /* 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 "parserprivate.h" #include "filedata.h" #include "snapshotitem.h" #include "treeleafitem.h" #include "util.h" #include "parser.h" #include #include using namespace Massif; #define VALIDATE(l, x) if (!(x)) { m_errorLineString = l; m_error = Invalid; return; } #define VALIDATE_RETURN(l, x, y) if (!(x)) { m_errorLineString = l; m_error = Invalid; return y; } static QByteArray midRef(const QByteArray& line, int offset, int length = -1) { return QByteArray::fromRawData(line.data() + offset, length == -1 ? (line.length() - offset) : length); } ParserPrivate::ParserPrivate(Parser* parser, QIODevice* file, FileData* data, const QStringList& customAllocators, QAtomicInt* shouldStop) : m_parser(parser) , m_file(file) , m_data(data) , m_nextLine(FileDesc) , m_currentLine(0) , m_error(NoError) - , m_snapshot(0) - , m_parentItem(0) + , m_snapshot(nullptr) + , m_parentItem(nullptr) , m_hadCustomAllocators(false) , m_expectedSnapshots(100) { foreach(const QString& allocator, customAllocators) { if (allocator.contains(QLatin1Char('*'))) { m_allocators << QRegExp(allocator, Qt::CaseSensitive, QRegExp::Wildcard); } else { m_plainAllocators << allocator.toLatin1(); } } while (!file->atEnd()) { if (shouldStop && *shouldStop) { m_error = Stopped; return; } if (file->size()) { // use pos to determine progress when reading the file, won't work for compressed files parser->setProgress(static_cast(double(file->pos() * 100) / file->size())); } const QByteArray& line = readLine(); switch (m_nextLine) { case FileDesc: parseFileDesc(line); break; case FileCmd: parseFileCmd(line); break; case FileTimeUnit: parseFileTimeUnit(line); break; case Snapshot: parseSnapshot(line); break; case SnapshotHeapTree: parseSnapshotHeapTree(line); break; case SnapshotMemHeap: parseSnapshotMemHeap(line); break; case SnapshotMemHeapExtra: parseSnapshotMemHeapExtra(line); break; case SnapshotTime: parseSnapshotTime(line); break; case SnapshotMemStacks: parseSnapshotMemStacks(line); break; case HeapTreeLeaf: parseHeapTreeLeaf(line); break; } if (m_error != NoError) { qWarning() << "invalid line" << m_currentLine << line; m_error = Invalid; if (m_errorLineString.isEmpty()) { m_errorLineString = line; } // we use fromRawData, so better detach here m_errorLineString.detach(); break; } } if (!file->atEnd()) { m_error = Invalid; } } ParserPrivate::~ParserPrivate() { } ParserPrivate::Error ParserPrivate::error() const { return m_error; } int ParserPrivate::errorLine() const { if (m_error == Invalid) { return m_currentLine; } else { return -1; } } QByteArray ParserPrivate::errorLineString() const { return m_errorLineString; } //BEGIN Parser Functions void ParserPrivate::parseFileDesc(const QByteArray& line) { // desc: ... VALIDATE(line, line.startsWith("desc: ")) m_data->setDescription(QString::fromUtf8(line.mid(6))); m_nextLine = FileCmd; if (!m_file->size()) { // for zipped files, parse the desc line for a --max-snapshots parameter // and use that number for the progress bar. read the manual to know that // this might not be a good measure, but better than nothing. QRegExp pattern(QStringLiteral("--max-snapshots=([0-9]+)"), Qt::CaseSensitive, QRegExp::RegExp2); if (pattern.indexIn(m_data->description()) != -1) { m_expectedSnapshots = pattern.cap(1).toInt(); } } } void ParserPrivate::parseFileCmd(const QByteArray& line) { // cmd: ... VALIDATE(line, line.startsWith("cmd: ")) m_data->setCmd(QString::fromUtf8(line.mid(5))); m_nextLine = FileTimeUnit; } void ParserPrivate::parseFileTimeUnit(const QByteArray& line) { // time_unit: ... VALIDATE(line, line.startsWith("time_unit: ")) m_data->setTimeUnit(QString::fromUtf8(line.mid(11))); m_nextLine = Snapshot; } void ParserPrivate::parseSnapshot(const QByteArray& line) { VALIDATE(line, line == "#-----------") // snapshot=N QByteArray nextLine = readLine(); VALIDATE(nextLine, nextLine.startsWith("snapshot=")) bool ok; uint number = midRef(nextLine, 9).toUInt(&ok); VALIDATE(nextLine, ok) nextLine = readLine(); VALIDATE(nextLine, nextLine == "#-----------") m_snapshot = new SnapshotItem; m_data->addSnapshot(m_snapshot); m_snapshot->setNumber(number); m_nextLine = SnapshotTime; if (!m_file->size()) { // see above: progress calculation for compressed files m_parser->setProgress(number * 100 / m_expectedSnapshots); } } void ParserPrivate::parseSnapshotTime(const QByteArray& line) { VALIDATE(line, line.startsWith("time=")) bool ok; double time = midRef(line, 5).toDouble(&ok); VALIDATE(line, ok) m_snapshot->setTime(time); m_nextLine = SnapshotMemHeap; } void ParserPrivate::parseSnapshotMemHeap(const QByteArray& line) { VALIDATE(line, line.startsWith("mem_heap_B=")) bool ok; quint64 bytes = midRef(line, 11).toULongLong(&ok); VALIDATE(line, ok) m_snapshot->setMemHeap(bytes); m_nextLine = SnapshotMemHeapExtra; } void ParserPrivate::parseSnapshotMemHeapExtra(const QByteArray& line) { VALIDATE(line, line.startsWith("mem_heap_extra_B=")) bool ok; quint64 bytes = midRef(line, 17).toULongLong(&ok); VALIDATE(line, ok) m_snapshot->setMemHeapExtra(bytes); m_nextLine = SnapshotMemStacks; } void ParserPrivate::parseSnapshotMemStacks(const QByteArray& line) { VALIDATE(line, line.startsWith("mem_stacks_B=")) bool ok; quint64 bytes = midRef(line, 13).toULongLong(&ok); VALIDATE(line, ok) m_snapshot->setMemStacks(bytes); m_nextLine = SnapshotHeapTree; } void ParserPrivate::parseSnapshotHeapTree(const QByteArray& line) { VALIDATE(line, line.startsWith("heap_tree=")) QByteArray valueRef = midRef(line, 10); if (valueRef == "empty") { m_nextLine = Snapshot; } else if (valueRef == "detailed") { m_nextLine = HeapTreeLeaf; } else if (valueRef == "peak") { m_nextLine = HeapTreeLeaf; m_data->setPeak(m_snapshot); } else { m_error = Invalid; return; } } bool sortLeafsByCost(TreeLeafItem* l, TreeLeafItem* r) { return l->cost() > r->cost(); } void ParserPrivate::parseHeapTreeLeaf(const QByteArray& line) { parseheapTreeLeafInternal(line, 0); m_nextLine = Snapshot; // we need to do some post processing if we had custom allocators: // - sort by cost // - merge "in XYZ places all below threshold" if (m_hadCustomAllocators) { Q_ASSERT(m_snapshot->heapTree()); QVector newChildren = m_snapshot->heapTree()->children(); - TreeLeafItem* belowThreshold = 0; + TreeLeafItem* belowThreshold = nullptr; uint places = 0; QString oldPlaces; ///TODO: is massif translateable? QRegExp matchBT(QStringLiteral("in ([0-9]+) places, all below massif's threshold"), Qt::CaseSensitive, QRegExp::RegExp2); QVector::iterator it = newChildren.begin(); while(it != newChildren.end()) { TreeLeafItem* child = *it; if (matchBT.indexIn(QString::fromLatin1(child->label())) != -1) { places += matchBT.cap(1).toUInt(); if (belowThreshold) { // merge with previously found node belowThreshold->setCost(belowThreshold->cost() + child->cost()); delete child; it = newChildren.erase(it); continue; } else { belowThreshold = child; oldPlaces = matchBT.cap(1); // no break, see above } } ++it; } if (belowThreshold) { QByteArray label = belowThreshold->label(); label.replace(oldPlaces.toUtf8(), QByteArray::number(places)); belowThreshold->setLabel(label); } qSort(newChildren.begin(), newChildren.end(), sortLeafsByCost); m_snapshot->heapTree()->setChildren(newChildren); } - m_parentItem = 0; + m_parentItem = nullptr; } struct SaveAndRestoreItem { SaveAndRestoreItem(TreeLeafItem** item, TreeLeafItem* val) : m_item(item) { m_oldVal = *m_item; *m_item = val; } ~SaveAndRestoreItem() { *m_item = m_oldVal; } TreeLeafItem** m_item; TreeLeafItem* m_oldVal; }; QByteArray ParserPrivate::getLabel(const QByteArray& original) { QSet::const_iterator it = m_labels.constFind(original); if (it != m_labels.constEnd()) { // reuse known label to leverage implicit sharing return *it; } else { m_labels.insert(original); return original; } } bool ParserPrivate::parseheapTreeLeafInternal(const QByteArray& line, int depth) { VALIDATE_RETURN(line, line.length() > depth + 1 && line.at(depth) == 'n', false) int colonPos = line.indexOf(':', depth); VALIDATE_RETURN(line, colonPos != -1, false) bool ok; unsigned int children = midRef(line, depth + 1, colonPos - depth - 1).toUInt(&ok); VALIDATE_RETURN(line, ok, false) int spacePos = line.indexOf(' ', colonPos + 2); VALIDATE_RETURN(line, spacePos != -1, false) quint64 cost = midRef(line, colonPos + 2, spacePos - colonPos - 2).toULongLong(&ok); VALIDATE_RETURN(line, ok, false) if (!cost && !children) { // ignore these empty entries return true; } const QByteArray label = getLabel(line.mid(spacePos + 1)); bool isCustomAlloc = false; if (depth > 0 && !m_allocators.isEmpty()) { const QByteArray func = functionInLabel(label); foreach(const QByteArray& allocator, m_plainAllocators) { if (func.contains(allocator)) { isCustomAlloc = true; break; } } if (!isCustomAlloc) { const QString funcString = QString::fromLatin1(func); foreach(const QRegExp& allocator, m_allocators) { if (allocator.indexIn(funcString) != -1) { isCustomAlloc = true; break; } } } } - TreeLeafItem* newParent = 0; + TreeLeafItem* newParent = nullptr; if (!isCustomAlloc) { TreeLeafItem* leaf = new TreeLeafItem; leaf->setCost(cost); leaf->setLabel(label); if (!depth) { m_snapshot->setHeapTree(leaf); } else { Q_ASSERT(m_parentItem); m_parentItem->addChild(leaf); } newParent = leaf; } else { // the first line/heaptree start must never be a custom allocator, e.g.: // n11: 1776070 (heap allocation functions) malloc/new/new[], --alloc-fns, etc. Q_ASSERT(depth); Q_ASSERT(m_snapshot->heapTree()); newParent = m_snapshot->heapTree(); m_hadCustomAllocators = true; } SaveAndRestoreItem lastParent(&m_parentItem, newParent); for (unsigned int i = 0; i < children; ++i) { QByteArray nextLine = readLine(); if (nextLine.isEmpty()) { // fail gracefully if the tree is not complete, esp. useful for cases where // an app run with massif crashes and massif doesn't finish the full tree dump. return true; } if (!parseheapTreeLeafInternal(nextLine, depth + 1)) { return false; } } return true; } QByteArray ParserPrivate::readLine() { ++m_currentLine; const int read = m_file->readLine(m_lineBuffer, BUF_SIZE); if (read == -1) { return {}; } if (read == BUF_SIZE - 1 && m_lineBuffer[BUF_SIZE - 2] != '\n') { // support for really long lines that don't fit into the buffer QByteArray line = QByteArray::fromRawData(m_lineBuffer, read) + m_file->readLine(); line.chop(1); // remove trailing \n return line; } return QByteArray::fromRawData(m_lineBuffer, read - 1 /* -1 to remove trailing \n */); } //END Parser Functions diff --git a/massifdata/parseworker.h b/massifdata/parseworker.h index e2db7f7..9c0a82f 100644 --- a/massifdata/parseworker.h +++ b/massifdata/parseworker.h @@ -1,67 +1,67 @@ /* This file is part of Massif Visualizer Copyright 2012 Milian Wolff 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 . */ #ifndef MASSIFDATA_PARSEWORKER_H #define MASSIFDATA_PARSEWORKER_H #include #include #include namespace Massif { class FileData; class ParseWorker : public QObject { Q_OBJECT public: - explicit ParseWorker(QObject* parent = 0); + explicit ParseWorker(QObject* parent = nullptr); void stop(); public Q_SLOTS: void parse(const QUrl &url, const QStringList& allocators); Q_SIGNALS: /** * Emitted once a file was properly parsed. */ void finished(const QUrl& url, Massif::FileData* data); /** * Emitted if a file could not be parsed. */ void error(const QString& error, const QString& message); void progressRange(int min, int max); void progress(int value); private: QAtomicInt m_shouldStop; }; } #endif // MASSIFDATA_PARSEWORKER_H diff --git a/massifdata/snapshotitem.cpp b/massifdata/snapshotitem.cpp index 6cf2bce..763b354 100644 --- a/massifdata/snapshotitem.cpp +++ b/massifdata/snapshotitem.cpp @@ -1,101 +1,101 @@ /* 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 "snapshotitem.h" #include "treeleafitem.h" using namespace Massif; SnapshotItem::SnapshotItem() - : m_number(0), m_time(0), m_memHeap(0), m_memHeapExtra(0), m_memStacks(0), m_heapTree(0) + : m_number(0), m_time(0), m_memHeap(0), m_memHeapExtra(0), m_memStacks(0), m_heapTree(nullptr) { } SnapshotItem::~SnapshotItem() { delete m_heapTree; } void SnapshotItem::setNumber(uint num) { m_number = num; } uint SnapshotItem::number() const { return m_number; } void SnapshotItem::setTime(double time) { m_time = time; } double SnapshotItem::time() const { return m_time; } void SnapshotItem::setMemHeap(quint64 bytes) { m_memHeap = bytes; } quint64 SnapshotItem::memHeap() const { return m_memHeap; } void SnapshotItem::setMemHeapExtra(quint64 bytes) { m_memHeapExtra = bytes; } quint64 SnapshotItem::memHeapExtra() const { return m_memHeapExtra; } void SnapshotItem::setMemStacks(quint64 bytes) { m_memStacks = bytes; } quint64 SnapshotItem::memStacks() const { return m_memStacks; } void SnapshotItem::setHeapTree(TreeLeafItem* root) { m_heapTree = root; } TreeLeafItem* SnapshotItem::heapTree() const { return m_heapTree; } quint64 SnapshotItem::cost() const { return m_memHeap + m_memStacks; } diff --git a/massifdata/treeleafitem.cpp b/massifdata/treeleafitem.cpp index 5dc2513..95cd697 100644 --- a/massifdata/treeleafitem.cpp +++ b/massifdata/treeleafitem.cpp @@ -1,76 +1,76 @@ /* 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 "treeleafitem.h" using namespace Massif; TreeLeafItem::TreeLeafItem() - : m_cost(0), m_parent(0) + : m_cost(0), m_parent(nullptr) { } TreeLeafItem::~TreeLeafItem() { qDeleteAll(m_children); } void TreeLeafItem::setLabel(const QByteArray& label) { m_label = label; } QByteArray TreeLeafItem::label() const { return m_label; } void TreeLeafItem::setCost(quint64 bytes) { m_cost = bytes; } quint64 TreeLeafItem::cost() const { return m_cost; } void TreeLeafItem::addChild(TreeLeafItem* leaf) { leaf->m_parent = this; m_children << leaf; } void TreeLeafItem::setChildren(const QVector< TreeLeafItem* >& leafs) { m_children = leafs; } QVector< TreeLeafItem* > TreeLeafItem::children() const { return m_children; } TreeLeafItem* TreeLeafItem::parent() const { return m_parent; } diff --git a/test/datamodeltest.cpp b/test/datamodeltest.cpp index 3b8fb16..9e02230 100644 --- a/test/datamodeltest.cpp +++ b/test/datamodeltest.cpp @@ -1,180 +1,180 @@ /* This file is part of Massif Visualizer Copyright 2010 Milian Wolff 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 "datamodeltest.h" #include "modeltest.h" #include "massifdata/parser.h" #include "massifdata/filedata.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 #include #include #include #include QTEST_MAIN(DataModelTest) using namespace Massif; void DataModelTest::parseFile() { const QString path = QFINDTESTDATA("/data/massif.out.kate"); QFile file(path); QVERIFY(file.open(QIODevice::ReadOnly)); Parser parser; QScopedPointer scopedData(parser.parse(&file)); FileData* data = scopedData.data(); QVERIFY(data); { TotalCostModel* model = new TotalCostModel(this); new ModelTest(model, this); model->setSource(data); QVERIFY(model->rowCount() == data->snapshots().size()); for ( int r = 0; r < model->rowCount(); ++r ) { for ( int c = 0; c < model->columnCount(); ++c ) { qDebug() << r << c << model->data(model->index(r, c)); } } // remove data - model->setSource(0); + model->setSource(nullptr); } { DetailedCostModel* model = new DetailedCostModel(this); new ModelTest(model, this); model->setSource(data); for ( int r = 0; r < model->rowCount(); ++r ) { for ( int c = 0; c < model->columnCount(); ++c ) { qDebug() << r << c << model->data(model->index(r, c)); } if ( r ) { // we want that the snapshots are ordered properly QVERIFY(model->data(model->index(r, 0)).toDouble() > model->data(model->index(r - 1, 0)).toDouble()); } } // remove data - model->setSource(0); + model->setSource(nullptr); } { DataTreeModel* model = new DataTreeModel(this); new ModelTest(model, this); model->setSource(data); QVERIFY(model->rowCount() == data->snapshots().size()); // remove data - model->setSource(0); + model->setSource(nullptr); } } void DataModelTest::testUtils() { { QByteArray l("0x6F675AB: KDevelop::IndexedIdentifier::IndexedIdentifier(KDevelop::Identifier const&) (identifier.cpp:1050)"); QCOMPARE(prettyLabel(l), QByteArray("KDevelop::IndexedIdentifier::IndexedIdentifier(KDevelop::Identifier const&) (identifier.cpp:1050)")); QCOMPARE(functionInLabel(l), QByteArray("KDevelop::IndexedIdentifier::IndexedIdentifier(KDevelop::Identifier const&)")); QCOMPARE(addressInLabel(l), QByteArray("0x6F675AB")); QCOMPARE(locationInLabel(l), QByteArray("identifier.cpp:1050")); } { QByteArray l("0x6F675AB: moz_xmalloc (mozalloc.cpp:98)"); QCOMPARE(prettyLabel(l), QByteArray("moz_xmalloc (mozalloc.cpp:98)")); QCOMPARE(functionInLabel(l), QByteArray("moz_xmalloc")); QCOMPARE(addressInLabel(l), QByteArray("0x6F675AB")); QCOMPARE(locationInLabel(l), QByteArray("mozalloc.cpp:98")); } { QByteArray l("0xC94BD60: KDevelop::Bucket::prepareChange() (itemrepository.h:1007)"); QCOMPARE(prettyLabel(l), QByteArray("KDevelop::Bucket::prepareChange() (itemrepository.h:1007)")); QCOMPARE(functionInLabel(l), QByteArray("KDevelop::Bucket::prepareChange()")); QCOMPARE(addressInLabel(l), QByteArray("0xC94BD60")); QCOMPARE(locationInLabel(l), QByteArray("itemrepository.h:1007")); } { QByteArray l("0x8E2B358: QString::QString(QChar const*, int) (in /usr/lib/libQtCore.so.4.8.4)"); QCOMPARE(prettyLabel(l), QByteArray("QString::QString(QChar const*, int) (in /usr/lib/libQtCore.so.4.8.4)")); QCOMPARE(functionInLabel(l), QByteArray("QString::QString(QChar const*, int)")); QCOMPARE(addressInLabel(l), QByteArray("0x8E2B358")); QCOMPARE(locationInLabel(l), QByteArray("in /usr/lib/libQtCore.so.4.8.4")); } } void DataModelTest::shortenTemplates_data() { QTest::addColumn("id"); QTest::addColumn("idShortened"); QTest::newRow("no-tpl") << QByteArray("A::B(C::D const&) (file.cpp:1)") << QByteArray("A::B(C::D const&) (file.cpp:1)"); QTest::newRow("tpl-func") << QByteArray("A::B(C::D const&) (file.cpp:1)") << QByteArray("A::B<>(C::D const&) (file.cpp:1)"); QTest::newRow("tpl-arg") << QByteArray("A::B(C::D const&) (file.cpp:1)") << QByteArray("A::B(C::D<> const&) (file.cpp:1)"); QTest::newRow("tpl-multi") << QByteArray("A::B(C::D const&) (file.cpp:1)") << QByteArray("A::B<>(C<>::D<> const&) (file.cpp:1)"); QTest::newRow("tpl-nested") << QByteArray("A::B, T2>(C::D const&) (file.cpp:1)") << QByteArray("A::B<>(C<>::D<> const&) (file.cpp:1)"); } void DataModelTest::shortenTemplates() { QFETCH(QByteArray, id); QFETCH(QByteArray, idShortened); KConfigGroup conf = KSharedConfig::openConfig()->group(QLatin1String("Settings")); conf.writeEntry(QLatin1String("shortenTemplates"), true); QCOMPARE(prettyLabel(id), idShortened); conf.writeEntry(QLatin1String("shortenTemplates"), false); QCOMPARE(prettyLabel(id), id); } void DataModelTest::bigMem() { // see also: https://bugs.kde.org/show_bug.cgi?id=294108 const QString path = QFINDTESTDATA("/data/massif.out.huge"); QFile file(path); QVERIFY(file.open(QIODevice::ReadOnly)); Parser parser; QScopedPointer scopedData(parser.parse(&file)); FileData* data = scopedData.data(); QVERIFY(data); QCOMPARE(data->snapshots().count(), 1); QCOMPARE(data->peak()->memHeap(), quint64(5021305210)); QCOMPARE(data->peak()->memHeapExtra(), quint64(5021305211)); QCOMPARE(data->peak()->memStacks(), quint64(5021305212)); } diff --git a/test/modeltest.h b/test/modeltest.h index 452f421..50fe989 100644 --- a/test/modeltest.h +++ b/test/modeltest.h @@ -1,94 +1,94 @@ /**************************************************************************** ** ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef MODELTEST_H #define MODELTEST_H #include #include #include class ModelTest : public QObject { Q_OBJECT public: - explicit ModelTest( QAbstractItemModel *model, QObject *parent = 0 ); + explicit ModelTest( QAbstractItemModel *model, QObject *parent = nullptr ); private Q_SLOTS: void nonDestructiveBasicTest(); void rowCount(); void columnCount(); void hasIndex(); void index(); void parent(); void data(); protected Q_SLOTS: void runAllTests(); void layoutAboutToBeChanged(); void layoutChanged(); void rowsAboutToBeInserted( const QModelIndex &parent, int start, int end ); void rowsInserted( const QModelIndex & parent, int start, int end ); void rowsAboutToBeRemoved( const QModelIndex &parent, int start, int end ); void rowsRemoved( const QModelIndex & parent, int start, int end ); private: void checkChildren( const QModelIndex &parent, int currentDepth = 0 ); QAbstractItemModel *model; struct Changing { QModelIndex parent; int oldSize; QVariant last; QVariant next; }; QStack insert; QStack remove; bool fetchingMore; QList changing; }; #endif diff --git a/visualizer/allocatorsmodel.cpp b/visualizer/allocatorsmodel.cpp index 8520349..11d931e 100644 --- a/visualizer/allocatorsmodel.cpp +++ b/visualizer/allocatorsmodel.cpp @@ -1,195 +1,195 @@ /* This file is part of Massif Visualizer Copyright 2014 Milian Wolff 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 "allocatorsmodel.h" #include "massifdata/snapshotitem.h" #include "massifdata/filedata.h" #include "massifdata/treeleafitem.h" #include using namespace Massif; namespace { ParsedLabel labelForNode(const TreeLeafItem* node) { ParsedLabel label; if (!isBelowThreshold(node->label())) { label = parseLabel(node->label()); label.address.clear(); } return label; } } AllocatorsModel::AllocatorsModel(const FileData* data, QObject* parent) : QAbstractItemModel(parent) { m_data.reserve(1024); QHash labelToData; foreach (const SnapshotItem* snapshot, data->snapshots()) { if (!snapshot->heapTree()) { continue; } foreach (const TreeLeafItem* node, snapshot->heapTree()->children()) { const ParsedLabel label = labelForNode(node); int idx = labelToData.value(label, -1); if (idx == -1) { idx = m_data.size(); labelToData[label] = idx; Data data; data.label = label; data.peak = node; m_data << data; } Data& data = m_data[idx]; if (data.peak->cost() < node->cost()) { data.peak = node; } } } } AllocatorsModel::~AllocatorsModel() { } int AllocatorsModel::columnCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; } return NUM_COLUMNS; } int AllocatorsModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; } return m_data.size(); } QModelIndex AllocatorsModel::index(int row, int column, const QModelIndex& parent) const { if (parent.isValid() || row < 0 || column < 0 || row >= m_data.size() || column >= NUM_COLUMNS) { return QModelIndex(); } return createIndex(row, column); } QModelIndex AllocatorsModel::parent(const QModelIndex& /*child*/) const { return QModelIndex(); } QVariant AllocatorsModel::data(const QModelIndex& index, int role) const { if (role != Qt::DisplayRole && role != Qt::ToolTipRole && role != SortRole && role != ItemRole) { return QVariant(); } if (!index.isValid() || index.parent().isValid() || index.row() < 0 || index.row() >= m_data.size() || index.column() < 0 || index.column() >= NUM_COLUMNS) { return QVariant(); } const Data& data = m_data.value(index.row()); if (role == Qt::ToolTipRole) { QString tooltip = i18n("
peak cost:
%1
", prettyCost(data.peak->cost())); tooltip += formatLabelForTooltip(data.label); return finalizeTooltip(tooltip); } else if (role == ItemRole) { - return QVariant::fromValue(ModelItem(data.peak, 0)); + return QVariant::fromValue(ModelItem(data.peak, nullptr)); } switch (index.column()) { case Function: if (data.label.function.isEmpty()) { return i18n("below threshold"); } return shortenTemplates(data.label.function); case Location: return data.label.location; case Peak: if (role == SortRole) { return data.peak->cost(); } return prettyCost(data.peak->cost()); } return QVariant(); } QVariant AllocatorsModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole || orientation != Qt::Horizontal || section < 0 || section >= NUM_COLUMNS) { return QVariant(); } switch (section) { case Function: return i18n("Function"); case Location: return i18n("Location"); case Peak: return i18n("Peak"); } return QVariant(); } QModelIndex AllocatorsModel::indexForItem(const ModelItem& item) const { if (!item.first) { return QModelIndex(); } const ParsedLabel label = labelForNode(item.first); for (int i = 0; i < m_data.size(); ++i) { if (m_data[i].label == label) { return createIndex(i, Function); } } return QModelIndex(); } void AllocatorsModel::settingsChanged() { if (m_data.isEmpty()) { return; } // update shorten templates dataChanged(createIndex(0, Function), createIndex(m_data.size() - 1, Function)); } diff --git a/visualizer/allocatorsmodel.h b/visualizer/allocatorsmodel.h index b57531d..a14c33e 100644 --- a/visualizer/allocatorsmodel.h +++ b/visualizer/allocatorsmodel.h @@ -1,81 +1,81 @@ /* This file is part of Massif Visualizer Copyright 2014 Milian Wolff 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 . */ #ifndef ALLOCATORSMODEL_H #define ALLOCATORSMODEL_H #include #include #include #include "modelitem.h" namespace Massif { class TreeLeafItem; class FileData; class AllocatorsModel : public QAbstractItemModel { Q_OBJECT public: - explicit AllocatorsModel(const FileData* data, QObject* parent = 0); + explicit AllocatorsModel(const FileData* data, QObject* parent = nullptr); ~AllocatorsModel() override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& parent = QModelIndex()) const override; QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex& child) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; QModelIndex indexForItem(const Massif::ModelItem& item) const; void settingsChanged(); enum Columns { Function = 0, Peak, Location, NUM_COLUMNS, }; enum Roles { SortRole = Qt::UserRole + 1, ItemRole, }; private: struct Data { ParsedLabel label; const TreeLeafItem* peak; }; QVector m_data; }; } #endif // ALLOCATORSMODEL_H diff --git a/visualizer/datatreemodel.cpp b/visualizer/datatreemodel.cpp index 3a025aa..f881a81 100644 --- a/visualizer/datatreemodel.cpp +++ b/visualizer/datatreemodel.cpp @@ -1,296 +1,296 @@ /* 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 "datatreemodel.h" #include "massifdata/filedata.h" #include "massifdata/snapshotitem.h" #include "massifdata/treeleafitem.h" #include "massifdata/util.h" #include #include using namespace Massif; DataTreeModel::DataTreeModel(QObject* parent) - : QAbstractItemModel(parent), m_data(0) + : QAbstractItemModel(parent), m_data(nullptr) { } DataTreeModel::~DataTreeModel() { } void DataTreeModel::setSource(const FileData* data) { if (m_data) { beginRemoveRows(QModelIndex(), 0, rowCount() - 1); - m_data = 0; + m_data = nullptr; m_nodeToRow.clear(); m_heapRootToSnapshot.clear(); endRemoveRows(); } if (data) { beginInsertRows(QModelIndex(), 0, data->snapshots().size() - 1); m_data = data; int row = 0; foreach(const SnapshotItem* item, m_data->snapshots()) { if (item->heapTree()) { mapNodeToRow(item->heapTree(), row++); m_heapRootToSnapshot[item->heapTree()] = item; } else { row++; } } endInsertRows(); } } void DataTreeModel::mapNodeToRow(const TreeLeafItem* node, const int row) { m_nodeToRow[node] = row; int r = 0; foreach(const TreeLeafItem* child, node->children()) { mapNodeToRow(child, r++); } } QVariant DataTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(section) Q_UNUSED(orientation) Q_UNUSED(role) return i18n("Snapshots"); } QVariant DataTreeModel::data(const QModelIndex& index, int role) const { // FIXME kdchart queries (-1, -1) for empty models if ( index.row() == -1 || index.column() == -1 ) { // qWarning() << "DataTreeModel::data: FIXME fix kdchart views to not query model data for invalid indices!"; return QVariant(); } Q_ASSERT(index.row() >= 0 && index.row() < rowCount(index.parent())); Q_ASSERT(index.column() >= 0 && index.column() < columnCount(index.parent())); Q_ASSERT(m_data); // custom background for peak snapshot if ( role == Qt::BackgroundRole ) { // double maxValue = 1; const double maxValue = m_data->peak()->cost(); double currentValue = 0; if ( !index.parent().isValid() && m_data->peak() ) { // maxValue = m_data->peak()->memHeap(); const SnapshotItem* snapshot = m_data->snapshots().at(index.row()); currentValue = snapshot->cost(); } else if (index.parent().isValid()) { Q_ASSERT(index.internalPointer()); const TreeLeafItem* node = static_cast(index.internalPointer()); currentValue = node->cost(); Q_ASSERT(node->parent()); /* TreeLeafItem* parent = node->parent(); while (parent->parent()) { parent = parent->parent(); } maxValue = parent->cost(); // normalize maxValue -= parent->children().last()->cost(); currentValue -= parent->children().last()->cost(); */ } if (currentValue > 0) { const double ratio = (currentValue / maxValue); QColor c = QColor::fromHsv(120 - ratio * 120, 255, 255, (-((ratio-1) * (ratio-1))) * 120 + 120); // QColor c = QColor::fromHsv(120 - ratio * 120, 255, 255, 120); return QBrush(c); } else { return QVariant(); } } if (role == ModelItemRole) { return QVariant::fromValue(itemForIndex(index)); } if (role != Qt::DisplayRole && role != Qt::ToolTipRole && role != RawLabelRole && role != TreeItemRole) { return QVariant(); } if (!index.parent().isValid()) { const SnapshotItem* snapshot = m_data->snapshots().at(index.row()); if (role == Qt::ToolTipRole) { if (snapshot == m_data->peak()) { return i18n("Peak snapshot: heap cost of %1", prettyCost(snapshot->cost())); } else { return i18n("Snapshot #%1: heap cost of %2", snapshot->number(), prettyCost(snapshot->cost())); } } else if (role == RawLabelRole) { return i18nc("%1: snapshot number", "Snapshot #%1", snapshot->number()); } else if (role == TreeItemRole) { - return QVariant::fromValue(0); + return QVariant::fromValue(nullptr); } const QString costStr = prettyCost(snapshot->cost()); if (snapshot == m_data->peak()) { return i18nc("%1: cost, %2: snapshot number", "%1: Snapshot #%2 (peak)", costStr, snapshot->number()); } else { return i18nc("%1: cost, %2: snapshot number", "%1: Snapshot #%2", costStr, snapshot->number()); } } else { Q_ASSERT(index.internalPointer()); const TreeLeafItem* item = static_cast(index.internalPointer()); if (role == Qt::ToolTipRole) { return tooltipForTreeLeaf(item, snapshotForTreeLeaf(item), item->label()); } else if (role == RawLabelRole) { return item->label(); } else if (role == TreeItemRole) { return QVariant::fromValue(item); } return i18nc("%1: cost, %2: snapshot label (i.e. func name etc.)", "%1: %2", prettyCost(item->cost()), QString::fromLatin1(prettyLabel(item->label()))); } return QVariant(); } int DataTreeModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) return 1; } int DataTreeModel::rowCount(const QModelIndex& parent) const { if (!m_data || (parent.isValid() && parent.column() != 0)) { return 0; } if (parent.isValid()) { if (!parent.internalPointer()) { // snapshot without detailed heaptree return 0; } return static_cast(parent.internalPointer())->children().size(); } else { return m_data->snapshots().size(); } } QModelIndex DataTreeModel::index(int row, int column, const QModelIndex& parent) const { if (row < 0 || row >= rowCount(parent) || column < 0 || column >= columnCount(parent)) { // invalid return QModelIndex(); } if (parent.isValid()) { if (parent.column() == 0) { Q_ASSERT(parent.internalPointer()); // parent is a tree leaf item return createIndex(row, column, static_cast(static_cast(parent.internalPointer())->children().at(row))); } else { return QModelIndex(); } } else { return createIndex(row, column, static_cast(m_data->snapshots().at(row)->heapTree())); } } QModelIndex DataTreeModel::parent(const QModelIndex& child) const { if (child.internalPointer()) { const TreeLeafItem* item = static_cast(child.internalPointer()); if (item->parent()) { int row = m_nodeToRow.value(item->parent(), -1); Q_ASSERT(row != -1); // somewhere in the detailed heap tree return createIndex(row, 0, static_cast(item->parent())); } else { // snapshot item with heap tree return QModelIndex(); } } else { // snapshot item without detailed heap tree return QModelIndex(); } } QModelIndex DataTreeModel::indexForSnapshot(const SnapshotItem* snapshot) const { int idx = m_data->snapshots().indexOf(const_cast(snapshot)); if ( idx == -1 ) { return QModelIndex(); } return index(idx, 0); } QModelIndex DataTreeModel::indexForTreeLeaf(const TreeLeafItem* node) const { if (!m_data) { return QModelIndex(); } int row = m_nodeToRow.value(node, -1); if (row == -1) { return QModelIndex(); } return createIndex(row, 0, const_cast(static_cast(node))); } ModelItem DataTreeModel::itemForIndex(const QModelIndex& idx) const { if (!m_data || !idx.isValid() || idx.row() >= m_data->snapshots().count()) { - return ModelItem(0, 0); + return ModelItem(nullptr, nullptr); } if (idx.parent().isValid()) { Q_ASSERT(idx.internalPointer()); - return ModelItem(static_cast(idx.internalPointer()), 0); + return ModelItem(static_cast(idx.internalPointer()), nullptr); } else { - return ModelItem(0, m_data->snapshots().at(idx.row())); + return ModelItem(nullptr, m_data->snapshots().at(idx.row())); } } QModelIndex DataTreeModel::indexForItem(const ModelItem& item) const { if (!item.first && !item.second) { return QModelIndex(); } Q_ASSERT((item.first && !item.second) || (!item.first && item.second)); if (item.first) { return indexForTreeLeaf(item.first); } else { return indexForSnapshot(item.second); } } const SnapshotItem* DataTreeModel::snapshotForTreeLeaf(const TreeLeafItem* node) const { while (node->parent()) { node = node->parent(); } return m_heapRootToSnapshot.value(node); } diff --git a/visualizer/datatreemodel.h b/visualizer/datatreemodel.h index c776ca2..3224ed0 100644 --- a/visualizer/datatreemodel.h +++ b/visualizer/datatreemodel.h @@ -1,94 +1,94 @@ /* 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 . */ #ifndef MASSIF_DATATREEMODEL_H #define MASSIF_DATATREEMODEL_H #include #include "modelitem.h" namespace Massif { class FileData; class TreeLeafItem; class SnapshotItem; /** * A model that gives a tree representation of the full Massif data. Useful for e.g. ListViews. */ class DataTreeModel : public QAbstractItemModel { public: - explicit DataTreeModel(QObject* parent = 0); + explicit DataTreeModel(QObject* parent = nullptr); ~DataTreeModel() override; /** * That the source data for this model. */ void setSource(const FileData* data); /** * @return Item for given index. At maximum one of the pointers in the pair will be valid. */ ModelItem itemForIndex(const QModelIndex& idx) const; /** * @return Index for given item. At maximum one of the pointers should be valid in the input pair. */ QModelIndex indexForItem(const ModelItem& item) const; /** * @return Index for given snapshot, or invalid if it's not a detailed snapshot. */ QModelIndex indexForSnapshot(const SnapshotItem* snapshot) const; /** * @return Index for given TreeLeafItem, or invalid if it's not covered by this model. */ QModelIndex indexForTreeLeaf(const TreeLeafItem* node) const; enum CustomRoles { RawLabelRole = Qt::UserRole + 1, TreeItemRole, ModelItemRole, }; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& parent = QModelIndex()) const override; QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex& child) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; const SnapshotItem* snapshotForTreeLeaf(const TreeLeafItem* node) const; private: const FileData* m_data; void mapNodeToRow(const TreeLeafItem* node, const int row); QHash m_nodeToRow; QHash m_heapRootToSnapshot; }; } #endif // MASSIF_DATATREEMODEL_H diff --git a/visualizer/detailedcostmodel.cpp b/visualizer/detailedcostmodel.cpp index 05a9d3b..50293b4 100644 --- a/visualizer/detailedcostmodel.cpp +++ b/visualizer/detailedcostmodel.cpp @@ -1,415 +1,415 @@ /* 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 "detailedcostmodel.h" #include "massifdata/filedata.h" #include "massifdata/snapshotitem.h" #include "massifdata/treeleafitem.h" #include "massifdata/util.h" #include "KChartGlobal" #include "KChartDataValueAttributes" #include "KChartLineAttributes" #include #include #include #include #include #include using namespace Massif; namespace { QVariant colorForColumn(int column, int columnCount, int role) { QColor c = QColor::fromHsv(double(column + 1) / columnCount * 255, 255, 255); if (role == KChart::DatasetBrushRole) { return QBrush(c); } else { return QPen(c); } } } DetailedCostModel::DetailedCostModel(QObject* parent) - : QAbstractTableModel(parent), m_data(0), m_maxDatasetCount(10) + : QAbstractTableModel(parent), m_data(nullptr), m_maxDatasetCount(10) { } DetailedCostModel::~DetailedCostModel() { } void DetailedCostModel::setSource(const FileData* data) { if (m_data) { beginRemoveRows(QModelIndex(), 0, rowCount() - 1); - m_data = 0; + m_data = nullptr; m_columns.clear(); m_rows.clear(); m_nodes.clear(); m_peaks.clear(); endRemoveRows(); } if (data) { // get top cost points: // we traverse the detailed heap trees until the first fork QMultiMap sortColumnMap; foreach (const SnapshotItem* snapshot, data->snapshots()) { if (snapshot->heapTree()) { QVector nodes; foreach (const TreeLeafItem* node, snapshot->heapTree()->children()) { if (isBelowThreshold(node->label())) { continue; } // find interesting node, i.e. until first fork const TreeLeafItem* firstNode = node; while (node->children().size() == 1 && node->children().at(0)->cost() == node->cost()) { node = node->children().at(0); } if (node->children().isEmpty()) { // when we traverse the tree down until the end (i.e. no forks), // we end up in main() most probably, and that's uninteresting node = firstNode; } if (!sortColumnMap.values().contains(node->label())) { sortColumnMap.insert(node->cost(), node->label()); m_peaks[node->label()] = qMakePair(node, snapshot); } else { quint64 cost = sortColumnMap.key(node->label()); if (node->cost() > cost) { sortColumnMap.remove(cost, node->label()); cost = node->cost(); sortColumnMap.insert(cost, node->label()); m_peaks[node->label()].first = node; m_peaks[node->label()].second = snapshot; } } nodes << node; } m_rows << snapshot; m_nodes[snapshot] = nodes; } } // ugh, yet another bad usage of QList in Qt's API m_columns = sortColumnMap.values().toVector(); QAlgorithmsPrivate::qReverse(m_columns.begin(), m_columns.end()); if (m_rows.isEmpty()) { return; } // +1 for the offset (+0 would be m_rows.size() -1) beginInsertRows(QModelIndex(), 0, m_rows.size()); m_data = data; endInsertRows(); } } void DetailedCostModel::setMaximumDatasetCount(int count) { Q_ASSERT(count >= 0); const int currentCols = qMin(m_columns.size(), m_maxDatasetCount); const int newCols = qMin(m_columns.size(), count); if (currentCols == newCols) { return; } if (newCols < currentCols) { beginRemoveColumns(QModelIndex(), newCols * 2, currentCols * 2 - 1); } else { beginInsertColumns(QModelIndex(), currentCols * 2, newCols * 2 - 1); } m_maxDatasetCount = count; if (newCols < currentCols) { endRemoveColumns(); } else { endInsertColumns(); } Q_ASSERT(columnCount() == newCols * 2); } int DetailedCostModel::maximumDatasetCount() const { return m_maxDatasetCount; } QVariant DetailedCostModel::data(const QModelIndex& index, int role) const { // FIXME kdchart queries (-1, -1) for empty models if ( index.row() == -1 || index.column() == -1 ) { // qWarning() << "DetailedCostModel::data: FIXME fix kdchart views to not query model data for invalid indices!"; return QVariant(); } Q_ASSERT(index.row() >= 0 && index.row() < rowCount(index.parent())); Q_ASSERT(index.column() >= 0 && index.column() < columnCount(index.parent())); Q_ASSERT(m_data); Q_ASSERT(!index.parent().isValid()); if ( role == KChart::LineAttributesRole ) { static KChart::LineAttributes attributes; attributes.setDisplayArea(true); if (index == m_selection) { attributes.setTransparency(255); } else if (index.column() == m_selection.column()) { attributes.setTransparency(152); } else { attributes.setTransparency(127); } return QVariant::fromValue(attributes); } if (role == KChart::DatasetBrushRole || role == KChart::DatasetPenRole) { return colorForColumn(index.column(), columnCount(), role); } if ( role != Qt::DisplayRole && role != Qt::ToolTipRole ) { return QVariant(); } if (index.row() == 0) { if (role == Qt::ToolTipRole) { return QVariant(); } else { if (index.column() % 2 == 0) { // get x-coordinate of the last snapshot with cost below 0.1% of peak cost QVector< SnapshotItem* >::const_iterator it = m_data->snapshots().constBegin(); double time = 0; while (it != m_data->snapshots().constEnd() && (*it)->cost() < m_data->peak()->cost() * 0.001) { time = (*it)->time(); ++it; } return time; } else { // cost to 0 return 0; } } } const SnapshotItem* snapshot; if (role == Qt::ToolTipRole) { // hack: the ToolTip will only be queried by KChart and that one uses the // left index, but we want it to query the right one Q_ASSERT(index.row() < m_rows.size()); snapshot = m_rows.at(index.row()); } else { snapshot = m_rows.at(index.row() - 1); } if (index.column() % 2 == 0 && role != Qt::ToolTipRole) { return snapshot->time(); } else { - const TreeLeafItem* node = 0; + const TreeLeafItem* node = nullptr; const QByteArray needle = m_columns.at(index.column() / 2); foreach(const TreeLeafItem* n, m_nodes[snapshot]) { if (n->label() == needle) { node = n; break; } } if (role == Qt::ToolTipRole) { return tooltipForTreeLeaf(node, snapshot, needle); } else { return node ? node->cost() : 0; } } } QVariant DetailedCostModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && section % 2 == 0 && section < columnCount()) { if (role == KChart::DatasetBrushRole || role == KChart::DatasetPenRole) { return colorForColumn(section, columnCount(), role); } else if (role == Qt::DisplayRole ) { // only show name without memory address or location QByteArray label = prettyLabel(m_columns.at(section / 2)); if (label.indexOf("???") != -1) { return label; } int endPos = label.lastIndexOf('('); if (endPos == -1) { return label; } label = label.mid(0, endPos - 1); const int maxLen = 40; if (label.length() > maxLen) { label.resize(maxLen - 3); label.append("..."); } return label; } } return QAbstractItemModel::headerData(section, orientation, role); } int DetailedCostModel::columnCount(const QModelIndex&) const { return qMin(m_maxDatasetCount, m_columns.size()) * 2; } int DetailedCostModel::rowCount(const QModelIndex& parent) const { if (!m_data) { return 0; } if (parent.isValid()) { return 0; } else { // +1 to get a zero row first return m_rows.count() + 1; } } DetailedCostModel::Peaks DetailedCostModel::peaks() const { Peaks peaks; QHash::const_iterator it = m_peaks.constBegin(); while (it != m_peaks.end()) { int row = m_rows.indexOf(it->second); Q_ASSERT(row >= 0); int column = m_columns.indexOf(it->first->label()); if (column >= m_maxDatasetCount || column == -1) { ++it; continue; } Q_ASSERT(column >= 0); peaks[index(row + 1, column*2)] = it->first; ++it; } Q_ASSERT(peaks.size() == qMin(m_maxDatasetCount, m_columns.size())); return peaks; } QModelIndex DetailedCostModel::indexForSnapshot(const SnapshotItem* snapshot) const { int row = m_rows.indexOf(snapshot); if (row == -1) { return QModelIndex(); } return index(row + 1, 0); } QModelIndex DetailedCostModel::indexForTreeLeaf(const TreeLeafItem* node) const { int column = m_columns.indexOf(node->label()); if (column == -1 || column >= m_maxDatasetCount) { return QModelIndex(); } Nodes::const_iterator it = m_nodes.constBegin(); while (it != m_nodes.constEnd()) { if (it->contains(node)) { return index(m_rows.indexOf(it.key()), column * 2); } ++it; } return QModelIndex(); } ModelItem DetailedCostModel::itemForIndex(const QModelIndex& idx) const { if (!idx.isValid() || idx.parent().isValid() || idx.row() > rowCount() || idx.column() > columnCount()) { - return ModelItem(0, 0); + return ModelItem(nullptr, nullptr); } if (idx.row() == 0) { - return ModelItem(0, 0); + return ModelItem(nullptr, nullptr); } const QByteArray needle = m_columns.at(idx.column() / 2); for (int i = 1; i < 3 && idx.row() - i >= 0; ++i) { const SnapshotItem* snapshot = m_rows.at(idx.row() - i); foreach(const TreeLeafItem* n, m_nodes[snapshot]) { if (n->label() == needle) { - return ModelItem(n, 0); + return ModelItem(n, nullptr); } } } - return ModelItem(0, 0); + return ModelItem(nullptr, nullptr); } QModelIndex DetailedCostModel::indexForItem(const ModelItem& item) const { if (!item.first && !item.second) { return QModelIndex(); } Q_ASSERT((item.first && !item.second) || (!item.first && item.second)); if (item.first) { return indexForTreeLeaf(item.first); } else { return indexForSnapshot(item.second); } } void DetailedCostModel::setSelection(const QModelIndex& index) { m_selection = index; } void DetailedCostModel::hideFunction(const TreeLeafItem* node) { beginResetModel(); Nodes::iterator it = m_nodes.begin(); while (it != m_nodes.end()) { QVector< const TreeLeafItem* >::iterator it2 = it.value().begin(); while (it2 != it.value().end()) { if ((*it2)->label() == node->label()) { it2 = it.value().erase(it2); } else { ++it2; } } ++it; } int idx = m_columns.indexOf(node->label()); if (idx != -1) { m_columns.remove(idx); } endResetModel(); } void DetailedCostModel::hideOtherFunctions(const TreeLeafItem* node) { beginResetModel(); m_columns.clear(); m_columns << node->label(); Nodes::iterator it = m_nodes.begin(); const Nodes::iterator end = m_nodes.end(); while (it != end) { QVector< const TreeLeafItem* >::iterator it2 = it.value().begin(); while (it2 != it.value().end()) { if ((*it2)->label() != node->label()) { it2 = it.value().erase(it2); } else { ++it2; } } ++it; } endResetModel(); } diff --git a/visualizer/detailedcostmodel.h b/visualizer/detailedcostmodel.h index 39d0955..24cfb22 100644 --- a/visualizer/detailedcostmodel.h +++ b/visualizer/detailedcostmodel.h @@ -1,122 +1,122 @@ /* 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 . */ #ifndef MASSIF_DETAILEDCOSTMODEL_H #define MASSIF_DETAILEDCOSTMODEL_H #include #include #include "modelitem.h" namespace Massif { class FileData; class SnapshotItem; class TreeLeafItem; /** * A model that gives a tabular access on the costs in a massif output file. */ class DetailedCostModel : public QAbstractTableModel { public: - explicit DetailedCostModel(QObject* parent = 0); + explicit DetailedCostModel(QObject* parent = nullptr); ~DetailedCostModel() override; /** * That the source data for this model. */ void setSource(const FileData* data); int columnCount(const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; /** * Sets the maximum number of datasets in this model to @p count. */ void setMaximumDatasetCount(int count); int maximumDatasetCount() const; typedef QMap Peaks; /** * @return List of peaks with their heap tree leaf items. */ Peaks peaks() const; /** * @return ModelItem for given index. At maximum one of the pointers in the pair will be valid. */ ModelItem itemForIndex(const QModelIndex& idx) const; /** * @return Index for given item. Only one of the pointers in the pair should be valid. */ QModelIndex indexForItem(const ModelItem& item) const; /** * @return Index for given snapshot, or invalid if it's not a detailed snapshot. */ QModelIndex indexForSnapshot(const SnapshotItem* snapshot) const; /** * @return Index for given TreeLeafItem, or invalid if it's not covered by this model. */ QModelIndex indexForTreeLeaf(const TreeLeafItem* node) const; /** * Select @p index, which changes the graphical representation of its data. */ void setSelection(const QModelIndex& index); /** * Hide function @p func. */ void hideFunction(const TreeLeafItem* node); /** * Hide all functions except for @p func. */ void hideOtherFunctions(const TreeLeafItem* node); private: const FileData* m_data; // columns => label QVector m_columns; // only to sort snapshots by number QVector m_rows; typedef QHash > Nodes; // snapshot item => cost intensive nodes Nodes m_nodes; // peaks: Label => TreeLeafItem,Snapshot QHash m_peaks; // selected item QModelIndex m_selection; int m_maxDatasetCount; }; } #endif // MASSIF_DETAILEDCOSTMODEL_H diff --git a/visualizer/dotgraphgenerator.cpp b/visualizer/dotgraphgenerator.cpp index 2d4daaa..1262f4d 100644 --- a/visualizer/dotgraphgenerator.cpp +++ b/visualizer/dotgraphgenerator.cpp @@ -1,319 +1,319 @@ /* 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 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_snapshot(nullptr) , 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& knownNodes, - quint64& maxCost, GraphNode* parent = 0) + quint64& maxCost, GraphNode* parent = nullptr) { // 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; + return nullptr; } - GraphNode* node = knownNodes.value(item->label(), 0); + GraphNode* node = knownNodes.value(item->label(), nullptr); 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 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; } diff --git a/visualizer/dotgraphgenerator.h b/visualizer/dotgraphgenerator.h index b15b540..4d6e938 100644 --- a/visualizer/dotgraphgenerator.h +++ b/visualizer/dotgraphgenerator.h @@ -1,81 +1,81 @@ /* 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 . */ #ifndef MASSIF_DOTGRAPHGENERATOR_H #define MASSIF_DOTGRAPHGENERATOR_H #include #include #include namespace Massif { class SnapshotItem; class TreeLeafItem; struct GraphNode; class DotGraphGenerator : public QThread { Q_OBJECT public: /** * Generates a Dot graph file representing @p snapshot * and writes it to a temporary file. */ - DotGraphGenerator(const SnapshotItem* snapshot, const QString& timeUnit, QObject* parent = 0); + DotGraphGenerator(const SnapshotItem* snapshot, const QString& timeUnit, QObject* parent = nullptr); /** * Generates a Dot graph file representing @p node * and writes it to a temporary file. */ - DotGraphGenerator(const TreeLeafItem* node, const QString& timeUnit, QObject* parent = 0); + DotGraphGenerator(const TreeLeafItem* node, const QString& timeUnit, QObject* parent = nullptr); ~DotGraphGenerator() override; /** * Stops generating the Dot graph file and deletes the temp file. */ void cancel(); void run() override; /** * @return A path to the generated Dot graph file. Path might be empty if errors occurred. */ QString outputFile() const; /** * @return The GraphViz node ID for the most cost-intensive tree leaf item. */ QString mostCostIntensiveGraphvizId() const; private: void nodeToDot(GraphNode* node, QTextStream& out, const QString& parentId = QString(), quint64 cost = 0); const SnapshotItem* m_snapshot; const TreeLeafItem* m_node; QTemporaryFile m_file; bool m_canceled; quint64 m_maxCost; QString m_timeUnit; QString m_costlyGraphvizId; quint64 m_highestCost; }; } #endif // MASSIF_DOTGRAPHGENERATOR_H diff --git a/visualizer/totalcostmodel.cpp b/visualizer/totalcostmodel.cpp index 9cae700..aaed476 100644 --- a/visualizer/totalcostmodel.cpp +++ b/visualizer/totalcostmodel.cpp @@ -1,194 +1,194 @@ /* 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 "totalcostmodel.h" #include "massifdata/filedata.h" #include "massifdata/snapshotitem.h" #include "massifdata/treeleafitem.h" #include "massifdata/util.h" #include "KChartGlobal" #include "KChartLineAttributes" #include #include using namespace Massif; -TotalCostModel::TotalCostModel(QObject* parent): QAbstractTableModel(parent), m_data(0) +TotalCostModel::TotalCostModel(QObject* parent): QAbstractTableModel(parent), m_data(nullptr) { } TotalCostModel::~TotalCostModel() { } void TotalCostModel::setSource(const FileData* data) { if (m_data) { beginRemoveRows(QModelIndex(), 0, rowCount() - 1); - m_data = 0; + m_data = nullptr; endRemoveRows(); } if (data) { beginInsertRows(QModelIndex(), 0, data->snapshots().size() - 1); m_data = data; endInsertRows(); } } QModelIndex TotalCostModel::peak() const { Q_ASSERT(m_data); if (!m_data->peak()) { return QModelIndex(); } return index(m_data->snapshots().indexOf(m_data->peak()), 0); } QVariant TotalCostModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_ASSERT(orientation != Qt::Horizontal || section < columnCount()); if (section == 0 && orientation == Qt::Horizontal) { if ( role == KChart::DatasetPenRole ) { return QPen(Qt::red); } else if ( role == KChart::DatasetBrushRole ) { return QBrush(Qt::red); } else if ( role == Qt::DisplayRole ) { return i18n("Total Memory Heap Consumption"); } } return QAbstractItemModel::headerData(section, orientation, role); } QVariant TotalCostModel::data(const QModelIndex& index, int role) const { // FIXME kdchart queries (-1, -1) for empty models if ( index.row() == -1 || index.column() == -1 ) { // qWarning() << "TotalCostModel::data: FIXME fix kdchart views to not query model data for invalid indices!"; return QVariant(); } Q_ASSERT(index.row() >= 0 && index.row() < rowCount(index.parent())); Q_ASSERT(index.column() >= 0 && index.column() < columnCount(index.parent())); Q_ASSERT(m_data); Q_ASSERT(!index.parent().isValid()); if ( role == KChart::LineAttributesRole ) { static KChart::LineAttributes attributes; attributes.setDisplayArea(true); if (index == m_selection) { attributes.setTransparency(255); } else { attributes.setTransparency(50); } return QVariant::fromValue(attributes); } if ( role == KChart::DatasetPenRole ) { return QPen(Qt::red); } else if ( role == KChart::DatasetBrushRole ) { return QBrush(Qt::red); } if ( role != Qt::DisplayRole && role != Qt::ToolTipRole ) { return QVariant(); } if ( role == Qt::ToolTipRole ) { // hack: the ToolTip will only be queried by KDChart and that one uses the // left index, but we want it to query the right one. but we also need to // take the very last data set into account to prevent an overflow there const SnapshotItem* snapshot = m_data->snapshots().at(qMin(index.row() + 1, m_data->snapshots().size() - 1)); return i18n("Snapshot #%1:\n" "Heap cost of %2\n" "Extra heap cost of %3\n" "Stack cost of %4", snapshot->number(), prettyCost(snapshot->memHeap()), prettyCost(snapshot->memHeapExtra()), prettyCost(snapshot->memStacks())); } const SnapshotItem* snapshot = m_data->snapshots().at(index.row()); if (index.column() == 0) { return snapshot->time(); } else { Q_ASSERT(index.column() == 1); return snapshot->cost(); } } int TotalCostModel::columnCount(const QModelIndex&) const { return 2; } int TotalCostModel::rowCount(const QModelIndex& parent) const { if (!m_data) { return 0; } if (parent.isValid()) { return 0; } else { // snapshot item return m_data->snapshots().count(); } } QModelIndex TotalCostModel::indexForSnapshot(const SnapshotItem* snapshot) const { int row = m_data->snapshots().indexOf(const_cast(snapshot)); if (row == -1) { return QModelIndex(); } return index(row, 0); } QModelIndex TotalCostModel::indexForTreeLeaf(const TreeLeafItem* node) const { Q_UNUSED(node) return QModelIndex(); } ModelItem TotalCostModel::itemForIndex(const QModelIndex& idx) const { if (!idx.isValid() || idx.parent().isValid() || idx.row() > rowCount() || idx.column() > columnCount()) { - return ModelItem(0, 0); + return ModelItem(nullptr, nullptr); } const SnapshotItem* snapshot = m_data->snapshots().at(idx.row()); - return ModelItem(0, snapshot); + return ModelItem(nullptr, snapshot); } QModelIndex TotalCostModel::indexForItem(const ModelItem& item) const { if ((!item.first && !item.second) || item.first) { return QModelIndex(); } return indexForSnapshot(item.second); } void TotalCostModel::setSelection(const QModelIndex& index) { m_selection = index; } diff --git a/visualizer/totalcostmodel.h b/visualizer/totalcostmodel.h index ed618c3..fd37b67 100644 --- a/visualizer/totalcostmodel.h +++ b/visualizer/totalcostmodel.h @@ -1,93 +1,93 @@ /* 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 . */ #ifndef MASSIF_TOTALCOSTMODEL_H #define MASSIF_TOTALCOSTMODEL_H #include #include "modelitem.h" namespace Massif { class FileData; class TreeLeafItem; class SnapshotItem; /** * A model that gives a tabular access on the costs in a massif output file. */ class TotalCostModel : public QAbstractTableModel { public: - explicit TotalCostModel(QObject* parent = 0); + explicit TotalCostModel(QObject* parent = nullptr); ~TotalCostModel() override; /** * That the source data for this model. */ void setSource(const FileData* data); /** * @return Index of the peak dataset. */ QModelIndex peak() const; int columnCount(const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; /** * @return Item for given index. At maximum one of the pointers in the pair will be valid. */ ModelItem itemForIndex(const QModelIndex& idx) const; /** * @return Index for given item. Only one of the pointers in the pair should be valid. */ QModelIndex indexForItem(const ModelItem& item) const; /** * @return Index for given snapshot, or invalid if it's not a detailed snapshot. */ QModelIndex indexForSnapshot(const SnapshotItem* snapshot) const; /** * @return Index for given TreeLeafItem, or invalid if it's not covered by this model. */ QModelIndex indexForTreeLeaf(const TreeLeafItem* node) const; /** * Select @p index, which changes the graphical representation of its data. */ void setSelection(const QModelIndex& index); private: const FileData* m_data; // selected item QModelIndex m_selection; }; } #endif // MASSIF_TOTALCOSTMODEL_H