diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp index 4545089..9ab62df 100644 --- a/app/mainwindow.cpp +++ b/app/mainwindow.cpp @@ -1,859 +1,861 @@ /* 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 "KDChartChart" #include "KDChartPlotter" #include "massifdata/filedata.h" #include "massifdata/parser.h" #include "massifdata/parseworker.h" #include "massifdata/snapshotitem.h" #include "massifdata/treeleafitem.h" #include "massifdata/util.h" #include "visualizer/totalcostmodel.h" #include "visualizer/detailedcostmodel.h" #include "visualizer/datatreemodel.h" #include "visualizer/filtereddatatreemodel.h" #include "visualizer/dotgraphgenerator.h" #include "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 #include #include #include #ifdef HAVE_KGRAPHVIEWER #include #endif using namespace Massif; using namespace KDChart; // Helper function static KConfigGroup allocatorConfig() { return KGlobal::config()->group("Allocators"); } MainWindow::MainWindow(QWidget* parent, Qt::WindowFlags f) : KParts::MainWindow(parent, f) , m_toggleTotal(0) , m_selectPeak(0) , m_recentFiles(0) , m_box(new QSpinBox(this)) , m_zoomIn(0) , m_zoomOut(0) , m_focusExpensive(0) , m_close(0) , m_print(0) , m_stopParser(0) , m_allocatorModel(new QStringListModel(this)) , m_newAllocator(0) , m_removeAllocator(0) , m_shortenTemplates(0) , m_currentDocument(0) { ui.setupUi(this); setWindowTitle(i18n("Massif Visualizer")); //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("kgraphviewerpart").factory(); if (factory) { KParts::ReadOnlyPart* readOnlyPart = factory->create("kgraphviewerpart", 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("callgraphToolBar"); removeToolBar(callgraphToolbar); delete callgraphToolbar; } //END KGraphViewer connect(ui.documents, SIGNAL(currentChanged(int)), this, SLOT(documentChanged())); //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, SIGNAL(modelReset()), this, SLOT(allocatorsChanged())); connect(m_allocatorModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(allocatorsChanged())); connect(ui.dataTreeView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(dataTreeContextMenuRequested(QPoint))); ui.dataTreeView->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui.allocatorView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(allocatorViewContextMenuRequested(QPoint))); ui.allocatorView->setContextMenuPolicy(Qt::CustomContextMenu); //END custom allocators setupActions(); setupGUI(StandardWindowOptions(Default ^ StatusBar)); statusBar()->hide(); // open page ui.stackedWidget->setCurrentWidget(ui.openPage); } MainWindow::~MainWindow() { while (ui.documents->count()) { closeCurrentFile(); } m_recentFiles->saveEntries(KGlobal::config()->group( QString() )); } void MainWindow::setupActions() { QAction* openFile = KStandardAction::open(this, SLOT(openFile()), actionCollection()); QAction* reload = KStandardAction::redisplay(this, SLOT(reloadCurrentFile()), actionCollection()); actionCollection()->addAction("file_reload", reload); m_recentFiles = KStandardAction::openRecent(this, SLOT(openFile(QUrl)), actionCollection()); m_recentFiles->loadEntries(KGlobal::config()->group( QString() )); m_close = KStandardAction::close(this, SLOT(closeCurrentFile()), actionCollection()); m_close->setEnabled(false); m_print = KStandardAction::print(this, SLOT(showPrintPreviewDialog()), actionCollection()); actionCollection()->addAction("file_print", m_print); m_print->setEnabled(false); m_stopParser = new QAction(i18n("Stop Parser"), actionCollection()); m_stopParser->setIcon(QIcon::fromTheme("process-stop")); connect(m_stopParser, SIGNAL(triggered(bool)), this, SLOT(stopParser())); m_stopParser->setEnabled(false); actionCollection()->addAction("file_stopparser", m_stopParser); KStandardAction::quit(qApp, SLOT(closeAllWindows()), actionCollection()); KStandardAction::preferences(this, SLOT(preferences()), actionCollection()); m_shortenTemplates = new QAction(QIcon::fromTheme("shortentemplates"), i18n("Shorten Templates"), actionCollection()); m_shortenTemplates->setCheckable(true); m_shortenTemplates->setChecked(Settings::shortenTemplates()); connect(m_shortenTemplates, SIGNAL(toggled(bool)), SLOT(slotShortenTemplates(bool))); actionCollection()->addAction("shorten_templates", m_shortenTemplates); m_toggleTotal = new QAction(QIcon::fromTheme("office-chart-area"), i18n("Toggle total cost graph"), actionCollection()); m_toggleTotal->setCheckable(true); m_toggleTotal->setChecked(true); m_toggleTotal->setEnabled(false); connect(m_toggleTotal, SIGNAL(toggled(bool)), SLOT(showTotalGraph(bool))); actionCollection()->addAction("toggle_total", m_toggleTotal); m_toggleDetailed = new QAction(QIcon::fromTheme("office-chart-area-stacked"), i18n("Toggle detailed cost graph"), actionCollection()); m_toggleDetailed->setCheckable(true); m_toggleDetailed->setChecked(true); m_toggleDetailed->setEnabled(false); connect(m_toggleDetailed, SIGNAL(toggled(bool)), SLOT(showDetailedGraph(bool))); actionCollection()->addAction("toggle_detailed", m_toggleDetailed); // If the toolbar is still there, kgraphviewer is available at runtime. #ifdef HAVE_KGRAPHVIEWER if (toolBar("callgraphToolBar")) { m_zoomIn = KStandardAction::zoomIn(this, SLOT(zoomIn()), actionCollection()); actionCollection()->addAction("zoomIn", m_zoomIn); m_zoomOut = KStandardAction::zoomOut(this, SLOT(zoomOut()), actionCollection()); actionCollection()->addAction("zoomOut", m_zoomOut); m_focusExpensive = new QAction(QIcon::fromTheme("flag-red"), i18n("Focus most expensive node"), actionCollection()); m_toggleDetailed->setEnabled(false); connect(m_focusExpensive, SIGNAL(triggered()), this, SLOT(focusExpensiveGraphNode())); actionCollection()->addAction("focusExpensive", m_focusExpensive); } #endif m_selectPeak = new QAction(QIcon::fromTheme("flag-red"), i18n("Select peak snapshot"), ui.dataTreeDock); m_selectPeak->setEnabled(false); connect(m_selectPeak, SIGNAL(triggered()), this, SLOT(selectPeakSnapshot())); actionCollection()->addAction("selectPeak", m_selectPeak); QAction* stackNumAction = actionCollection()->addAction("stackNum"); 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, SIGNAL(valueChanged(int)), this, SLOT(setStackNum(int))); stackNumLayout->addWidget(m_box); stackNumWidget->setLayout(stackNumLayout); // FIXME: Port to KF5 //stackNumAction->setDefaultWidget(stackNumWidget); //BEGIN custom allocators m_newAllocator = new QAction(QIcon::fromTheme("list-add"), i18n("add"), ui.allocatorDock); m_newAllocator->setToolTip(i18n("add custom allocator")); connect(m_newAllocator, SIGNAL(triggered()), this, SLOT(slotNewAllocator())); ui.dockMenuBar->addAction(m_newAllocator); m_removeAllocator = new QAction(QIcon::fromTheme("list-remove"), i18n("remove"), ui.allocatorDock); m_newAllocator->setToolTip(i18n("remove selected allocator")); connect(m_removeAllocator, SIGNAL(triggered()), this, SLOT(slotRemoveAllocator())); connect(ui.allocatorView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(allocatorSelectionChanged())); m_removeAllocator->setEnabled(false); ui.dockMenuBar->addAction(m_removeAllocator); m_markCustomAllocator = new QAction(i18n("mark as custom allocator"), ui.allocatorDock); connect(m_markCustomAllocator, SIGNAL(triggered()), this, SLOT(slotMarkCustomAllocator()), Qt::QueuedConnection); //END custom allocators //BEGIN hiding functions m_hideFunction = new QAction(i18n("hide function"), this); connect(m_hideFunction, SIGNAL(triggered()), this, SLOT(slotHideFunction())); m_hideOtherFunctions = new QAction(i18n("hide other functions"), this); connect(m_hideOtherFunctions, SIGNAL(triggered()), this, SLOT(slotHideOtherFunctions())); //END hiding functions //dock actions actionCollection()->addAction("toggleDataTree", ui.dataTreeDock->toggleViewAction()); actionCollection()->addAction("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, SIGNAL(settingsChanged(QString)), this, SLOT(settingsChanged())); dlg->show(); } void MainWindow::settingsChanged() { if (Settings::self()->shortenTemplates() != m_shortenTemplates->isChecked()) { m_shortenTemplates->setChecked(Settings::self()->shortenTemplates()); } Settings::self()->writeConfig(); if (m_currentDocument) { m_currentDocument->updateHeader(); m_currentDocument->updatePeaks(); } ui.dataTreeView->viewport()->update(); } void MainWindow::openFile() { const QList files = KFileDialog::getOpenUrls(QUrl("kfiledialog:///massif-visualizer"), QString("application/x-valgrind-massif"), this, i18n("Open Massif Output File")); foreach (const QUrl& file, files) { openFile(file); } } void MainWindow::reloadCurrentFile() { if (m_currentDocument->file().isValid()) { openFile(QUrl(m_currentDocument->file())); } } void MainWindow::stopParser() { Q_ASSERT(m_currentDocument); ParseWorker* parseWorker = m_documentsParseWorkers.take(m_currentDocument); QThread* thread = parseWorker->thread(); parseWorker->stop(); parseWorker->deleteLater(); thread->quit(); thread->wait(); m_stopParser->setEnabled(!m_documentsParseWorkers.isEmpty()); } 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(this); documentWidget->setLoadingMessage(i18n("loading file %1...", file.toString())); m_changingSelections.insert(documentWidget, false); // Create dedicated thread for this document. ParseWorker* parseWorker = new ParseWorker; QThread* thread = new QThread(this); thread->start(); parseWorker->moveToThread(thread); // Register thread in the hash map. m_documentsParseWorkers.insert(documentWidget, parseWorker); 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 { ui.documents->addTab(documentWidget, file.fileName()); } connect(parseWorker, SIGNAL(finished(QUrl, Massif::FileData*)), documentWidget, SLOT(parserFinished(QUrl, Massif::FileData*))); connect(parseWorker, SIGNAL(error(QString, QString)), documentWidget, SLOT(showError(QString, QString))); connect(parseWorker, SIGNAL(progressRange(int, int)), documentWidget, SLOT(setRange(int,int))); connect(parseWorker, SIGNAL(progress(int)), documentWidget, SLOT(setProgress(int))); connect(documentWidget, SIGNAL(loadingFinished()), this, SLOT(documentChanged())); parseWorker->parse(file, m_allocatorModel->stringList()); m_stopParser->setEnabled(true); m_recentFiles->addUrl(file); ui.stackedWidget->setCurrentWidget(ui.displayPage); } void MainWindow::treeSelectionChanged(const QModelIndex& now, const QModelIndex& before) { if (!m_currentDocument || currentChangingSelections()) { return; } if (now == before) { return; } setCurrentChangingSelections(true); const QPair< TreeLeafItem*, SnapshotItem* >& item = m_currentDocument->dataTreeModel()->itemForIndex( m_currentDocument->dataTreeFilterModel()->mapToSource(now) ); if (now.parent().isValid()) { m_currentDocument->detailedCostModel()->setSelection(m_currentDocument->detailedCostModel()->indexForItem(item)); m_currentDocument->totalCostModel()->setSelection(QModelIndex()); } else { m_currentDocument->totalCostModel()->setSelection(m_currentDocument->totalCostModel()->indexForItem(item)); m_currentDocument->detailedCostModel()->setSelection(QModelIndex()); } m_currentDocument->chart()->update(); #ifdef HAVE_KGRAPHVIEWER if (m_currentDocument->graphViewer()) { m_currentDocument->showDotGraph(item); } #endif setCurrentChangingSelections(false); } void MainWindow::detailedItemClicked(const QModelIndex& idx) { if (!m_currentDocument || currentChangingSelections()) { return; } setCurrentChangingSelections(true); m_currentDocument->detailedCostModel()->setSelection(idx); m_currentDocument->totalCostModel()->setSelection(QModelIndex()); // 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 const QModelIndex _idx = m_currentDocument->detailedCostModel()->index(idx.row() + 1, idx.column(), idx.parent()); ui.dataTreeView->selectionModel()->clearSelection(); QPair< TreeLeafItem*, SnapshotItem* > item = m_currentDocument->detailedCostModel()->itemForIndex(_idx); const QModelIndex& newIndex = m_currentDocument->dataTreeFilterModel()->mapFromSource( m_currentDocument->dataTreeModel()->indexForItem(item) ); ui.dataTreeView->selectionModel()->setCurrentIndex(newIndex, QItemSelectionModel::Select | QItemSelectionModel::Rows); ui.dataTreeView->scrollTo(ui.dataTreeView->selectionModel()->currentIndex()); m_currentDocument->chart()->update(); #ifdef HAVE_KGRAPHVIEWER if (m_currentDocument->graphViewer()) { m_currentDocument->showDotGraph(item); } #endif setCurrentChangingSelections(false); } void MainWindow::totalItemClicked(const QModelIndex& idx_) { if (!m_currentDocument || currentChangingSelections()) { return; } setCurrentChangingSelections(true); QModelIndex idx = idx_.model()->index(idx_.row() + 1, idx_.column(), idx_.parent()); m_currentDocument->detailedCostModel()->setSelection(QModelIndex()); m_currentDocument->totalCostModel()->setSelection(idx); QPair< TreeLeafItem*, SnapshotItem* > item = m_currentDocument->totalCostModel()->itemForIndex(idx); ui.dataTreeView->selectionModel()->clearSelection(); const QModelIndex& newIndex = m_currentDocument->dataTreeFilterModel()->mapFromSource( m_currentDocument->dataTreeModel()->indexForItem(item) ); ui.dataTreeView->selectionModel()->setCurrentIndex(newIndex, QItemSelectionModel::Select | QItemSelectionModel::Rows); ui.dataTreeView->scrollTo(ui.dataTreeView->selectionModel()->currentIndex()); m_currentDocument->chart()->update(); setCurrentChangingSelections(false); } void MainWindow::closeCurrentFile() { stopParser(); // Tab is now removed int tabToCloseIndex = ui.documents->currentIndex(); m_changingSelections.remove(m_currentDocument); ui.documents->widget(tabToCloseIndex)->deleteLater(); ui.documents->removeTab(tabToCloseIndex); } void MainWindow::showDetailedGraph(bool show) { if (!m_currentDocument) { return; } m_currentDocument->detailedDiagram()->setHidden(!show); m_toggleDetailed->setChecked(show); m_currentDocument->chart()->update(); } void MainWindow::showTotalGraph(bool show) { if (!m_currentDocument) { return; } m_currentDocument->totalDiagram()->setHidden(!show); m_toggleTotal->setChecked(show); m_currentDocument->chart()->update(); } #ifdef HAVE_KGRAPHVIEWER void MainWindow::zoomIn() { Q_ASSERT(m_currentDocument->graphViewer()); m_currentDocument->graphViewer()->zoomIn(); } void MainWindow::zoomOut() { Q_ASSERT(m_currentDocument->graphViewer()); m_currentDocument->graphViewer()->zoomOut(); } void MainWindow::focusExpensiveGraphNode() { Q_ASSERT(m_currentDocument); m_currentDocument->focusExpensiveGraphNode(); } #endif void MainWindow::selectPeakSnapshot() { Q_ASSERT(m_currentDocument); ui.dataTreeView->selectionModel()->clearSelection(); ui.dataTreeView->selectionModel()->setCurrentIndex( m_currentDocument->dataTreeFilterModel()->mapFromSource( m_currentDocument->dataTreeModel()->indexForSnapshot(m_currentDocument->data()->peak())), QItemSelectionModel::Select | QItemSelectionModel::Rows ); } void MainWindow::setStackNum(int num) { if (!m_currentDocument || !m_currentDocument->isLoaded()) { return; } m_currentDocument->detailedCostModel()->setMaximumDatasetCount(num); m_currentDocument->updatePeaks(); } 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 = m_currentDocument->dataTreeFilterModel()->mapToSource( ui.dataTreeView->indexAt(pos)); if (!idx.isValid()) { return; } if (!idx.parent().isValid()) { // snapshot item return; } QMenu menu; TreeLeafItem* item = m_currentDocument->dataTreeModel()->itemForIndex(idx).first; Q_ASSERT(item); prepareActions(&menu, item); menu.exec(ui.dataTreeView->mapToGlobal(pos)); } void MainWindow::chartContextMenuRequested(const QPoint& pos) { const QPoint dPos = m_currentDocument->detailedDiagram()->mapFromGlobal(m_currentDocument->chart()->mapToGlobal(pos)); const QModelIndex idx = m_currentDocument->detailedDiagram()->indexAt(dPos); if (!idx.isValid()) { return; } // 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 const QModelIndex _idx = m_currentDocument->detailedCostModel()->index(idx.row() + 1, idx.column(), idx.parent()); QPair< TreeLeafItem*, SnapshotItem* > item = m_currentDocument->detailedCostModel()->itemForIndex(_idx); if (!item.first) { return; } QMenu menu; menu.addAction(m_markCustomAllocator); prepareActions(&menu, item.first); menu.exec(m_currentDocument->detailedDiagram()->mapToGlobal(dPos)); } void MainWindow::prepareActions(QMenu* menu, TreeLeafItem* item) { QString func = functionInLabel(item->label()); if (func.length() > 40) { func.resize(40); func.append("..."); } menu->setTitle(func); m_markCustomAllocator->setData(item->label()); menu->addAction(m_markCustomAllocator); m_hideFunction->setData(QVariant::fromValue(item)); menu->addAction(m_hideFunction); m_hideOtherFunctions->setData(QVariant::fromValue(item)); menu->addAction(m_hideOtherFunctions); } void MainWindow::slotHideFunction() { m_currentDocument->detailedCostModel()->hideFunction(m_hideFunction->data().value()); } void MainWindow::slotHideOtherFunctions() { m_currentDocument->detailedCostModel()->hideOtherFunctions(m_hideOtherFunctions->data().value()); } void MainWindow::slotShortenTemplates(bool shorten) { if (shorten == Settings::self()->shortenTemplates()) { return; } Settings::self()->setShortenTemplates(shorten); settingsChanged(); } void MainWindow::showPrintPreviewDialog() { Q_ASSERT(m_currentDocument); QPrinter printer; QPrintPreviewDialog *ppd = new QPrintPreviewDialog(&printer, this); ppd->setAttribute(Qt::WA_DeleteOnClose); connect(ppd, SIGNAL(paintRequested(QPrinter*)), SLOT(printFile(QPrinter*))); ppd->setWindowTitle(i18n("Massif Chart Print Preview")); ppd->resize(800, 600); ppd->exec(); } void MainWindow::printFile(QPrinter *printer) { QPainter painter; painter.begin(printer); m_currentDocument->chart()->paint(&painter, printer->pageRect()); painter.end(); } void MainWindow::updateWindowTitle() { if (m_currentDocument && m_currentDocument->isLoaded()) { setWindowTitle(i18n("Massif Visualizer - evaluation of %1 (%2)", m_currentDocument->data()->cmd(), m_currentDocument->file().fileName())); } else { setWindowTitle(i18n("Massif Visualizer")); } } void MainWindow::documentChanged() { if (m_currentDocument) { if (m_currentDocument->totalDiagram()) { disconnect(m_currentDocument->totalDiagram(), SIGNAL(clicked(QModelIndex)), this, SLOT(totalItemClicked(QModelIndex))); } if (m_currentDocument->detailedDiagram()) { disconnect(m_currentDocument->detailedDiagram(), SIGNAL(clicked(QModelIndex)), this, SLOT(detailedItemClicked(QModelIndex))); } if (m_currentDocument->chart()) { disconnect(m_currentDocument->chart(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(chartContextMenuRequested(QPoint))); } disconnect(m_currentDocument, SIGNAL(tabChanged(int)), this, SLOT(documentTabChanged(int))); disconnect(ui.filterDataTree, SIGNAL(textChanged(QString)), m_currentDocument->dataTreeFilterModel(), SLOT(setFilter(QString))); disconnect(ui.dataTreeView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(treeSelectionChanged(QModelIndex,QModelIndex))); } m_currentDocument = qobject_cast(ui.documents->currentWidget()); updateWindowTitle(); #ifdef HAVE_KGRAPHVIEWER if (toolBar("callgraphToolBar")) { m_zoomIn->setEnabled(m_currentDocument && m_currentDocument->graphViewer()); m_zoomOut->setEnabled(m_currentDocument && m_currentDocument->graphViewer()); m_focusExpensive->setEnabled(m_currentDocument && m_currentDocument->graphViewer()); } #endif m_print->setEnabled(m_currentDocument && m_currentDocument->isLoaded()); m_selectPeak->setEnabled(m_currentDocument && m_currentDocument->data()); actionCollection()->action("file_reload")->setEnabled(m_currentDocument); m_toggleDetailed->setEnabled(m_currentDocument && m_currentDocument->detailedDiagram()); m_toggleTotal->setEnabled(m_currentDocument && m_currentDocument->totalDiagram()); m_close->setEnabled(m_currentDocument); if (!m_currentDocument) { ui.dataTreeView->setModel(0); ui.stackedWidget->setCurrentWidget(ui.openPage); return; } ui.dataTreeView->setModel(m_currentDocument->dataTreeFilterModel()); if (m_currentDocument->totalDiagram()) { connect(m_currentDocument->totalDiagram(), SIGNAL(clicked(QModelIndex)), this, SLOT(totalItemClicked(QModelIndex))); } if (m_currentDocument->detailedDiagram()) { connect(m_currentDocument->detailedDiagram(), SIGNAL(clicked(QModelIndex)), this, SLOT(detailedItemClicked(QModelIndex))); } if (m_currentDocument->detailedCostModel()) { m_box->setValue(m_currentDocument->detailedCostModel()->maximumDatasetCount()); } if (m_currentDocument->chart()) { connect(m_currentDocument->chart(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(chartContextMenuRequested(QPoint))); } connect(ui.filterDataTree, SIGNAL(textChanged(QString)), m_currentDocument->dataTreeFilterModel(), SLOT(setFilter(QString))); connect(ui.dataTreeView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(treeSelectionChanged(QModelIndex,QModelIndex))); connect(m_currentDocument, SIGNAL(tabChanged(int)), this, SLOT(documentTabChanged(int))); if (m_toggleDetailed->isEnabled()) { m_toggleDetailed->setChecked(!m_currentDocument->detailedDiagram()->isHidden()); } if (m_toggleTotal->isEnabled()) { m_toggleTotal->setChecked(!m_currentDocument->totalDiagram()->isHidden()); } #ifdef HAVE_KGRAPHVIEWER documentTabChanged(m_currentDocument->currentIndex()); #else documentTabChanged(0); #endif } bool MainWindow::currentChangingSelections() const { return m_changingSelections[m_currentDocument]; } void MainWindow::setCurrentChangingSelections(bool changingSelections) { m_changingSelections[m_currentDocument] = changingSelections; } void MainWindow::documentTabChanged(int index) { Q_ASSERT(m_currentDocument); toolBar("chartToolBar")->setVisible(index == 0); foreach(QAction* action, toolBar("chartToolBar")->actions()) { action->setEnabled(m_currentDocument->data() && index == 0); } toolBar("callgraphToolBar")->setVisible(index == 1); foreach(QAction* action, toolBar("callgraphToolBar")->actions()) { action->setEnabled(m_currentDocument->data() && index == 1); } } #include "mainwindow.moc" diff --git a/visualizer/dotgraphgenerator.cpp b/visualizer/dotgraphgenerator.cpp index 0da3c2e..0499061 100644 --- a/visualizer/dotgraphgenerator.cpp +++ b/visualizer/dotgraphgenerator.cpp @@ -1,318 +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 -#include +#include namespace Massif { struct GraphNode { const TreeLeafItem* item; // incoming calls + cost QHash children; // outgoing calls QVector parents; quint64 accumulatedCost; bool visited; quint32 belowThresholdCount; quint64 belowThresholdCost; }; } Q_DECLARE_TYPEINFO(Massif::GraphNode, Q_MOVABLE_TYPE); using namespace Massif; DotGraphGenerator::DotGraphGenerator(const SnapshotItem* snapshot, const QString& timeUnit, QObject* parent) : QThread(parent) , m_snapshot(snapshot) , m_node(snapshot->heapTree()) , m_canceled(false) , m_timeUnit(timeUnit) , m_highestCost(0) { m_file.open(); } DotGraphGenerator::DotGraphGenerator(const TreeLeafItem* node, const QString& timeUnit, QObject* parent) : QThread(parent) , m_snapshot(0) , m_node(node) , m_canceled(false) , m_timeUnit(timeUnit) , m_highestCost(0) { m_file.open(); } DotGraphGenerator::~DotGraphGenerator() { qDebug() << "closing generator, file will get removed"; } void DotGraphGenerator::cancel() { m_canceled = true; } QString getLabel(const TreeLeafItem* node) { QString label = prettyLabel(node->label()); const int lineWidth = 40; if (label.length() > lineWidth) { int lastPos = 0; int lastBreak = 0; while (true) { lastPos = label.indexOf(',', lastPos); if (lastPos == -1) { break; } else if (lastPos - lastBreak > lineWidth) { label.insert(lastPos, "\\n\\ \\ "); lastPos = lastPos + 4; lastBreak = lastPos; continue; } else { lastPos++; } } } return label; } QString getColor(quint64 cost, quint64 maxCost) { Q_ASSERT(cost <= maxCost); const double ratio = (double(cost) / maxCost); Q_ASSERT(ratio <= 1.0); return QColor::fromHsv(120 - ratio * 120, (-((ratio-1) * (ratio-1))) * 255 + 255, 255, 255).name(); // return QColor::fromHsv(120 - ratio * 120, 255, 255).name(); } GraphNode* buildGraph(const TreeLeafItem* item, QMultiHash& knownNodes, quint64& maxCost, GraphNode* parent = 0) { // merge below-threshold items if (parent && item->children().isEmpty()) { static QRegExp matchBT("in ([0-9]+) places, all below massif's threshold", Qt::CaseSensitive, QRegExp::RegExp2); if (matchBT.indexIn(QString::fromLatin1(item->label())) != -1) { parent->belowThresholdCost += item->cost(); parent->belowThresholdCount += matchBT.cap(1).toInt(); } return 0; } GraphNode* node = knownNodes.value(item->label(), 0); if (!node) { node = new GraphNode; knownNodes.insert(item->label(), node); node->item = item; node->accumulatedCost = 0; node->visited = false; node->belowThresholdCost = 0; node->belowThresholdCount = 0; } if (parent && !node->parents.contains(parent)) { node->parents << parent; } node->accumulatedCost += item->cost(); if (node->accumulatedCost > maxCost) { maxCost = node->accumulatedCost; } foreach(TreeLeafItem* child, item->children()) { GraphNode* childNode = buildGraph(child, knownNodes, maxCost, node); if (!childNode) { // was below-threshold item continue; } QMultiHash< GraphNode*, quint64 >::iterator it = node->children.find(childNode); if (it != node->children.end()) { it.value() += child->cost(); } else { node->children.insert(childNode, child->cost()); } } return node; } void DotGraphGenerator::run() { if (!m_file.isOpen()) { 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 += " | " + prettyLabel(node->item->label()); wasGrouped = true; } QString shape; if (wasGrouped) { label = "{" + label + "}"; // <...> would be an id, escape it label = label.replace('<', "\\<"); label = label.replace('>', "\\>"); shape = "record"; } else { shape = "box"; } const QString color = isRoot ? "white" : getColor(node->accumulatedCost, m_maxCost); out << '"' << nodeId << "\" [shape=" << shape << ",label=\"" << label << "\",fillcolor=\"" << color << "\"];\n"; if (!node) { return; } QMultiHash< GraphNode*, quint64 >::const_iterator it = node->children.constBegin(); while(it != node->children.constEnd()) { if (m_canceled) { return; } nodeToDot(it.key(), out, nodeId, it.value()); ++it; } // handle below-threshold if (node->belowThresholdCount) { // node const QString btLabel = i18np("in one place below threshold", "in %1 places, all below threshold", node->belowThresholdCount); out << '"' << nodeId << "-bt\" [shape=box,label=\"" << btLabel << "\",fillcolor=\"" << getColor(node->belowThresholdCost, m_maxCost) << "\"];\n"; // edge out << '"' << nodeId << "-bt\" -> \"" << nodeId << "\" [label =\"" << prettyCost(node->belowThresholdCost) << "\"];\n"; } } QString DotGraphGenerator::outputFile() const { return m_file.fileName(); } QString DotGraphGenerator::mostCostIntensiveGraphvizId() const { return m_costlyGraphvizId; } diff --git a/visualizer/dotgraphgenerator.h b/visualizer/dotgraphgenerator.h index 999a3e2..89bdf22 100644 --- a/visualizer/dotgraphgenerator.h +++ b/visualizer/dotgraphgenerator.h @@ -1,83 +1,83 @@ /* 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 +#include #include "visualizer_export.h" namespace Massif { class SnapshotItem; class TreeLeafItem; class GraphNode; class VISUALIZER_EXPORT 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); /** * 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(); /** * Stops generating the Dot graph file and deletes the temp file. */ void cancel(); virtual void run(); /** * @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; - KTemporaryFile m_file; + QTemporaryFile m_file; bool m_canceled; quint64 m_maxCost; QString m_timeUnit; QString m_costlyGraphvizId; quint64 m_highestCost; }; } #endif // MASSIF_DOTGRAPHGENERATOR_H