diff --git a/kcachegrind/toplevel.cpp b/kcachegrind/toplevel.cpp
index 74fe1b7..fc48c39 100644
--- a/kcachegrind/toplevel.cpp
+++ b/kcachegrind/toplevel.cpp
@@ -1,2392 +1,2392 @@
/* This file is part of KCachegrind.
Copyright (C) 2002-2016 Josef Weidendorfer
KCachegrind 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, version 2.
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
/*
* KCachegrind top level window
*/
#define TRACE_UPDATES 0
#define ENABLE_DUMPDOCK 0
#include "toplevel.h"
#include // for system()
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if ENABLE_DUMPDOCK
#include "dumpselection.h"
#endif
#include "partselection.h"
#include "functionselection.h"
#include "stackselection.h"
#include "stackbrowser.h"
#include "tracedata.h"
#include "globalguiconfig.h"
#include "config.h"
#include "configdlg.h"
#include "multiview.h"
#include "callgraphview.h"
TopLevel::TopLevel()
: KXmlGuiWindow(0)
{
QDBusConnection::sessionBus().registerObject(QStringLiteral("/KCachegrind"), this, QDBusConnection::ExportScriptableSlots);
_progressBar = 0;
_statusbar = statusBar();
_statusLabel = new QLabel(_statusbar);
_statusbar->addWidget(_statusLabel, 1);
_ccProcess = 0;
_layoutCount = 1;
_layoutCurrent = 0;
resetState();
KConfig *kconfig = KSharedConfig::openConfig().data();
GlobalGUIConfig::config()->readOptions();
createDocks();
_multiView = new MultiView(this, this );
_multiView->setObjectName(QStringLiteral("MultiView"));
setCentralWidget(_multiView);
createActions();
_partDockShown->setChecked(!_partDock->isHidden());
_stackDockShown->setChecked(!_stackDock->isHidden());
_functionDockShown->setChecked(!_functionDock->isHidden());
connect(_partDock, &QDockWidget::visibilityChanged,
this, &TopLevel::partVisibilityChanged);
connect(_stackDock, &QDockWidget::visibilityChanged,
this, &TopLevel::stackVisibilityChanged);
connect(_functionDock, &QDockWidget::visibilityChanged,
this, &TopLevel::functionVisibilityChanged);
#if ENABLE_DUMPDOCK
_dumpDockShown->setChecked(!_dumpDock->isHidden());
connect(_dumpDock, SIGNAL(visibilityChanged(bool)),
this, SLOT(dumpVisibilityChanged(bool)));
#endif
// set toggle after reading configuration
_showPercentage = GlobalConfig::showPercentage();
_showExpanded = GlobalConfig::showExpanded();
_showCycles = GlobalConfig::showCycles();
_hideTemplates = GlobalConfig::hideTemplates();
_taPercentage->setChecked(_showPercentage);
_taExpanded->setChecked(_showExpanded);
_taCycles->setChecked(_showCycles);
_taHideTemplates->setChecked(_hideTemplates);
setupPartSelection(_partSelection);
// KCachegrind for KDE 3.0.x does not allow to hide toolbars...
setStandardToolBarMenuEnabled(true);
_openRecent->loadEntries( KConfigGroup( kconfig, "" ) );
// QT dock windows are created before (using QT position restoring)
createGUI();
setAutoSaveSettings();
// restore current state settings (not configuration options)
restoreCurrentState(QString::null); //krazy:exclude=nullstrassign for old broken gcc
// if this is the first toplevel, show tip of day
if (memberList().count() == 1)
QTimer::singleShot( 200, this, &TopLevel::slotShowTipOnStart );
}
void TopLevel::resetState()
{
_activeParts.clear();
_hiddenParts.clear();
_data = 0;
_function = 0;
_eventType = 0;
_eventType2 = 0;
_groupType = ProfileContext::InvalidType;
_group = 0;
// for delayed slots
_traceItemDelayed = 0;
_eventTypeDelayed = 0;
_eventType2Delayed = 0;
_groupTypeDelayed = ProfileContext::InvalidType;
_groupDelayed = 0;
_directionDelayed = TraceItemView::None;
_lastSender = 0;
}
/**
* Setup the part selection widget.
* Statusbar has to be created before.
*/
void TopLevel::setupPartSelection(PartSelection* ps)
{
// setup connections from the part selection widget
connect(ps, &PartSelection::partsHideSelected,
this, &TopLevel::partsHideSelectedSlotDelayed);
connect(ps, &PartSelection::partsUnhideAll,
this, &TopLevel::partsUnhideAllSlotDelayed);
}
/**
* This saves the current state of the main window and
* sub widgets.
*
* No positions are saved. These is done automatically for
* KToolbar, and manually in queryClose() for Qt docks.
*/
void TopLevel::saveCurrentState(const QString& postfix)
{
QString eventType = _eventType ? _eventType->name() : QStringLiteral("?");
QString eventType2 = _eventType2 ? _eventType2->name() : QStringLiteral("?");
ConfigGroup* stateConfig = ConfigStorage::group(QStringLiteral("CurrentState") + postfix);
stateConfig->setValue(QStringLiteral("EventType"), eventType);
stateConfig->setValue(QStringLiteral("EventType2"), eventType2);
stateConfig->setValue(QStringLiteral("GroupType"), ProfileContext::typeName(_groupType));
delete stateConfig;
_partSelection->saveOptions(QStringLiteral("PartOverview"), postfix);
_multiView->saveLayout(QStringLiteral("MainView"), postfix);
_multiView->saveOptions(QStringLiteral("MainView"), postfix);
}
/**
* This function is called when a trace is closed.
* Save browsing position for later restoring
*/
void TopLevel::saveTraceSettings()
{
QString key = traceKey();
ConfigGroup* lConfig = ConfigStorage::group(QStringLiteral("Layouts"));
lConfig->setValue(QStringLiteral("Count%1").arg(key), _layoutCount);
lConfig->setValue(QStringLiteral("Current%1").arg(key), _layoutCurrent);
delete lConfig;
ConfigGroup* pConfig = ConfigStorage::group(QStringLiteral("TracePositions"));
if (_eventType)
pConfig->setValue(QStringLiteral("EventType%1").arg(key), _eventType->name());
if (_eventType2)
pConfig->setValue(QStringLiteral("EventType2%1").arg(key), _eventType2->name());
if (_groupType != ProfileContext::InvalidType)
pConfig->setValue(QStringLiteral("GroupType%1").arg(key),
ProfileContext::typeName(_groupType));
if (_data) {
if (_group)
pConfig->setValue(QStringLiteral("Group%1").arg(key), _group->name());
saveCurrentState(key);
}
delete pConfig;
}
/**
* This restores the current state of the main window and
* sub widgets.
*
* This does NOT restore any positions. This is done automatically for
* KToolbar, and manually in the createDocks() for QT docks..
*/
void TopLevel::restoreCurrentState(const QString& postfix)
{
_partSelection->restoreOptions(QStringLiteral("PartOverview"), postfix);
_multiView->restoreLayout(QStringLiteral("MainView"), postfix);
_multiView->restoreOptions(QStringLiteral("MainView"), postfix);
_taSplit->setChecked(_multiView->childCount()>1);
_taSplitDir->setEnabled(_multiView->childCount()>1);
_taSplitDir->setChecked(_multiView->orientation() == Qt::Horizontal);
}
void TopLevel::createDocks()
{
_partDock = new QDockWidget(this);
_partDock->setObjectName(QStringLiteral("part dock"));
_partDock->setWindowTitle(i18n("Parts Overview"));
_partSelection = new PartSelection(this, _partDock);
_partDock->setWidget(_partSelection);
_stackDock = new QDockWidget(this);
_stackDock->setObjectName(QStringLiteral("stack dock"));
// Why is the caption only correct with a close button?
_stackSelection = new StackSelection(_stackDock);
_stackDock->setWidget(_stackSelection);
_stackDock->setWindowTitle(i18n("Top Cost Call Stack"));
_stackSelection->setWhatsThis( i18n(
"The Top Cost Call Stack"
"This is a purely fictional 'most probable' call stack. "
"It is built up by starting with the current selected "
"function and adds the callers/callees with highest cost "
"at the top and to bottom.
"
"The Cost and Calls columns show the "
"cost used for all calls from the function in the line "
"above.
"));
connect(_stackSelection, SIGNAL(functionSelected(CostItem*)),
this, SLOT(setTraceItemDelayed(CostItem*)));
_functionDock = new QDockWidget(this);
_functionDock->setObjectName(QStringLiteral("function dock"));
_functionDock->setWindowTitle(i18n("Flat Profile"));
_functionSelection = new FunctionSelection(this, _functionDock);
_functionDock->setWidget(_functionSelection);
#if ENABLE_DUMPDOCK
_dumpDock = new QDockWidget(this);
_dumpDock->setWindowTitle(i18n("Profile Dumps"));
_dumpSelection = new DumpSelection(this, _dumpDock,
"dumpSelection");
_dumpSelection->setTopLevel(this);
_dumpDock->setWidget(_dumpSelection);
_dumpSelection->setWhatsThis( i18n(
"Profile Dumps"
"This dockable shows in the top part the list of "
"loadable profile dumps in all subdirectories of: "
"
- current working directory of KCachegrind, "
"i.e. where it was started from, and
"
"- the default profile dump directory given in the "
"configuration.
"
"The list is sorted according to the target command "
"profiled in the corresponding dump.
"
"On selecting a profile dump, information for it "
"is shown in the bottom area of the dockable: "
"
- Options allows you to view the profiled "
"command and profile options of this dump. By changing "
"any item, a new (yet unexisting) profile template "
"is created. Press Run Profile to start a "
"profile run with these options in the background.
"
"- Info gives detailed info on the selected "
"dump like event cost summary and properties of the "
"simulated cache.
"
"- State is only available for current happening "
"profiles runs. Press Update to see different "
"counters of the run, and a stack trace of the current "
"position in the program profiled. Check the Every "
"option to let KCachegrind regularly poll these data. "
"Check the Sync option to let the dockable activate "
"the top function in the current loaded dump.
"));
#endif
// default positions, will be adjusted automatically by stored state in config
addDockWidget(Qt::LeftDockWidgetArea, _partDock );
addDockWidget(Qt::LeftDockWidgetArea, _stackDock );
addDockWidget(Qt::LeftDockWidgetArea, _functionDock );
_stackDock->hide();
_partDock->hide();
#if ENABLE_DUMPDOCK
addDockWidget( Qt::LeftDockWidgetArea, _dumpDock );
_dumpDock->hide();
#endif
KConfigGroup dockConfig(KSharedConfig::openConfig(), "Docks");
_forcePartDock = dockConfig.readEntry("ForcePartDockVisible", false);
}
TopLevel::~TopLevel()
{
delete _data;
}
void TopLevel::saveProperties(KConfigGroup & c)
{
if ( _data )
c.writeEntry("TraceName", _data->traceName());
}
void TopLevel::readProperties(const KConfigGroup &c)
{
QString traceName = c.readEntry("TraceName");
if (!traceName.isEmpty()) {
openDataFile(traceName);
}
}
void TopLevel::createLayoutActions()
{
QString hint;
QAction* action;
action = actionCollection()->addAction( QStringLiteral("layout_duplicate") );
action->setText( i18n( "&Duplicate" ) );
connect(action, &QAction::triggered, this, &TopLevel::layoutDuplicate);
actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Plus));
hint = i18n("Duplicate Current Layout"
"Make a copy of the current layout.
");
action->setWhatsThis( hint );
action = actionCollection()->addAction( QStringLiteral("layout_remove") );
action->setText( i18n( "&Remove" ) );
connect(action, &QAction::triggered, this, &TopLevel::layoutRemove);
hint = i18n("Remove Current Layout"
"Delete current layout and make the previous active.
");
action->setWhatsThis( hint );
action = actionCollection()->addAction( QStringLiteral("layout_next") );
action->setText( i18n( "&Go to Next" ) );
connect(action, &QAction::triggered, this, &TopLevel::layoutNext);
actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Right));
hint = i18n("Go to Next Layout");
action->setWhatsThis( hint );
action = actionCollection()->addAction( QStringLiteral("layout_previous") );
action->setText( i18n( "&Go to Previous" ) );
connect(action, &QAction::triggered, this, &TopLevel::layoutPrevious);
actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Left));
hint = i18n("Go to Previous Layout");
action->setWhatsThis( hint );
action = actionCollection()->addAction( QStringLiteral("layout_restore") );
action->setText( i18n( "&Restore to Default" ) );
connect(action, &QAction::triggered, this, &TopLevel::layoutRestore);
hint = i18n("Restore Layouts to Default");
action->setWhatsThis( hint );
action = actionCollection()->addAction( QStringLiteral("layout_save") );
action->setText( i18n( "&Save as Default" ) );
connect(action, &QAction::triggered, this, &TopLevel::layoutSave);
hint = i18n("Save Layouts as Default");
action->setWhatsThis( hint );
}
// TODO: split this up...
void TopLevel::createMiscActions()
{
QString hint;
QAction* action;
action = KStandardAction::openNew(this, SLOT(newWindow()), actionCollection());
hint = i18n("NewOpen new empty KCachegrind window.
");
action->setWhatsThis( hint );
action = actionCollection()->addAction( QStringLiteral("file_add") );
action->setText( i18n( "&Add..." ) );
connect(action, SIGNAL(triggered(bool) ), SLOT(add()));
hint = i18n("Add Profile Data"
"This opens an additional profile data file in the current window.
");
action->setWhatsThis( hint );
action = actionCollection()->addAction( QStringLiteral("reload") );
action->setIcon( QIcon::fromTheme(QStringLiteral("view-refresh")) );
action->setText( i18nc("Reload a document", "&Reload" ) );
connect(action, &QAction::triggered, this, &TopLevel::reload);
actionCollection()->setDefaultShortcut(action, KStandardShortcut::Reload);
hint = i18n("Reload Profile Data"
"This loads any new created parts, too.
");
action->setWhatsThis( hint );
action = actionCollection()->addAction( QStringLiteral("export") );
action->setText( i18n( "&Export Graph" ) );
connect(action, &QAction::triggered, this, &TopLevel::exportGraph);
hint = i18n("Export Call Graph"
"Generates a file with extension .dot for the tools "
"of the GraphViz package.
");
action->setWhatsThis( hint );
_taDump = actionCollection()->add( QStringLiteral("dump") );
_taDump->setIcon( QIcon::fromTheme(QStringLiteral("edit-redo")) );
_taDump->setText( i18n( "&Force Dump" ) );
connect(_taDump, &QAction::triggered, this, &TopLevel::forceTrace);
actionCollection()->setDefaultShortcut(_taDump, QKeySequence::Undo);
hint = i18n("Force Dump"
"This forces a dump for a Callgrind profile run "
"in the current directory. This action is checked while "
"KCachegrind looks for the dump. If the dump is "
"finished, it automatically reloads the current trace. "
"If this is the one from the running Callgrind, the new "
"created trace part will be loaded, too.
"
"Force dump creates a file 'callgrind.cmd', and "
"checks every second for its existence. A running "
"Callgrind will detect this file, dump a trace part, "
"and delete 'callgrind.cmd'. "
"The deletion is detected by KCachegrind, "
"and it does a Reload. If there is no Callgrind "
"running, press 'Force Dump' again to cancel the dump "
"request. This deletes 'callgrind.cmd' itself and "
"stops polling for a new dump.
"
"Note: A Callgrind run only detects "
"existence of 'callgrind.cmd' when actively running "
"a few milliseconds, i.e. "
"not sleeping. Tip: For a profiled GUI program, "
"you can awake Callgrind e.g. by resizing a window "
"of the program.
");
_taDump->setWhatsThis( hint );
action = KStandardAction::open(this, SLOT(load()), actionCollection());
hint = i18n("Open Profile Data"
"This opens a profile data file, with possible multiple parts
");
action->setToolTip( hint );
action->setWhatsThis( hint );
_openRecent = KStandardAction::openRecent(this, SLOT(load(const QUrl&)),
actionCollection());
KStandardAction::showStatusbar(this,
SLOT(toggleStatusBar()), actionCollection());
_partDockShown = actionCollection()->add(QStringLiteral("settings_show_partdock"));
_partDockShown->setText(i18n("Parts Overview"));
connect(_partDockShown, &QAction::triggered, this, &TopLevel::togglePartDock);
hint = i18n("Show/Hide the Parts Overview Dockable");
_partDockShown->setToolTip( hint );
_partDockShown->setWhatsThis( hint );
_stackDockShown = actionCollection()->add(QStringLiteral("settings_show_stackdock"));
_stackDockShown->setText(i18n("Call Stack"));
connect(_stackDockShown, &QAction::triggered, this, &TopLevel::toggleStackDock);
hint = i18n("Show/Hide the Call Stack Dockable");
_stackDockShown->setToolTip( hint );
_stackDockShown->setWhatsThis( hint );
_functionDockShown = actionCollection()->add(QStringLiteral("settings_show_profiledock"));
_functionDockShown->setText(i18n("Function Profile"));
connect(_functionDockShown, &QAction::triggered, this, &TopLevel::toggleFunctionDock);
hint = i18n("Show/Hide the Function Profile Dockable");
_functionDockShown->setToolTip( hint );
_functionDockShown->setWhatsThis( hint );
#if ENABLE_DUMPDOCK
_dumpDockShown = actionCollection()->add("settings_show_dumpdock",
this, SLOT(toggleDumpDock()));
_dumpDockShown->setText(i18n("Profile Dumps"));
hint = i18n("Show/Hide the Profile Dumps Dockable");
_dumpDockShown->setToolTip( hint );
_dumpDockShown->setWhatsThis( hint );
#endif
_taPercentage = actionCollection()->add(QStringLiteral("view_percentage"));
_taPercentage->setIcon(QIcon::fromTheme(QStringLiteral("percent")));
_taPercentage->setText(i18n("Relative"));
connect(_taPercentage, &QAction::triggered, this, &TopLevel::togglePercentage);
hint = i18n("Show relative instead of absolute costs");
_taPercentage->setToolTip( hint );
_taPercentage->setWhatsThis( hint );
_taExpanded = actionCollection()->add(QStringLiteral("view_expanded"));
_taExpanded->setIcon(QIcon::fromTheme(QStringLiteral("move")));
_taExpanded->setText(i18n("Relative to Parent"));
connect(_taExpanded, &QAction::triggered, this, &TopLevel::toggleExpanded);
hint = i18n("Show percentage costs relative to parent");
_taExpanded->setToolTip( hint );
_taExpanded->setWhatsThis( hint );
hint = i18n("Show percentage costs relative to parent"
"If this is switched off, percentage costs are always shown "
"relative to the total cost of the profile part(s) that are "
"currently browsed. By turning on this option, percentage cost "
"of shown cost items will be relative to the parent cost item.
"
""
"Cost Type | Parent Cost |
"
"Function Cumulative | Total |
"
"Function Self | Function Group (*) / Total |
"
"Call | Function Inclusive |
"
"Source Line | Function Inclusive |
"
"
"
"(*) Only if function grouping is switched on (e.g. ELF object grouping).
");
_taExpanded->setWhatsThis( hint );
_taCycles = actionCollection()->add(QStringLiteral("view_cycles"));
_taCycles->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo")));
_taCycles->setText(i18n( "Cycle Detection" ));
connect(_taCycles, &QAction::triggered, this, &TopLevel::toggleCycles);
hint = i18n("Detect recursive cycles"
"If this is switched off, the treemap drawing will show "
"black areas when a recursive call is made instead of drawing the "
"recursion ad infinitum. Note that "
"the size of black areas often will be wrong, as inside recursive "
"cycles the cost of calls cannot be determined; the error is small, "
"however, for false cycles (see documentation).
"
"The correct handling for cycles is to detect them and collapse all "
"functions of a cycle into an artificial function, which is done when this "
"option is selected. Unfortunately, with GUI applications, this often will "
"lead to huge false cycles, making the analysis impossible; therefore, there "
"is the option to switch this off.
");
_taCycles->setWhatsThis( hint );
_taHideTemplates = actionCollection()->add(QStringLiteral("hide_templates"));
_taHideTemplates->setIcon(QIcon::fromTheme(QStringLiteral("hidetemplates")));
_taHideTemplates->setText(i18n( "Shorten Templates" ));
connect(_taHideTemplates, &QAction::triggered, this, &TopLevel::toggleHideTemplates);
_taHideTemplates->setToolTip(i18n( "Hide Template Parameters in C++ Symbols" ));
hint = i18n("Hide Template Parameters in C++ Symbols"
"If this is switched on, every symbol displayed will have "
"any C++ template parameters hidden, just showing <> "
"instead of a potentially nested template parameter.
"
"In this mode, you can hover the mouse pointer over the "
"activated symbol label to show a tooltip with the "
"unabbreviated symbol.
");
_taHideTemplates->setWhatsThis(hint);
KStandardAction::quit(this, SLOT(close()), actionCollection());
KStandardAction::preferences(this, SLOT(configure()), actionCollection());
KStandardAction::keyBindings(this, SLOT(configureKeys()), actionCollection());
KStandardAction::configureToolbars(this,SLOT(configureToolbars()),
actionCollection());
_paUp = new KToolBarPopupAction( QIcon::fromTheme( QStringLiteral("go-up") ),
i18n( "&Up" ), this );
connect( _paUp, &QAction::triggered,
_stackSelection, &StackSelection::browserUp );
actionCollection()->addAction( QStringLiteral("go_up"), _paUp );
actionCollection()->setDefaultShortcut(_paUp, QKeySequence(Qt::ALT + Qt::Key_Up));
connect( _paUp->menu(), &QMenu::aboutToShow,
this, &TopLevel::upAboutToShow );
connect( _paUp->menu(), &QMenu::triggered,
this, &TopLevel::upTriggered );
hint = i18n("Go Up"
"Go to last selected caller of current function. "
"If no caller was visited, use that with highest cost.
");
_paUp->setToolTip( hint );
_paUp->setWhatsThis( hint );
QPair< KGuiItem, KGuiItem > backForward = KStandardGuiItem::backAndForward();
_paBack = new KToolBarPopupAction( backForward.first.icon(),
backForward.first.text(), this );
connect( _paBack, &QAction::triggered,
_stackSelection, &StackSelection::browserBack );
actionCollection()->addAction( QStringLiteral("go_back"), _paBack );
actionCollection()->setDefaultShortcut(_paBack, QKeySequence(Qt::ALT + Qt::Key_Left));
connect( _paBack->menu(), &QMenu::aboutToShow,
this, &TopLevel::backAboutToShow );
connect( _paBack->menu(), &QMenu::triggered,
this, &TopLevel::backTriggered );
hint = i18n("Go back in function selection history");
_paBack->setToolTip( hint );
_paBack->setWhatsThis( hint );
_paForward = new KToolBarPopupAction( backForward.second.icon(),
backForward.second.text(), this );
connect( _paForward, &QAction::triggered,
_stackSelection, &StackSelection::browserForward );
actionCollection()->addAction( QStringLiteral("go_forward"), _paForward );
actionCollection()->setDefaultShortcut(_paForward, QKeySequence(Qt::ALT + Qt::Key_Right));
connect( _paForward->menu(), &QMenu::aboutToShow,
this, &TopLevel::forwardAboutToShow );
connect( _paForward->menu(), &QMenu::triggered,
this, &TopLevel::forwardTriggered );
hint = i18n("Go forward in function selection history");
_paForward->setToolTip( hint );
_paForward->setWhatsThis( hint );
_saCost = actionCollection()->add(QStringLiteral("view_cost_type"));
_saCost->setText(i18n("Primary Event Type"));
hint = i18n("Select primary event type of costs");
_saCost->setComboWidth(300);
_saCost->setToolTip( hint );
_saCost->setWhatsThis( hint );
// This is needed because for KDE4, "_saCost->setComboWidth(300);" seems to
// have no effect. Instead, list box entry widths are used to determine the
// combobox width. However, at KCachegrind startup, we do not have yet
// a list of event types, as this depends on the profile data.
// In KDE 4.2, we used a translatable string, which did not really work as
// the semantic is not known to translators. Instead, we use a
// nontranslatable string now...
QStringList dummyItems;
dummyItems << QStringLiteral("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
_saCost->setItems(dummyItems);
// cost types are dependent on loaded data, thus KSelectAction
// is filled in setData()
connect( _saCost, SIGNAL(triggered(const QString&)),
this, SLOT(eventTypeSelected(const QString&)));
_saCost2 = actionCollection()->add(QStringLiteral("view_cost_type2"));
_saCost2->setText(i18n("Secondary Event Type"));
hint = i18n("Select secondary event type for cost e.g. shown in annotations");
_saCost2->setComboWidth(300);
_saCost2->setToolTip( hint );
_saCost2->setWhatsThis( hint );
_saCost2->setItems(dummyItems);
connect( _saCost2, SIGNAL(triggered(const QString&)),
this, SLOT(eventType2Selected(const QString&)));
saGroup = actionCollection()->add(QStringLiteral("view_group_type"));
saGroup->setText(i18n("Grouping"));
hint = i18n("Select how functions are grouped into higher level cost items");
saGroup->setToolTip( hint );
saGroup->setWhatsThis( hint );
QStringList args;
args << i18n("(No Grouping)")
<< ProfileContext::i18nTypeName(ProfileContext::Object)
<< ProfileContext::i18nTypeName(ProfileContext::File)
<< ProfileContext::i18nTypeName(ProfileContext::Class)
<< ProfileContext::i18nTypeName(ProfileContext::FunctionCycle);
saGroup->setItems(args);
connect( saGroup, SIGNAL(triggered(int)),
this, SLOT(groupTypeSelected(int)));
_taSplit = actionCollection()->add(QStringLiteral("view_split"));
_taSplit->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right")));
_taSplit->setText(i18n("Split"));
connect(_taSplit, &QAction::triggered, this, &TopLevel::splitSlot);
hint = i18n("Show two information panels");
_taSplit->setToolTip( hint );
_taSplit->setWhatsThis( hint );
_taSplitDir = actionCollection()->add(QStringLiteral("view_split_dir"));
_taSplitDir->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right")));
_taSplitDir->setText(i18n("Split Horizontal"));
connect(_taSplitDir, &QAction::triggered, this, &TopLevel::splitDirSlot);
hint = i18n("Change Split Orientation when main window is split.");
_taSplitDir->setToolTip( hint );
_taSplitDir->setWhatsThis( hint );
// copied from KMail...
KStandardAction::tipOfDay( this, SLOT( slotShowTip() ), actionCollection() );
}
void TopLevel::createActions()
{
createMiscActions();
createLayoutActions();
}
void TopLevel::toggleStatusBar()
{
if (statusBar()->isVisible())
statusBar()->hide();
else
statusBar()->show();
}
void TopLevel::togglePartDock()
{
if (!_partDock->isVisible())
_partDock->show();
else
_partDock->hide();
}
void TopLevel::toggleStackDock()
{
if (!_stackDock->isVisible())
_stackDock->show();
else
_stackDock->hide();
}
void TopLevel::toggleDumpDock()
{
#if ENABLE_DUMPDOCK
if (!_dumpDock->isVisible())
_dumpDock->show();
else
_dumpDock->hide();
#endif
}
void TopLevel::toggleFunctionDock()
{
if (!_functionDock->isVisible())
_functionDock->show();
else
_functionDock->hide();
}
void TopLevel::togglePercentage()
{
setPercentage(_taPercentage->isChecked());
}
void TopLevel::setAbsoluteCost()
{
setPercentage(false);
}
void TopLevel::setRelativeCost()
{
setPercentage(true);
}
void TopLevel::updateViewsOnChange(int change)
{
_partSelection->notifyChange(change);
_functionSelection->notifyChange(change);
_multiView->notifyChange(change);
}
void TopLevel::setPercentage(bool show)
{
if (_showPercentage == show) return;
_showPercentage = show;
if (_taPercentage->isChecked() != show)
_taPercentage->setChecked(show);
GlobalConfig::setShowPercentage(_showPercentage);
_stackSelection->refresh();
updateViewsOnChange(TraceItemView::configChanged);
}
void TopLevel::toggleExpanded()
{
bool show = _taExpanded->isChecked();
if (_showExpanded == show) return;
_showExpanded = show;
GlobalConfig::setShowExpanded(_showExpanded);
_stackSelection->refresh();
updateViewsOnChange(TraceItemView::configChanged);
}
void TopLevel::toggleCycles()
{
bool show = _taCycles->isChecked();
if (_showCycles == show) return;
_showCycles = show;
GlobalConfig::setShowCycles(_showCycles);
if (!_data) return;
_data->invalidateDynamicCost();
_data->updateFunctionCycles();
_stackSelection->rebuildStackList();
updateViewsOnChange(TraceItemView::configChanged);
}
void TopLevel::toggleHideTemplates()
{
bool b = _taHideTemplates->isChecked();
if (_hideTemplates == b) return;
_hideTemplates = b;
GlobalConfig::setHideTemplates(_hideTemplates);
_stackSelection->refresh();
updateViewsOnChange(TraceItemView::configChanged);
}
void TopLevel::partVisibilityChanged(bool v)
{
_partDockShown->setChecked(v);
}
void TopLevel::stackVisibilityChanged(bool v)
{
_stackDockShown->setChecked(v);
}
#if ENABLE_DUMPDOCK
void TopLevel::dumpVisibilityChanged(bool v)
#else
void TopLevel::dumpVisibilityChanged(bool)
#endif
{
#if ENABLE_DUMPDOCK
_dumpDockShown->setChecked(v);
#endif
}
void TopLevel::functionVisibilityChanged(bool v)
{
_functionDockShown->setChecked(v);
if (v)
_functionSelection->updateView();
}
void TopLevel::querySlot()
{
_functionSelection->query(queryLineEdit->text());
}
void TopLevel::configureKeys()
{
KShortcutsDialog::configure(actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, this);
}
void TopLevel::configureToolbars()
{
KEditToolBar *dlg = new KEditToolBar(guiFactory(), this);
if (dlg->exec())
createGUI();
delete dlg;
}
void TopLevel::newWindow()
{
TopLevel* t = new TopLevel();
t->show();
}
void TopLevel::load()
{
QUrl url = QFileDialog::getOpenFileUrl(this,
i18n("Select Callgrind Profile Data"),
QUrl(),
i18n("Callgrind Profile Data (cachegrind.out* callgrind.out*);;All Files (*)"));
load(url);
}
void TopLevel::load(const QUrl& url)
{
if (url.isEmpty()) return;
QString tmpFileName;
QTemporaryFile tmpFile;
if (url.isLocalFile()) {
tmpFileName = url.toLocalFile();
}
else if (tmpFile.open()){
// network transparency
tmpFileName = tmpFile.fileName();
KIO::FileCopyJob *job = KIO::file_copy(url,
QUrl::fromLocalFile(tmpFileName));
KJobWidgets::setWindow(job, this);
job->exec();
}
if (!tmpFileName.isEmpty()) {
_openRecent->addUrl(url);
_openRecent->saveEntries( KConfigGroup( KSharedConfig::openConfig(), QString() ) );
load(tmpFileName);
} else {
KMessageBox::error(this, i18n("Could not open the file \"%1\". "
"Check it exists and you have enough "
"permissions to read it.",
url.toDisplayString()));
}
}
/* if file name is ".": load first file found in current directory, but do
* not show an error message if nothing could be loaded
*/
void TopLevel::load(QString file)
{
if (file.isEmpty()) return;
bool showError = true;
if (file == QStringLiteral("."))
showError = false;
if (_data && _data->parts().count()>0) {
// In new window
TopLevel* t = new TopLevel();
t->show();
t->loadDelayed(file);
return;
}
bool loaded = openDataFile(file);
if (!loaded && showError)
KMessageBox::error(this, i18n("Could not open the file \"%1\". "
"Check it exists and you have enough "
"permissions to read it.", file));
}
void TopLevel::add()
{
QUrl url = QFileDialog::getOpenFileUrl(this,
i18n("Add Callgrind Profile Data"),
QUrl(),
i18n("Callgrind Profile Data (cachegrind.out* callgrind.out*);;All Files (*)"));
add(url);
}
void TopLevel::add(const QUrl &url)
{
if (url.isEmpty()) return;
QString tmpFileName;
QTemporaryFile tmpFile;
if (url.isLocalFile()) {
tmpFileName = url.toLocalFile();
}
else if (tmpFile.open()){
// network transparency
tmpFileName = tmpFile.fileName();
KIO::FileCopyJob *job = KIO::file_copy(url,
QUrl::fromLocalFile(tmpFileName));
KJobWidgets::setWindow(job, this);
job->exec();
}
if (!tmpFileName.isEmpty()) {
_openRecent->addUrl(url);
_openRecent->saveEntries( KSharedConfig::openConfig()->group( QString() ) );
add(tmpFileName);
}
}
void TopLevel::add(QString file)
{
if (file.isEmpty()) return;
if (_data) {
_data->load(file);
// GUI update for added data
configChanged();
return;
}
openDataFile(file);
}
void TopLevel::loadDelayed(QString file)
{
_loadFilesDelayed << file;
QTimer::singleShot(0, this, &TopLevel::loadTraceDelayed);
}
void TopLevel::loadDelayed(QStringList files)
{
_loadFilesDelayed << files;
QTimer::singleShot(0, this, &TopLevel::loadTraceDelayed);
}
void TopLevel::loadTraceDelayed()
{
if (_loadFilesDelayed.isEmpty()) return;
if (_loadFilesDelayed.count()>1) {
// FIXME: we expect all files to be local and existing
TraceData* d = new TraceData(this);
d->load(_loadFilesDelayed);
setData(d);
}
else {
QString file = _loadFilesDelayed[0];
// if URL scheme is missing (URL is relative), this is a local file
QUrl u = QUrl::fromUserInput(file, QString(), QUrl::AssumeLocalFile);
if (u.isLocalFile())
load(file); // special case for local file: maybe just prefix
else
load(u);
}
_loadFilesDelayed.clear();
}
void TopLevel::reload()
{
QString trace;
if (!_data || _data->parts().count()==0)
trace = QStringLiteral("."); // open first trace found in dir
else
trace = _data->traceName();
// this also keeps sure we have the same browsing position...
openDataFile(trace);
}
void TopLevel::exportGraph()
{
if (!_data || !_function) return;
QString n = QStringLiteral("callgraph.dot");
GraphExporter ge(_data, _function, _eventType, _groupType, n);
ge.writeDot();
#ifdef Q_OS_UNIX
// shell commands only work in UNIX
QString cmd = QStringLiteral("(dot %1 -Tps > %2.ps; xdg-open %3.ps)&")
.arg(n).arg(n).arg(n);
if (::system(QFile::encodeName( cmd ))<0)
qDebug() << "TopLevel::exportGraph: can not run " << cmd;
#endif
}
bool TopLevel::setEventType(QString s)
{
EventType* ct;
ct = (_data) ? _data->eventTypes()->type(s) : 0;
// if costtype with given name not found, use first available
if (!ct && _data) ct = _data->eventTypes()->type(0);
return setEventType(ct);
}
bool TopLevel::setEventType2(QString s)
{
EventType* ct;
// Special type i18n("(Hidden)") gives 0
ct = (_data) ? _data->eventTypes()->type(s) : 0;
return setEventType2(ct);
}
void TopLevel::eventTypeSelected(const QString& s)
{
EventType* ct;
ct = (_data) ? _data->eventTypes()->typeForLong(s) : 0;
setEventType(ct);
}
void TopLevel::eventType2Selected(const QString& s)
{
EventType* ct;
ct = (_data) ? _data->eventTypes()->typeForLong(s) : 0;
setEventType2(ct);
}
bool TopLevel::setEventType(EventType* ct)
{
if (_eventType == ct) return false;
_eventType = ct;
if (ct) {
QStringList l = _saCost->items();
int idx = l.indexOf(ct->longName());
if (idx >= 0)
_saCost->setCurrentItem(idx);
}
_partSelection->setEventType(_eventType);
_stackSelection->setEventType(_eventType);
_functionSelection->setEventType(_eventType);
_multiView->setEventType(_eventType);
updateStatusBar();
return true;
}
bool TopLevel::setEventType2(EventType* ct)
{
if (_eventType2 == ct) return false;
_eventType2 = ct;
QString longName = ct ? ct->longName() : i18n("(Hidden)");
QStringList l = _saCost2->items();
int idx = l.indexOf(longName);
if (idx >= 0)
_saCost2->setCurrentItem(idx);
_partSelection->setEventType2(_eventType2);
_stackSelection->setEventType2(_eventType2);
_functionSelection->setEventType2(_eventType2);
_multiView->setEventType2(_eventType2);
updateStatusBar();
return true;
}
void TopLevel::groupTypeSelected(int cg)
{
switch(cg) {
case 0: setGroupType( ProfileContext::Function ); break;
case 1: setGroupType( ProfileContext::Object ); break;
case 2: setGroupType( ProfileContext::File ); break;
case 3: setGroupType( ProfileContext::Class ); break;
case 4: setGroupType( ProfileContext::FunctionCycle ); break;
default: break;
}
}
bool TopLevel::setGroupType(QString s)
{
ProfileContext::Type gt;
gt = ProfileContext::type(s);
// only allow Function/Object/File/Class as grouptype
switch(gt) {
case ProfileContext::Object:
case ProfileContext::File:
case ProfileContext::Class:
case ProfileContext::FunctionCycle:
break;
default:
gt = ProfileContext::Function;
}
return setGroupType(gt);
}
bool TopLevel::setGroupType(ProfileContext::Type gt)
{
if (_groupType == gt) return false;
_groupType = gt;
int idx = -1;
switch(gt) {
case ProfileContext::Function: idx = 0; break;
case ProfileContext::Object: idx = 1; break;
case ProfileContext::File: idx = 2; break;
case ProfileContext::Class: idx = 3; break;
case ProfileContext::FunctionCycle: idx = 4; break;
default:
break;
}
if (idx==-1) return false;
if (saGroup->currentItem() != idx)
saGroup->setCurrentItem(idx);
_stackSelection->setGroupType(_groupType);
_partSelection->set(_groupType);
_functionSelection->set(_groupType);
_multiView->set(_groupType);
updateStatusBar();
return true;
}
bool TopLevel::setGroup(QString s)
{
TraceCostItem* ci = _functionSelection->group(s);
if (!ci)
return false;
return setGroup(ci);
}
bool TopLevel::setGroup(TraceCostItem* g)
{
if (_group == g) return false;
_group = g;
_functionSelection->setGroup(g);
updateStatusBar();
return true;
}
bool TopLevel::setFunction(QString s)
{
if (!_data) return false;
ProfileCostArray* f = _data->search(ProfileContext::Function, s, _eventType);
if (!f) return false;
return setFunction((TraceFunction*)f);
}
bool TopLevel::setFunction(TraceFunction* f)
{
if (_function == f) return false;
_function = f;
_multiView->activate(f);
_functionSelection->activate(f);
_partSelection->activate(f);
_stackSelection->setFunction(_function);
StackBrowser* b = _stackSelection->browser();
if (b) {
// do not disable up: a press forces stack-up extending...
_paForward->setEnabled(b->canGoForward());
_paBack->setEnabled(b->canGoBack());
}
#if TRACE_UPDATES
qDebug("TopLevel::setFunction(%s), lastSender %s",
f ? f->prettyName().ascii() : "0",
_lastSender ? _lastSender->name() :"0" );
#endif
return true;
}
/**
* Delayed versions.
* We always have a pair of slots: One receiver to start the
* delay with a singleShot Timer. It stores the parameter into a
* temporary variable. And one parameterless slot for
* forwarding, using this temporary.
*/
void TopLevel::setEventTypeDelayed(EventType* ct)
{
_eventTypeDelayed = ct;
QTimer::singleShot (0, this, SLOT(setEventTypeDelayed()));
}
void TopLevel::setEventType2Delayed(EventType* ct)
{
_eventType2Delayed = ct;
QTimer::singleShot (0, this, SLOT(setEventType2Delayed()));
}
void TopLevel::setEventTypeDelayed()
{
setEventType(_eventTypeDelayed);
}
void TopLevel::setEventType2Delayed()
{
setEventType2(_eventType2Delayed);
}
void TopLevel::setGroupTypeDelayed(ProfileContext::Type gt)
{
_groupTypeDelayed = gt;
QTimer::singleShot (0, this, SLOT(setGroupTypeDelayed()));
}
void TopLevel::setGroupTypeDelayed()
{
setGroupType(_groupTypeDelayed);
}
void TopLevel::setGroupDelayed(TraceCostItem* g)
{
#if TRACE_UPDATES
qDebug("TopLevel::setGroupDelayed(%s), sender %s",
g ? g->prettyName().ascii() : "0",
_lastSender ? _lastSender->name() :"0" );
#endif
_groupDelayed = g;
QTimer::singleShot (0, this, SLOT(setGroupDelayed()));
}
void TopLevel::setGroupDelayed()
{
setGroup(_groupDelayed);
}
void TopLevel::setDirectionDelayed(TraceItemView::Direction d)
{
_directionDelayed = d;
QTimer::singleShot (0, this, SLOT(setDirectionDelayed()));
}
void TopLevel::setDirectionDelayed()
{
switch(_directionDelayed) {
case TraceItemView::Back:
_stackSelection->browserBack();
break;
case TraceItemView::Forward:
_stackSelection->browserForward();
break;
case TraceItemView::Up:
{
StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0;
HistoryItem* hi = b ? b->current() : 0;
TraceFunction* f = hi ? hi->function() : 0;
if (!f) break;
f = hi->stack()->caller(f, false);
if (f) setFunction(f);
}
break;
default: break;
}
_directionDelayed = TraceItemView::None;
}
void TopLevel::setTraceItemDelayed(CostItem* i)
{
// no need to select same item a 2nd time...
if (_traceItemDelayed == i) return;
_traceItemDelayed = i;
_lastSender = sender();
qDebug() << "Selected " << (i ? i->prettyName() : QStringLiteral("(none)"));
#if TRACE_UPDATES
qDebug("TopLevel::setTraceItemDelayed(%s), sender %s",
i ? i->prettyName().ascii() : "0",
_lastSender ? _lastSender->name() :"0" );
#endif
QTimer::singleShot (0, this, SLOT(setTraceItemDelayed()));
}
void TopLevel::setTraceItemDelayed()
{
if (!_traceItemDelayed) return;
switch(_traceItemDelayed->type()) {
case ProfileContext::Function:
case ProfileContext::FunctionCycle:
setFunction((TraceFunction*)_traceItemDelayed);
break;
case ProfileContext::Object:
case ProfileContext::File:
case ProfileContext::Class:
_multiView->activate(_traceItemDelayed);
break;
#if 0
// this conflicts with the selection policy of InstrView ?!?
case ProfileContext::Instr:
case ProfileContext::Line:
// only for multiview
_multiView->activate(_traceItemDelayed);
break;
#endif
default: break;
}
_traceItemDelayed = 0;
_lastSender = 0;
}
/**
* A TraceData object cannot be viewed many times in different
* toplevel windows. Thus, this toplevel window takes ownership
* of the TraceData object: on closing the window or opening
* another trace, the object is destroyed.
*/
void TopLevel::setData(TraceData* data)
{
if (data == _data) return;
_lastSender = 0;
saveTraceSettings();
if (_data) {
_partSelection->setData(0);
_stackSelection->setData(0);
_functionSelection->setData(0);
_multiView->setData(0);
_multiView->updateView(true);
// we are the owner...
delete _data;
}
// reset members
resetState();
_data = data;
// fill cost type list
QStringList types;
if (_data) {
/* add all supported virtual types */
EventTypeSet* m = _data->eventTypes();
m->addKnownDerivedTypes();
/* first, fill selection list with available cost types */
for (int i=0;irealCount();i++)
types << m->realType(i)->longName();
for (int i=0;iderivedCount();i++)
types << m->derivedType(i)->longName();
}
_saCost->setItems(types);
_saCost->setComboWidth(300);
if (types.count()>0) {
// second type list gets an additional "(Hidden)"
types.prepend(i18n("(Hidden)"));
}
_saCost2->setItems(types);
_saCost2->setComboWidth(300);
// default is hidden
if (types.count()>0)
_saCost2->setCurrentItem(0);
_partSelection->setData(_data);
_stackSelection->setData(_data);
_functionSelection->setData(_data);
_multiView->setData(_data);
// Force update of _data in all children of _multiView
// This is needed to make restoring of activeItem work!
_multiView->updateView(true);
/* this is needed to let the other widgets know the types */
restoreTraceTypes();
restoreTraceSettings();
QString caption;
if (_data) {
caption = _data->traceName();
if (!_data->command().isEmpty())
caption += " [" + _data->command() + ']';
}
setWindowTitle(caption);
if (!_data || (!_forcePartDock && _data->parts().count()<2)) {
_partDock->hide();
_partDockShown->setChecked(false);
}
else {
_partDock->show();
_partDockShown->setChecked(true);
}
updateStatusBar();
}
void TopLevel::addEventTypeMenu(QMenu* popup, bool withCost2)
{
if (_data) {
QMenu *popup1, *popup2 = 0;
QAction* action;
popup1 = popup->addMenu(i18n("Primary Event Type"));
connect(popup1, SIGNAL(triggered(QAction*)),
this, SLOT(setEventType(QAction*)));
if (withCost2) {
popup2 = popup->addMenu(i18n("Secondary Event Type"));
connect(popup2, SIGNAL(triggered(QAction*)),
this, SLOT(setEventType2(QAction*)));
if (_eventType2) {
action = popup2->addAction(i18n("Hide"));
action->setData(199);
popup2->addSeparator();
}
}
EventTypeSet* m = _data->eventTypes();
EventType* ct;
for (int i=0;irealCount();i++) {
ct = m->realType(i);
action = popup1->addAction(ct->longName());
action->setCheckable(true);
action->setData(100+i);
if (_eventType == ct) action->setChecked(true);
if (popup2) {
action = popup2->addAction(ct->longName());
action->setCheckable(true);
action->setData(100+i);
if (_eventType2 == ct) action->setChecked(true);
}
}
for (int i=0;iderivedCount();i++) {
ct = m->derivedType(i);
action = popup1->addAction(ct->longName());
action->setCheckable(true);
action->setData(200+i);
if (_eventType == ct) action->setChecked(true);
if (popup2) {
action = popup2->addAction(ct->longName());
action->setCheckable(true);
action->setData(200+i);
if (_eventType2 == ct) action->setChecked(true);
}
}
}
if (_showPercentage)
popup->addAction(i18n("Show Absolute Cost"),
this, SLOT(setAbsoluteCost()));
else
popup->addAction(i18n("Show Relative Cost"),
this, SLOT(setRelativeCost()));
}
bool TopLevel::setEventType(QAction* action)
{
if (!_data) return false;
int id = action->data().toInt(0);
EventTypeSet* m = _data->eventTypes();
EventType* ct=0;
if (id >=100 && id<199) ct = m->realType(id-100);
if (id >=200 && id<299) ct = m->derivedType(id-200);
return ct ? setEventType(ct) : false;
}
bool TopLevel::setEventType2(QAction* action)
{
if (!_data) return false;
int id = action->data().toInt(0);
EventTypeSet* m = _data->eventTypes();
EventType* ct=0;
if (id >=100 && id<199) ct = m->realType(id-100);
if (id >=200 && id<299) ct = m->derivedType(id-200);
return setEventType2(ct);
}
void TopLevel::addGoMenu(QMenu* popup)
{
popup->addAction(i18n("Go Back"), this, SLOT(goBack()));
popup->addAction(i18n("Go Forward"), this, SLOT(goForward()));
popup->addAction(i18n("Go Up"), this, SLOT(goUp()));
}
void TopLevel::goBack()
{
setDirectionDelayed(TraceItemView::Back);
}
void TopLevel::goForward()
{
setDirectionDelayed(TraceItemView::Forward);
}
void TopLevel::goUp()
{
setDirectionDelayed(TraceItemView::Up);
}
QString TopLevel::traceKey()
{
if (!_data || _data->command().isEmpty()) return QString();
QString name = _data->command();
QString key;
for (int l=0;lvalue(QStringLiteral("GroupType%1").arg(key),QString()).toString();
eventType = pConfig->value(QStringLiteral("EventType%1").arg(key),QString()).toString();
eventType2 = pConfig->value(QStringLiteral("EventType2%1").arg(key),QString()).toString();
delete pConfig;
ConfigGroup* cConfig = ConfigStorage::group(QStringLiteral("CurrentState"));
if (groupType.isEmpty())
groupType = cConfig->value(QStringLiteral("GroupType"),QString()).toString();
if (eventType.isEmpty())
eventType = cConfig->value(QStringLiteral("EventType"),QString()).toString();
if (eventType2.isEmpty())
eventType2 = cConfig->value(QStringLiteral("EventType2"),QString()).toString();
delete cConfig;
setGroupType(groupType);
setEventType(eventType);
setEventType2(eventType2);
// if still no cost type set, use first available
if (!_eventType && !_saCost->items().isEmpty())
eventTypeSelected(_saCost->items().first());
ConfigGroup* aConfig = ConfigStorage::group(QStringLiteral("Layouts"));
_layoutCount = aConfig->value(QStringLiteral("Count%1").arg(key), 0).toInt();
_layoutCurrent = aConfig->value(QStringLiteral("Current%1").arg(key), 0).toInt();
delete aConfig;
if (_layoutCount == 0) layoutRestore();
updateLayoutActions();
}
/**
* This must be called after setting group/cost types in the function
* selection widget, because the group/function choosing depends on
* filled lists in the function selection widget
*/
void TopLevel::restoreTraceSettings()
{
if (!_data) return;
QString key = traceKey();
restoreCurrentState(key);
ConfigGroup* pConfig = ConfigStorage::group(QStringLiteral("TracePositions"));
QString group = pConfig->value(QStringLiteral("Group%1").arg(key),QString()).toString();
delete pConfig;
if (!group.isEmpty()) setGroup(group);
// restoreCurrentState() usually leads to a call to setTraceItemDelayed()
// to restore last active item...
if (!_traceItemDelayed) {
// function not available any more.. try with "main"
if (!setFunction(QStringLiteral("main")))
_functionSelection->selectTopFunction();
}
}
/* Layout */
void TopLevel::layoutDuplicate()
{
// save current and allocate a new slot
_multiView->saveLayout(QStringLiteral("Layout%1-MainView").arg(_layoutCurrent),
traceKey());
_layoutCurrent = _layoutCount;
_layoutCount++;
updateLayoutActions();
if (0) qDebug() << "TopLevel::layoutDuplicate: count " << _layoutCount;
}
void TopLevel::layoutRemove()
{
if (_layoutCount <2) return;
int from = _layoutCount-1;
if (_layoutCurrent == from) { _layoutCurrent--; from--; }
// restore from last and decrement count
_multiView->restoreLayout(QStringLiteral("Layout%1-MainView").arg(from),
traceKey());
_layoutCount--;
updateLayoutActions();
}
void TopLevel::layoutNext()
{
if (_layoutCount <2) return;
QString key = traceKey();
QString layoutPrefix = QStringLiteral("Layout%1-MainView");
_multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key);
_layoutCurrent++;
if (_layoutCurrent == _layoutCount) _layoutCurrent = 0;
_multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key);
if (0) qDebug() << "TopLevel::layoutNext: current "
<< _layoutCurrent << endl;
}
void TopLevel::layoutPrevious()
{
if (_layoutCount <2) return;
QString key = traceKey();
QString layoutPrefix = QStringLiteral("Layout%1-MainView");
_multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key);
_layoutCurrent--;
if (_layoutCurrent <0) _layoutCurrent = _layoutCount-1;
_multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key);
if (0) qDebug() << "TopLevel::layoutPrevious: current "
<< _layoutCurrent << endl;
}
void TopLevel::layoutSave()
{
QString key = traceKey();
QString layoutPrefix = QStringLiteral("Layout%1-MainView");
_multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key);
// save all layouts as defaults (ie. without any group name postfix)
for(int i=0;i<_layoutCount;i++) {
_multiView->restoreLayout(layoutPrefix.arg(i), key);
_multiView->saveLayout(layoutPrefix.arg(i), QString());
}
// restore the previously saved current layout
_multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key);
ConfigGroup* layoutConfig = ConfigStorage::group(QStringLiteral("Layouts"));
layoutConfig->setValue(QStringLiteral("DefaultCount"), _layoutCount);
layoutConfig->setValue(QStringLiteral("DefaultCurrent"), _layoutCurrent);
delete layoutConfig;
}
void TopLevel::layoutRestore()
{
KConfig *config = KSharedConfig::openConfig().data();
KConfigGroup aConfig(config, "Layouts");
_layoutCount = aConfig.readEntry("DefaultCount", 0);
_layoutCurrent = aConfig.readEntry("DefaultCurrent", 0);
if (_layoutCount == 0) {
_layoutCount++;
return;
}
QString layoutPrefix = QStringLiteral("Layout%1-MainView");
_multiView->restoreLayout( layoutPrefix.arg(_layoutCurrent), traceKey());
updateLayoutActions();
}
void TopLevel::updateLayoutActions()
{
QAction* ka;
ka = actionCollection()->action(QStringLiteral("layout_next"));
if (ka) ka->setEnabled(_layoutCount>1);
ka = actionCollection()->action(QStringLiteral("layout_previous"));
if (ka) ka->setEnabled(_layoutCount>1);
ka = actionCollection()->action(QStringLiteral("layout_remove"));
if (ka) ka->setEnabled(_layoutCount>1);
_statusbar->showMessage(i18n("Layout Count: %1", _layoutCount), 1000);
}
void TopLevel::updateStatusBar()
{
if (!_data || _data->parts().count()==0) {
_statusLabel->setText(i18n("No profile data file loaded."));
return;
}
QString status = QStringLiteral("%1 [%2] - ")
.arg(_data->shortTraceName())
.arg(_data->activePartRange());
if (_eventType) {
status += i18n("Total %1 Cost: %2",
_eventType->longName(),
_data->prettySubCost(_eventType));
/* this gets too long...
if (_eventType2 && (_eventType2 != _eventType))
status += i18n(", %1 Cost: %2")
.arg(_eventType2->longName())
.arg(_data->prettySubCost(_eventType2));
*/
}
else
status += i18n("No event type selected");
/* Not working... should give group of selected function
if (_groupType != ProfileContext::Function) {
status += QString(" - %1 '%2'")
.arg(ProfileContext::i18nTypeName(_groupType))
.arg(_group ? _group->prettyName() : i18n("(None)"));
}
*/
_statusLabel->setText(status);
}
void TopLevel::configure()
{
if (ConfigDlg::configure( GlobalGUIConfig::config(),
_data, this)) {
GlobalGUIConfig::config()->saveOptions();
configChanged();
}
else
GlobalGUIConfig::config()->readOptions();
}
bool TopLevel::queryClose()
{
saveTraceSettings();
// save current toplevel options as defaults...
GlobalConfig::setShowPercentage(_showPercentage);
GlobalConfig::setShowExpanded(_showExpanded);
GlobalConfig::setShowCycles(_showCycles);
GlobalConfig::setHideTemplates(_hideTemplates);
GlobalGUIConfig::config()->saveOptions();
saveCurrentState(QString());
// toolbar and dock positions are automatically stored
// if part dock was chosen visible even for only 1 part loaded,
// keep this choice...
_forcePartDock = false;
if (_data && (_data->parts().count()<2) && _partDock->isVisible())
_forcePartDock=true;
KConfigGroup dockConfig(KSharedConfig::openConfig(), "Docks");
dockConfig.writeEntry("ForcePartDockVisible", _forcePartDock);
return true;
}
void TopLevel::splitSlot()
{
int count = _multiView->childCount();
if (count<1) count = 1;
if (count>2) count = 2;
count = 3-count;
_multiView->setChildCount(count);
_taSplit->setChecked(count>1);
_taSplitDir->setEnabled(count>1);
_taSplitDir->setChecked(_multiView->orientation() == Qt::Horizontal);
}
void TopLevel::splitDirSlot()
{
_multiView->setOrientation( _taSplitDir->isChecked() ?
Qt::Horizontal : Qt::Vertical );
}
// this is called after a config change in the dialog
void TopLevel::configChanged()
{
// Invalidate found/cached dirs of source files if we have TraceData loaded.
if (_data) {
_data->resetSourceDirs();
}
_stackSelection->refresh();
updateViewsOnChange(TraceItemView::configChanged);
}
void TopLevel::slotShowTipOnStart() {
KTipDialog::showTip(this);
}
void TopLevel::slotShowTip() {
KTipDialog::showTip( this, QString(), true );
}
void TopLevel::dummySlot()
{
}
void TopLevel::activePartsChangedSlot(const TracePartList& list)
{
if (!_data) return;
if (!_data->activateParts(list)) {
// qDebug("TopLevel::activePartsChangedSlot: No Change!");
return;
}
_activeParts = list;
_partSelection->set(list);
_multiView->set(list);
_functionSelection->set(list);
_stackSelection->refresh();
updateStatusBar();
}
void TopLevel::partsHideSelectedSlotDelayed()
{
QTimer::singleShot( 0, this, &TopLevel::partsHideSelectedSlot );
}
// this puts selected parts into hidden list,
// deselects them and makes the remaining parts selected
void TopLevel::partsHideSelectedSlot()
{
if (!_data) return;
TracePartList newHidden, newActive;
foreach(TracePart* part, _data->parts()) {
if (_activeParts.contains(part) ||
_hiddenParts.contains(part))
newHidden.append(part);
else
newActive.append(part);
}
_hiddenParts = newHidden;
_partSelection->hiddenPartsChangedSlot(_hiddenParts);
#if 0
_mainWidget1->hiddenPartsChangedSlot(_hiddenParts);
_mainWidget2->hiddenPartsChangedSlot(_hiddenParts);
#endif
activePartsChangedSlot(newActive);
}
void TopLevel::partsUnhideAllSlotDelayed()
{
QTimer::singleShot( 0, this, &TopLevel::partsUnhideAllSlot );
}
// this unhides all hidden parts. Does NOT change selection
void TopLevel::partsUnhideAllSlot()
{
if (!_data) return;
_hiddenParts.clear();
_partSelection->hiddenPartsChangedSlot(_hiddenParts);
#if 0
_mainWidget1->hiddenPartsChangedSlot(_hiddenParts);
_mainWidget2->hiddenPartsChangedSlot(_hiddenParts);
#endif
}
void TopLevel::forceTrace()
{
if (_ccProcess) {
// callgrind_control still running, cancel request
qDebug("TopLevel::forceTrace: killing old callgrind_control");
_ccProcess->kill();
delete _ccProcess;
_ccProcess = 0;
_ccOutput = QString();
}
if (!_taDump->isChecked()) return;
// get PID of first loaded part
int pid = 0;
TracePart* p = 0;
TracePartList pl;
if (_data) pl = _data->parts();
if (!pl.isEmpty()) p = pl.first();
if (p) pid = p->processID();
if (pid == 0) {
showMessage(i18n("Cannot determine receiver PID for dump request"),
5000);
_taDump->setChecked(false);
return;
}
qDebug("TopLevel::forceTrace: run 'callgrind_control -d %d'", pid);
_ccProcess = new QProcess(this);
connect(_ccProcess, &QProcess::readyReadStandardOutput,
this, &TopLevel::ccReadOutput);
connect(_ccProcess, SIGNAL(error(QProcess::ProcessError)),
SLOT(ccError(QProcess::ProcessError)));
connect(_ccProcess, SIGNAL(finished(int,QProcess::ExitStatus)),
SLOT(ccExit(int,QProcess::ExitStatus)));
_ccProcess->start(QStringLiteral("callgrind_control -d %1").arg(pid),
QIODevice::ReadOnly);
}
void TopLevel::ccReadOutput()
{
QProcess* p = qobject_cast(sender());
qDebug("TopLevel::ccReadOutput: QProcess %p", p);
// signal from old/uninteresting process?
if (!_ccProcess) return;
if (p != _ccProcess) return;
- _ccOutput.append(QString(_ccProcess->readAllStandardOutput()));
+ _ccOutput.append(QString::fromLocal8Bit(_ccProcess->readAllStandardOutput()));
}
void TopLevel::ccError(QProcess::ProcessError e)
{
QProcess* p = qobject_cast(sender());
qDebug("TopLevel::ccError: Got %d from QProcess %p",
e, p);
// signal from old/uninteresting process?
if (!_ccProcess) return;
if (p != _ccProcess) return;
showMessage(i18n("Error running callgrind_control"), 5000);
_ccProcess->deleteLater();
_ccProcess = 0;
}
void TopLevel::ccExit(int exitCode, QProcess::ExitStatus s)
{
QProcess* p = qobject_cast(sender());
qDebug("TopLevel::ccExit: QProcess %p, exitCode %d",
p, exitCode);
// signal from old/uninteresting process?
if (!_ccProcess) return;
if (p != _ccProcess) return;
_ccProcess->deleteLater();
_ccProcess = 0;
_taDump->setChecked(false);
// if not successful no need to reload
if ((s == QProcess::CrashExit) || (exitCode != 0))
return;
// FIXME: Are we sure that file is completely
// dumped after waiting one second?
QTimer::singleShot( 1000, this, &TopLevel::reload );
}
void TopLevel::forwardAboutToShow()
{
QMenu *popup = _paForward->menu();
popup->clear();
StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0;
HistoryItem* hi = b ? b->current() : 0;
TraceFunction* f;
QAction* action;
if (!hi) {
popup->addAction(i18n("(No Stack)"));
return;
}
hi = hi->next();
if (!hi) {
popup->addAction(i18n("(No next function)"));
return;
}
int count = 1;
while (countfunction();
if (!f) break;
QString name = GlobalConfig::shortenSymbol(f->prettyName());
//qDebug("forward: Adding %s", name.ascii());
action = popup->addAction(name);
action->setData(count);
hi = hi->next();
count++;
}
}
void TopLevel::backAboutToShow()
{
QMenu *popup = _paBack->menu();
popup->clear();
StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0;
HistoryItem* hi = b ? b->current() : 0;
TraceFunction* f;
QAction* action;
if (!hi) {
popup->addAction(i18n("(No Stack)"));
return;
}
hi = hi->last();
if (!hi) {
popup->addAction(i18n("(No previous function)"));
return;
}
int count = 1;
while (countfunction();
if (!f) break;
QString name = GlobalConfig::shortenSymbol(f->prettyName());
//qDebug("back: Adding %s", name.ascii());
action = popup->addAction(name);
action->setData(count);
hi = hi->last();
count++;
}
}
void TopLevel::upAboutToShow()
{
QMenu *popup = _paUp->menu();
popup->clear();
StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0;
HistoryItem* hi = b ? b->current() : 0;
TraceFunction* f = hi ? hi->function() : 0;
QAction* action;
if (!f) {
popup->addAction(i18n("(No Stack)"));
return;
}
f = hi->stack()->caller(f, false);
if (!f) {
popup->addAction(i18n("(No Function Up)"));
return;
}
int count = 1;
while (countprettyName());
action = popup->addAction(name);
action->setData(count);
f = hi->stack()->caller(f, false);
count++;
}
}
void TopLevel::forwardTriggered(QAction* action)
{
int count = action->data().toInt(0);
//qDebug("forwardTriggered: %d", count);
if( count <= 0)
return;
StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0;
if (!b) return;
while (count>1) {
b->goForward();
count--;
}
_stackSelection->browserForward();
}
void TopLevel::backTriggered(QAction* action)
{
int count = action->data().toInt(0);
//qDebug("backTriggered: %d", count);
if( count <= 0)
return;
StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0;
if (!b) return;
while (count>1) {
b->goBack();
count--;
}
_stackSelection->browserBack();
}
void TopLevel::upTriggered(QAction* action)
{
int count = action->data().toInt(0);
//qDebug("upTriggered: %d", count);
if( count <= 0)
return;
StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0;
HistoryItem* hi = b ? b->current() : 0;
if (!hi) return;
TraceFunction* f = hi->function();
while (count>0 && f) {
f = hi->stack()->caller(f, false);
count--;
}
//qDebug("upActivated: %s", f ? f->prettyName().ascii() : "??" );
if (f)
setFunction(f);
}
void TopLevel::showMessage(const QString& msg, int ms)
{
if (_statusbar)
_statusbar->showMessage(msg, ms);
}
void TopLevel::showStatus(const QString& msg, int progress)
{
static bool msgUpdateNeeded = true;
if (!_statusbar) return;
if (msg.isEmpty()) {
//reset status
if (_progressBar) {
_statusbar->removeWidget(_progressBar);
delete _progressBar;
_progressBar = 0;
}
_statusbar->clearMessage();
_progressMsg = msg;
return;
}
if (_progressMsg.isEmpty())
_progressStart.start();
if (msg != _progressMsg) {
_progressMsg = msg;
msgUpdateNeeded = true;
}
// do nothing if last change was less than 0.5 seconds ago
if (_progressStart.elapsed() < 500)
return;
if (!_progressBar) {
_progressBar = new QProgressBar(_statusbar);
_progressBar->setMaximumSize(200, _statusbar->height()-4);
_statusbar->addPermanentWidget(_progressBar, 1);
_progressBar->show();
msgUpdateNeeded = true;
}
_progressStart.restart();
if (msgUpdateNeeded) {
_statusbar->showMessage(msg);
msgUpdateNeeded = false;
}
_progressBar->setValue(progress);
// let the progress bar update itself
qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
}
void TopLevel::loadStart(const QString& filename)
{
showStatus(i18n("Loading %1", filename), 0);
Logger::_filename = filename;
}
void TopLevel::loadFinished(const QString& msg)
{
showStatus(QString(), 0);
if (!msg.isEmpty())
showMessage(i18n("Error loading %1: %2", _filename, msg),
2000);
}
void TopLevel::loadProgress(int progress)
{
showStatus(i18n("Loading %1", _filename), progress);
}
void TopLevel::loadError(int line, const QString& msg)
{
qCritical() << "Loading" << _filename << ":" << line << ": " << msg;
}
void TopLevel::loadWarning(int line, const QString& msg)
{
qWarning() << "Loading" << _filename << ":" << line << ": " << msg;
}
bool TopLevel::openDataFile(const QString& file)
{
TraceData* d = new TraceData(this);
int filesLoaded;
// see whether this file is compressed, than take the direct route
QMimeDatabase dataBase;
QString mimeType = dataBase.mimeTypeForFile(file, QMimeDatabase::MatchContent).name();
KCompressionDevice* compressed;
compressed = new KCompressionDevice(file,
KFilterDev::compressionTypeForMimeType(mimeType));
if (compressed &&
(compressed->compressionType() != KCompressionDevice::None)) {
filesLoaded = d->load(compressed, file);
} else {
// else fallback to string based method that can also find multi-part callgrind data.
filesLoaded = d->load(file);
}
if (filesLoaded > 0) {
setData(d);
return true;
} else {
return false;
}
}
diff --git a/libcore/tracedata.cpp b/libcore/tracedata.cpp
index b2a64ea..61270ea 100644
--- a/libcore/tracedata.cpp
+++ b/libcore/tracedata.cpp
@@ -1,3752 +1,3752 @@
/* This file is part of KCachegrind.
Copyright (c) 2002-2016 Josef Weidendorfer
KCachegrind 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, version 2.
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "tracedata.h"
#include
#include
#include
#include
#include
#include
#include "logger.h"
#include "loader.h"
#include "globalconfig.h"
#include "utils.h"
#include "fixcost.h"
#define TRACE_DEBUG 0
#define TRACE_ASSERTIONS 0
//---------------------------------------------------
// TraceJumpCost
TraceJumpCost::TraceJumpCost(ProfileContext* c)
:CostItem(c)
{
TraceJumpCost::clear();
}
TraceJumpCost::~TraceJumpCost()
{}
SubCost TraceJumpCost::executedCount()
{
if (_dirty) update();
return _executedCount;
}
SubCost TraceJumpCost::followedCount()
{
if (_dirty) update();
return _followedCount;
}
QString TraceJumpCost::costString(EventTypeSet*)
{
if (_dirty) update();
return QStringLiteral("%1/%2")
.arg(_followedCount.pretty())
.arg(_executedCount.pretty());
}
void TraceJumpCost::clear()
{
_followedCount = 0;
_executedCount = 0;
}
void TraceJumpCost::addCost(TraceJumpCost* item)
{
if (item->_dirty) item->update();
_followedCount += item->followedCount();
_executedCount += item->executedCount();
}
//---------------------------------------------------
// TraceCallCost
TraceCallCost::TraceCallCost(ProfileContext* context)
: ProfileCostArray(context)
{
_callCount = 0;
}
TraceCallCost::~TraceCallCost()
{}
QString TraceCallCost::costString(EventTypeSet* m)
{
return QStringLiteral("%1, Calls %2")
.arg(ProfileCostArray::costString(m))
.arg(_callCount.pretty());
}
QString TraceCallCost::prettyCallCount()
{
return _callCount.pretty();
}
void TraceCallCost::clear()
{
_callCount = 0;
ProfileCostArray::clear();
}
SubCost TraceCallCost::callCount()
{
if (_dirty) update();
return _callCount;
}
void TraceCallCost::addCallCount(SubCost c)
{
_callCount += c;
invalidate();
}
//---------------------------------------------------
// TraceInclusiveCost
TraceInclusiveCost::TraceInclusiveCost(ProfileContext* context)
: ProfileCostArray(context), _inclusive(context)
{}
TraceInclusiveCost::~TraceInclusiveCost()
{}
QString TraceInclusiveCost::costString(EventTypeSet* m)
{
return QStringLiteral("%1, Inclusive %2")
.arg(ProfileCostArray::costString(m))
.arg(_inclusive.costString(m));
}
void TraceInclusiveCost::clear()
{
_inclusive.clear();
ProfileCostArray::clear();
}
ProfileCostArray* TraceInclusiveCost::inclusive()
{
if (_dirty) update();
return &_inclusive;
}
void TraceInclusiveCost::addInclusive(ProfileCostArray* c)
{
_inclusive.addCost(c);
invalidate();
}
//---------------------------------------------------
// TraceListCost
TraceListCost::TraceListCost(ProfileContext* context)
: ProfileCostArray(context)
{
_lastDep = 0;
}
TraceListCost::~TraceListCost()
{}
void TraceListCost::addDep(ProfileCostArray* dep)
{
#if TRACE_ASSERTIONS
if (_deps.contains(dep)) {
qDebug("addDep: %s already in list!",
qPrintable(dep->fullName()));
return;
}
#endif
_deps.append(dep);
_lastDep = dep;
invalidate();
#if TRACE_DEBUG
qDebug("%s added\n %s (now %d)",
qPrintable( fullName() ), qPrintable(dep->fullName()),
_deps.count());
#endif
}
ProfileCostArray* TraceListCost::findDepFromPart(TracePart* part)
{
if (_lastDep && _lastDep->part() == part)
return _lastDep;
foreach(ProfileCostArray* dep, _deps) {
if (dep->part() == part) {
_lastDep = dep;
return dep;
}
}
return 0;
}
void TraceListCost::update()
{
if (!_dirty) return;
#if TRACE_DEBUG
qDebug("update %s (count %d)",
qPrintable( fullName() ), _deps.count());
#endif
clear();
foreach(ProfileCostArray* item, _deps) {
if (onlyActiveParts())
if (!item->part() || !item->part()->isActive()) continue;
addCost(item);
}
_dirty = false;
#if TRACE_DEBUG
qDebug(" > %s", qPrintable(costString(0)));
#endif
}
//---------------------------------------------------
// TraceJumpListCost
TraceJumpListCost::TraceJumpListCost(ProfileContext* context)
: TraceJumpCost(context)
{
_lastDep = 0;
}
TraceJumpListCost::~TraceJumpListCost()
{}
void TraceJumpListCost::addDep(TraceJumpCost* dep)
{
#if TRACE_ASSERTIONS
if (_deps.contains(dep)) {
qDebug("addDep: %s already in list!",
qPrintable(dep->fullName()));
return;
}
#endif
_deps.append(dep);
_lastDep = dep;
invalidate();
#if TRACE_DEBUG
qDebug("%s added\n %s (now %d)",
qPrintable( fullName() ), qPrintable(dep->fullName()),
_deps.count());
#endif
}
TraceJumpCost* TraceJumpListCost::findDepFromPart(TracePart* part)
{
if (_lastDep && _lastDep->part() == part)
return _lastDep;
foreach(TraceJumpCost* dep, _deps) {
if (dep->part() == part) {
_lastDep = dep;
return dep;
}
}
return 0;
}
void TraceJumpListCost::update()
{
if (!_dirty) return;
#if TRACE_DEBUG
qDebug("update %s (count %d)",
qPrintable( fullName() ), _deps.count());
#endif
clear();
foreach(TraceJumpCost* item, _deps) {
if (onlyActiveParts())
if (!item->part() || !item->part()->isActive()) continue;
addCost(item);
}
_dirty = false;
#if TRACE_DEBUG
qDebug(" > %s", qPrintable(costString(0)));
#endif
}
//---------------------------------------------------
// TraceCallListCost
TraceCallListCost::TraceCallListCost(ProfileContext* context)
: TraceCallCost(context)
{
_lastDep = 0;
}
TraceCallListCost::~TraceCallListCost()
{}
void TraceCallListCost::addDep(TraceCallCost* dep)
{
#if TRACE_ASSERTIONS
if (_deps.contains(dep)) {
qDebug("addDep: %s already in list!",
qPrintable(dep->fullName()));
return;
}
#endif
_deps.append(dep);
_lastDep = dep;
invalidate();
#if TRACE_DEBUG
qDebug("%s added\n %s (now %d)",
qPrintable( fullName() ), qPrintable(dep->fullName()),
_deps.count());
#endif
}
TraceCallCost* TraceCallListCost::findDepFromPart(TracePart* part)
{
if (_lastDep && _lastDep->part() == part)
return _lastDep;
foreach(TraceCallCost* dep, _deps) {
if (dep->part() == part) {
_lastDep = dep;
return dep;
}
}
return 0;
}
void TraceCallListCost::update()
{
if (!_dirty) return;
#if TRACE_DEBUG
qDebug("update %s (count %d)",
qPrintable( fullName() ), _deps.count());
#endif
/* Without dependent cost items, assume fixed costs,
* i.e. do not change cost */
if (_deps.count()>0) {
clear();
foreach(TraceCallCost* item, _deps) {
if (onlyActiveParts())
if (!item->part() || !item->part()->isActive()) continue;
addCost(item);
addCallCount(item->callCount());
}
}
_dirty = false;
#if TRACE_DEBUG
qDebug(" > %s", qPrintable(costString(0)));
#endif
}
//---------------------------------------------------
// TraceInclusiveListCost
TraceInclusiveListCost::TraceInclusiveListCost(ProfileContext* context)
: TraceInclusiveCost(context)
{
_lastDep = 0;
}
TraceInclusiveListCost::~TraceInclusiveListCost()
{}
void TraceInclusiveListCost::addDep(TraceInclusiveCost* dep)
{
#if TRACE_ASSERTIONS
if (_deps.contains(dep)) {
qDebug("addDep: %s already in list!",
qPrintable(dep->fullName()));
return;
}
#endif
_deps.append(dep);
_lastDep = dep;
invalidate();
#if TRACE_DEBUG
qDebug("%s added\n %s (now %d)",
qPrintable( fullName() ), qPrintable(dep->fullName()),
_deps.count());
#endif
}
TraceInclusiveCost* TraceInclusiveListCost::findDepFromPart(TracePart* part)
{
if (_lastDep && _lastDep->part() == part)
return _lastDep;
foreach(TraceInclusiveCost* dep, _deps) {
if (dep->part() == part) {
_lastDep = dep;
return dep;
}
}
return 0;
}
void TraceInclusiveListCost::update()
{
if (!_dirty) return;
#if TRACE_DEBUG
qDebug("update %s (count %d)",
qPrintable( fullName() ), _deps.count());
#endif
clear();
foreach(TraceInclusiveCost* item, _deps) {
if (onlyActiveParts())
if (!item->part() || !item->part()->isActive()) continue;
addCost(item);
addInclusive(item->inclusive());
}
_dirty = false;
#if TRACE_DEBUG
qDebug(" > %s", qPrintable(costString(0)));
#endif
}
//---------------------------------------------------
// TracePartInstrJump
TracePartInstrJump::TracePartInstrJump(TraceInstrJump* instrJump,
TracePartInstrJump* next)
: TraceJumpCost(ProfileContext::context(ProfileContext::PartInstrJump))
{
_dep = instrJump;
_next = next;
}
TracePartInstrJump::~TracePartInstrJump()
{}
//---------------------------------------------------
// TracePartInstrCall
TracePartInstrCall::TracePartInstrCall(TraceInstrCall* instrCall)
: TraceCallCost(ProfileContext::context(ProfileContext::PartInstrCall))
{
_dep = instrCall;
}
TracePartInstrCall::~TracePartInstrCall()
{}
//---------------------------------------------------
// TracePartInstr
TracePartInstr::TracePartInstr(TraceInstr* instr)
: ProfileCostArray(ProfileContext::context(ProfileContext::PartInstr))
{
_dep = instr;
}
TracePartInstr::~TracePartInstr()
{}
//---------------------------------------------------
// TracePartLineJump
TracePartLineJump::TracePartLineJump(TraceLineJump* lineJump)
: TraceJumpCost(ProfileContext::context(ProfileContext::PartLineJump))
{
_dep = lineJump;
}
TracePartLineJump::~TracePartLineJump()
{}
//---------------------------------------------------
// TracePartLineCall
TracePartLineCall::TracePartLineCall(TraceLineCall* lineCall)
: TraceCallCost(ProfileContext::context(ProfileContext::PartLineCall))
{
_dep = lineCall;
}
TracePartLineCall::~TracePartLineCall()
{}
//---------------------------------------------------
// TracePartLine
TracePartLine::TracePartLine(TraceLine* line)
: ProfileCostArray(ProfileContext::context(ProfileContext::PartLine))
{
_dep = line;
}
TracePartLine::~TracePartLine()
{}
//---------------------------------------------------
// TracePartCall
TracePartCall::TracePartCall(TraceCall* call)
: TraceCallListCost(ProfileContext::context(ProfileContext::PartCall))
{
_dep = call;
_firstFixCallCost = 0;
}
TracePartCall::~TracePartCall()
{}
bool TracePartCall::isRecursion()
{
return call()->isRecursion();
}
void TracePartCall::update()
{
#if !USE_FIXCOST
TraceCallListCost::update();
#else
if (!_dirty) return;
#if TRACE_DEBUG
qDebug("update %s", qPrintable( fullName() ));
#endif
/* Without dependent cost items, assume fixed costs,
* i.e. do not change cost */
if (_firstFixCallCost) {
clear();
FixCallCost* item;
for (item = _firstFixCallCost; item; item = item->nextCostOfPartCall())
item->addTo(this);
}
_dirty = false;
#if TRACE_DEBUG
qDebug(" > %s", qPrintable(costString(0)));
#endif
#endif // USE_FIXCOST
}
//---------------------------------------------------
// TracePartFunction
TracePartFunction::TracePartFunction(TraceFunction* function,
TracePartObject* partObject,
TracePartFile *partFile)
: TraceInclusiveCost(ProfileContext::context(ProfileContext::PartFunction))
{
_dep = function;
_partObject = partObject;
_partFile = partFile;
_partClass = 0;
_calledCount = 0;
_callingCount = 0;
_calledContexts = 0;
_callingContexts = 0;
_firstFixCost = 0;
_firstFixJump = 0;
}
TracePartFunction::~TracePartFunction()
{}
QString TracePartFunction::prettyCalledCount()
{
return _calledCount.pretty();
}
QString TracePartFunction::prettyCallingCount()
{
return _callingCount.pretty();
}
QString TracePartFunction::costString(EventTypeSet* m)
{
update();
QString res = TraceInclusiveCost::costString(m);
res += QStringLiteral(", called from %1: %2")
.arg(_calledContexts).arg(prettyCalledCount());
res += QStringLiteral(", calling from %1: %2")
.arg(_callingContexts).arg(prettyCallingCount());
return res;
}
void TracePartFunction::addPartInstr(TracePartInstr* ref)
{
#if TRACE_ASSERTIONS
if (_partInstr.contains(ref)) {
qDebug("TracePartFunction::addPartInstr: %s already in list!",
qPrintable(ref->name()));
return;
}
#endif
_partInstr.append(ref);
invalidate();
#if TRACE_DEBUG
qDebug("%s added\n %s (now %d)",
qPrintable( fullName() ), qPrintable(ref->fullName()),
_partInstr.count());
#endif
}
void TracePartFunction::addPartLine(TracePartLine* ref)
{
#if TRACE_ASSERTIONS
if (_partLines.contains(ref)) {
qDebug("TracePartFunction::addPartLine: %s already in list!",
qPrintable(ref->name()));
return;
}
#endif
_partLines.append(ref);
invalidate();
#if TRACE_DEBUG
qDebug("%s added\n %s (now %d)",
qPrintable( fullName() ), qPrintable(ref->fullName()),
_partLines.count());
#endif
}
void TracePartFunction::addPartCaller(TracePartCall* ref)
{
#if TRACE_ASSERTIONS
if (_partCallers.contains(ref)) {
qDebug("TracePartFunction::addPartCaller: %s already in list!",
qPrintable(ref->name()));
return;
}
#endif
_partCallers.append(ref);
invalidate();
#if TRACE_DEBUG
qDebug("%s added Caller\n %s (now %d)",
qPrintable( fullName() ), qPrintable(ref->fullName()),
_partCallers.count());
#endif
}
void TracePartFunction::addPartCalling(TracePartCall* ref)
{
#if TRACE_ASSERTIONS
if (_partCallings.contains(ref)) {
qDebug("TracePartFunction::addPartCalling: %s already in list!",
qPrintable(ref->name()));
return;
}
#endif
_partCallings.append(ref);
invalidate();
#if TRACE_DEBUG
qDebug("%s added Calling\n %s (now %d)",
qPrintable( fullName() ), qPrintable(ref->fullName()),
_partCallings.count());
#endif
}
SubCost TracePartFunction::calledCount()
{
if (_dirty) update();
return _calledCount;
}
int TracePartFunction::calledContexts()
{
if (_dirty) update();
return _calledContexts;
}
SubCost TracePartFunction::callingCount()
{
if (_dirty) update();
return _callingCount;
}
int TracePartFunction::callingContexts()
{
if (_dirty) update();
return _callingContexts;
}
void TracePartFunction::update()
{
if (!_dirty) return;
#if TRACE_DEBUG
qDebug("TracePartFunction::update %s (Callers %d, Callings %d, lines %d)",
qPrintable(name()), _partCallers.count(), _partCallings.count(),
_partLines.count());
#endif
_calledCount = 0;
_callingCount = 0;
_calledContexts = 0;
_callingContexts = 0;
// To calculate context counts, we just use first real event type (FIXME?)
EventType* e = data() ? data()->eventTypes()->realType(0) : 0;
// calculate additional cost metrics
foreach(TracePartCall* caller, _partCallers) {
if (e && (caller->subCost(e) >0))
_calledContexts++;
SubCost c = caller->callCount();
if (c>0) {
_calledCount += c;
}
}
foreach(TracePartCall* calling, _partCallings) {
if (e && (calling->subCost(e)>0))
_callingContexts++;
SubCost c = calling->callCount();
if (c>0) {
_callingCount += c;
}
}
// self cost
#if !USE_FIXCOST
if (_partLines.count()>0) {
ProfileCostArray::clear();
foreach(TracePartLine* line, _partLines)
addCost(line);
}
#else
if (_firstFixCost) {
ProfileCostArray::clear();
FixCost* item;
for (item = _firstFixCost; item; item = item->nextCostOfPartFunction())
item->addTo(this);
}
#endif
/* There are two possibilities to calculate inclusive cost:
* 1) sum of call costs to this function
* 2) sum of call costs from this function + self cost
*
* 1) is wrong if a function was called spontaneous, but also by a call.
* This eventually can happen with thread/process startup functions,
* and signal handlers.
*
* 2) is wrong with "skipped PLT" and the calltree skin, because
* cost of PLT is attributed to called function (?)
*
* For now, do 1) if there are callers, otherwise 2).
* Should this be fixed to take the maximum of 1) and 2) ?
*/
_inclusive.clear();
if (_calledCount>0) {
// inclusive cost: if possible, use caller sums
foreach(TracePartCall* caller, _partCallers) {
// detect simple recursion (no cycle)
if (caller->isRecursion()) continue;
addInclusive(caller);
}
}
else {
// without caller info, use calling sum + line costs
foreach(TracePartCall* calling, _partCallings) {
// detect simple recursion (no cycle)
if (calling->isRecursion()) continue;
addInclusive(calling);
}
_dirty = false; // do not recurse!
addInclusive(this);
}
_dirty = false;
#if TRACE_DEBUG
qDebug(" > %s", qPrintable(costString(0)));
#endif
}
//---------------------------------------------------
// TracePartClass
TracePartClass::TracePartClass(TraceClass* cls)
: TraceInclusiveListCost(ProfileContext::context(ProfileContext::PartClass))
{
_dep = cls;
}
TracePartClass::~TracePartClass()
{}
QString TracePartClass::prettyName() const
{
return QStringLiteral("%1 from %2")
.arg( _dep->name().isEmpty() ? QStringLiteral("(global)") : _dep->name())
.arg(part()->name());
}
//---------------------------------------------------
// TracePartFile
TracePartFile::TracePartFile(TraceFile* file)
: TraceInclusiveListCost(ProfileContext::context(ProfileContext::PartFile))
{
_dep = file;
}
TracePartFile::~TracePartFile()
{}
//---------------------------------------------------
// TracePartObject
TracePartObject::TracePartObject(TraceObject* object)
: TraceInclusiveListCost(ProfileContext::context(ProfileContext::PartObject))
{
_dep = object;
}
TracePartObject::~TracePartObject()
{}
//---------------------------------------------------
// TraceInstrJump
TraceInstrJump::TraceInstrJump(TraceInstr* instrFrom, TraceInstr* instrTo,
bool isCondJump)
: TraceJumpCost(ProfileContext::context(ProfileContext::InstrJump))
{
_first = 0;
_instrFrom = instrFrom;
_instrTo = instrTo;
_isCondJump = isCondJump;
}
TraceInstrJump::~TraceInstrJump()
{
// we are the owner of the TracePartInstrJump's generated in our factory
TracePartInstrJump* item = _first, *next;
while(item) {
next = item->next();
delete item;
item = next;
}
}
TracePartInstrJump* TraceInstrJump::partInstrJump(TracePart* part)
{
static TracePartInstrJump* item = 0;
// shortcut if recently used
if (item &&
(item->instrJump()==this) &&
(item->part() == part)) return item;
for(item = _first; item; item = item->next())
if (item->part() == part)
return item;
item = new TracePartInstrJump(this, _first);
item->setPosition(part);
_first = item;
return item;
}
void TraceInstrJump::update()
{
if (!_dirty) return;
clear();
TracePartInstrJump* item;
for (item = _first; item; item = item->next()) {
if (!item->part() || !item->part()->isActive()) continue;
addCost(item);
}
_dirty = false;
#if TRACE_DEBUG
qDebug("updated %s", qPrintable( fullName() ));
#endif
#if TRACE_DEBUG
qDebug(" > %s", qPrintable(costString(0)));
#endif
}
QString TraceInstrJump::name() const
{
return QStringLiteral("jump at 0x%1 to 0x%2")
.arg(_instrFrom->addr().toString())
.arg(_instrTo->addr().toString());
}
//---------------------------------------------------
// TraceLineJump
TraceLineJump::TraceLineJump(TraceLine* lineFrom, TraceLine* lineTo,
bool isCondJump)
: TraceJumpListCost(ProfileContext::context(ProfileContext::LineJump))
{
_lineFrom = lineFrom;
_lineTo = lineTo;
_isCondJump = isCondJump;
}
TraceLineJump::~TraceLineJump()
{
// we are the owner of TracePartLineJump's generated in our factory
qDeleteAll(_deps);
}
TracePartLineJump* TraceLineJump::partLineJump(TracePart* part)
{
TracePartLineJump* item = (TracePartLineJump*) findDepFromPart(part);
if (!item) {
item = new TracePartLineJump(this);
item->setPosition(part);
addDep(item);
}
return item;
}
QString TraceLineJump::name() const
{
return QStringLiteral("jump at %1 to %2")
.arg(_lineFrom->prettyName())
.arg(_lineTo->prettyName());
}
//---------------------------------------------------
// TraceInstrCall
TraceInstrCall::TraceInstrCall(TraceCall* call, TraceInstr* instr)
: TraceCallListCost(ProfileContext::context(ProfileContext::InstrCall))
{
_call = call;
_instr = instr;
}
TraceInstrCall::~TraceInstrCall()
{
// we are the owner of TracePartInstrCall's generated in our factory
qDeleteAll(_deps);
}
TracePartInstrCall* TraceInstrCall::partInstrCall(TracePart* part,
TracePartCall*)
{
TracePartInstrCall* item = (TracePartInstrCall*) findDepFromPart(part);
if (!item) {
item = new TracePartInstrCall(this);
item->setPosition(part);
addDep(item);
// instruction calls are not registered in function calls
// as together with line calls calls are duplicated
//partCall->addDep(item);
}
return item;
}
QString TraceInstrCall::name() const
{
return QStringLiteral("%1 at %2").arg(_call->name()).arg(_instr->name());
}
//---------------------------------------------------
// TraceLineCall
TraceLineCall::TraceLineCall(TraceCall* call, TraceLine* line)
: TraceCallListCost(ProfileContext::context(ProfileContext::LineCall))
{
_call = call;
_line = line;
}
TraceLineCall::~TraceLineCall()
{
// we are the owner of TracePartLineCall's generated in our factory
qDeleteAll(_deps);
}
TracePartLineCall* TraceLineCall::partLineCall(TracePart* part,
TracePartCall* partCall)
{
TracePartLineCall* item = (TracePartLineCall*) findDepFromPart(part);
if (!item) {
item = new TracePartLineCall(this);
item->setPosition(part);
addDep(item);
partCall->addDep(item);
}
return item;
}
QString TraceLineCall::name() const
{
return QStringLiteral("%1 at %2").arg(_call->name()).arg(_line->name());
}
//---------------------------------------------------
// TraceCall
TraceCall::TraceCall(TraceFunction* caller, TraceFunction* called)
: TraceCallListCost(ProfileContext::context(ProfileContext::Call))
{
_caller = caller;
_called = called;
}
TraceCall::~TraceCall()
{
// we are the owner of all items generated in our factories
qDeleteAll(_deps);
qDeleteAll(_lineCalls);
}
TracePartCall* TraceCall::partCall(TracePart* part,
TracePartFunction* partCaller,
TracePartFunction* partCalling)
{
TracePartCall* item = (TracePartCall*) findDepFromPart(part);
if (!item) {
item = new TracePartCall(this);
item->setPosition(part);
addDep(item);
partCaller->addPartCalling(item);
partCalling->addPartCaller(item);
}
return item;
}
TraceInstrCall* TraceCall::instrCall(TraceInstr* i)
{
foreach(TraceInstrCall* icall, _instrCalls)
if (icall->instr() == i)
return icall;
TraceInstrCall* icall = new TraceInstrCall(this, i);
_instrCalls.append(icall);
invalidate();
#if TRACE_DEBUG
qDebug("Created %s [TraceCall::instrCall]", qPrintable(icall->fullName()));
#endif
i->addInstrCall(icall);
return icall;
}
TraceLineCall* TraceCall::lineCall(TraceLine* l)
{
foreach(TraceLineCall* lcall, _lineCalls)
if (lcall->line() == l)
return lcall;
TraceLineCall* lcall = new TraceLineCall(this, l);
_lineCalls.append(lcall);
invalidate();
#if TRACE_DEBUG
qDebug("Created %s [TraceCall::lineCall]", qPrintable(lcall->fullName()));
#endif
l->addLineCall(lcall);
return lcall;
}
void TraceCall::invalidateDynamicCost()
{
foreach(TraceLineCall* lc, _lineCalls)
lc->invalidate();
foreach(TraceInstrCall* ic, _instrCalls)
ic->invalidate();
invalidate();
}
QString TraceCall::name() const
{
return QStringLiteral("%1 => %2")
.arg(_caller->name())
.arg(_called->name());
}
int TraceCall::inCycle()
{
if (!_caller || !_called) return 0;
if (!_caller->cycle()) return 0;
if (_caller == _caller->cycle()) return 0;
if (_caller->cycle() != _called->cycle()) return 0;
return _caller->cycle()->cycleNo();
}
void TraceCall::update()
{
if (!_dirty) return;
// special handling for cycles
if (_caller && _caller->cycle() && _caller==_caller->cycle()) {
// we have no part calls: use inclusive cost of called function
clear();
if (_called)
addCost(_called->inclusive());
_dirty = false;
return;
}
TraceCallListCost::update();
}
TraceFunction* TraceCall::caller(bool /*skipCycle*/) const
{
return _caller;
}
TraceFunction* TraceCall::called(bool skipCycle) const
{
if (!skipCycle && _called) {
// if this is a call to a cycle member from outside of the cycle,
// fake it to be a call to the whole cycle
if (_called->cycle() && _caller &&
(_caller->cycle() != _called->cycle()))
return _called->cycle();
}
return _called;
}
QString TraceCall::callerName(bool skipCycle) const
{
if (!_caller) return QObject::tr("(no caller)");
if (!skipCycle) {
// if this call goes into a cycle, add the entry function
TraceFunctionCycle* c = _called->cycle();
if (c && _caller && (_caller->cycle() != c)) {
QString via = _called->prettyName();
return QObject::tr("%1 via %2").arg(_caller->prettyName()).arg(via);
}
}
return _caller->prettyName();
}
QString TraceCall::calledName(bool skipCycle) const
{
if (!_called) return QObject::tr("(no callee)");
if (!skipCycle) {
// if this call goes into a cycle, add the entry function
TraceFunctionCycle* c = _called->cycle();
if (c && _caller && (_caller->cycle() != c)) {
// HACK to get rid of cycle postfix...
_called->setCycle(0);
QString via = _called->prettyName();
_called->setCycle(c);
return QObject::tr("%1 via %2").arg(c->name()).arg(via);
}
}
return _called->prettyName();
}
//---------------------------------------------------
// TraceInstr
TraceInstr::TraceInstr()
: TraceListCost(ProfileContext::context(ProfileContext::Instr))
{
_addr = 0;
_line = 0;
_function = 0;
}
TraceInstr::~TraceInstr()
{
// we are the owner of items generated in our factories
qDeleteAll(_deps);
qDeleteAll(_instrJumps);
}
bool TraceInstr::hasCost(EventType* ct)
{
if (subCost(ct) >0)
return true;
foreach(TraceInstrCall* ic, _instrCalls)
if (ic->subCost(ct) >0)
return true;
foreach(TraceInstrJump* ij, _instrJumps)
if (ij->executedCount() >0)
return true;
return false;
}
TracePartInstr* TraceInstr::partInstr(TracePart* part,
TracePartFunction* partFunction)
{
TracePartInstr* item = (TracePartInstr*) findDepFromPart(part);
if (!item) {
item = new TracePartInstr(this);
item->setPosition(part);
addDep(item);
//part->addDep(item);
partFunction->addPartInstr(item);
}
return item;
}
TraceInstrJump* TraceInstr::instrJump(TraceInstr* to, bool isJmpCond)
{
foreach(TraceInstrJump* jump, _instrJumps)
if (jump->instrTo() == to)
return jump;
TraceInstrJump* jump = new TraceInstrJump(this, to, isJmpCond);
_instrJumps.append(jump);
return jump;
}
void TraceInstr::addInstrCall(TraceInstrCall* instrCall)
{
#if TRACE_ASSERTIONS
if (_instrCalls.contains(instrCall)) return;
if (instrCall->instr() != this) {
qDebug("Can not add instruction call to another instruction!");
return;
}
#endif
_instrCalls.append(instrCall);
invalidate();
#if TRACE_DEBUG
qDebug("%s added\n %s (now %d)",
qPrintable( fullName() ),
qPrintable(instrCall->fullName()), _instrCalls.count());
#endif
}
QString TraceInstr::name() const
{
return QStringLiteral("0x%1").arg(_addr.toString());
}
QString TraceInstr::prettyName() const
{
return QStringLiteral("0x%1").arg(_addr.toString());
}
//---------------------------------------------------
// TraceLine
TraceLine::TraceLine()
: TraceListCost(ProfileContext::context(ProfileContext::Line))
{
_lineno = 0;
_sourceFile = 0;
}
TraceLine::~TraceLine()
{
// we are the owner of items generated in our factories
qDeleteAll(_deps);
qDeleteAll(_lineJumps);
}
bool TraceLine::hasCost(EventType* ct)
{
if (subCost(ct) >0)
return true;
foreach(TraceLineCall* lc, _lineCalls)
if (lc->subCost(ct) >0)
return true;
foreach(TraceLineJump* lj, _lineJumps)
if (lj->executedCount() >0)
return true;
return false;
}
TracePartLine* TraceLine::partLine(TracePart* part,
TracePartFunction* partFunction)
{
TracePartLine* item = (TracePartLine*) findDepFromPart(part);
if (!item) {
item = new TracePartLine(this);
item->setPosition(part);
addDep(item);
#if !USE_FIXCOST
part->addDep(item);
#endif
partFunction->addPartLine(item);
}
return item;
}
TraceLineJump* TraceLine::lineJump(TraceLine* to, bool isJmpCond)
{
foreach(TraceLineJump* jump, _lineJumps)
if (jump->lineTo() == to)
return jump;
TraceLineJump* jump = new TraceLineJump(this, to, isJmpCond);
_lineJumps.append(jump);
return jump;
}
void TraceLine::addLineCall(TraceLineCall* lineCall)
{
#if TRACE_ASSERTIONS
if (_lineCalls.contains(lineCall)) return;
if (lineCall->line() != this) {
qDebug("Can not add line call to another line!");
return;
}
#endif
TraceFunction* caller = lineCall->call()->caller();
TraceFunction* function = _sourceFile->function();
if (caller != function) {
// We regard 2 functions as the same if they have
// same class, name, object
if ((caller->cls() != function->cls()) ||
(caller->name() != function->name()) ||
(caller->object() != function->object())) {
qDebug("ERROR: Adding line call, line %d\n of %s to\n %s ?!",
lineCall->line()->lineno(),
qPrintable(caller->info()), qPrintable(function->info()));
}
}
_lineCalls.append(lineCall);
invalidate();
#if TRACE_DEBUG
qDebug("%s added\n %s (now %d)",
qPrintable( fullName() ),
qPrintable(lineCall->fullName()), _lineCalls.count());
#endif
}
QString TraceLine::name() const
{
QString fileShortName = _sourceFile->file()->shortName();
if (fileShortName.isEmpty())
return TraceFile::prettyEmptyName();
return QStringLiteral("%1:%2")
.arg(fileShortName).arg(_lineno);
}
QString TraceLine::prettyName() const
{
return QStringLiteral("%1 [%2]")
.arg(name()).arg(_sourceFile->function()->prettyName());
}
//---------------------------------------------------
// TraceCostItem
TraceCostItem::TraceCostItem(ProfileContext* context)
: TraceInclusiveListCost(context)
{
}
TraceCostItem::~TraceCostItem()
{}
//---------------------------------------------------
// TraceFunctionSource
TraceFunctionSource::TraceFunctionSource(TraceFunction* function,
TraceFile* file)
: ProfileCostArray(ProfileContext::context(ProfileContext::FunctionSource))
{
_file = file;
_function = function;
// the function is dependent from our cost sum
_dep = _function;
_lineMap = 0;
_lineMapFilled = false;
_line0 = 0;
}
TraceFunctionSource::~TraceFunctionSource()
{
delete _lineMap;
delete _line0;
}
QString TraceFunctionSource::name() const
{
return QStringLiteral("%1 for %2").arg(_file->name()).arg(_function->name());
}
uint TraceFunctionSource::firstLineno()
{
// lazy generate the map if not done up to now
TraceLineMap* map = lineMap();
// ignore line 0 here
if (!map || map->count() == 0) return 0;
TraceLineMap::Iterator it = map->begin();
return (*it).lineno();
}
uint TraceFunctionSource::lastLineno()
{
// lazy generate the map if not done up to now
TraceLineMap* map = lineMap();
// ignore line 0 here
if (!map || map->count() == 0) return 0;
TraceLineMap::Iterator it = map->end();
--it;
return (*it).lineno();
}
/* factory */
TraceLine* TraceFunctionSource::line(uint lineno, bool createNew)
{
if (lineno == 0) {
if (!_line0) {
if (!createNew) return 0;
_line0 = new TraceLine;
_line0->setSourceFile(this);
_line0->setLineno(0);
}
return _line0;
}
if (!createNew) {
if (!_lineMap) return 0;
TraceLineMap::Iterator it = _lineMap->find(lineno);
if (it == _lineMap->end()) return 0;
return &(it.value());
}
if (!_lineMap) _lineMap = new TraceLineMap;
TraceLine& l = (*_lineMap)[lineno];
if (!l.isValid()) {
l.setSourceFile(this);
l.setLineno(lineno);
#if TRACE_DEBUG
qDebug("Created %s [TraceFunctionSource::line]",
qPrintable(l.fullName()));
#endif
}
return &l;
}
void TraceFunctionSource::update()
{
if (!_dirty) return;
clear();
// no need to create lineMap if not already created
if (_lineMap) {
TraceLineMap::Iterator lit;
for ( lit = _lineMap->begin();
lit != _lineMap->end(); ++lit )
addCost( &(*lit) );
}
_dirty = false;
}
void TraceFunctionSource::invalidateDynamicCost()
{
// no need to create lineMap if not already created
if (_lineMap) {
TraceLineMap::Iterator lit;
for ( lit = _lineMap->begin();
lit != _lineMap->end(); ++lit )
(*lit).invalidate();
}
invalidate();
}
TraceLineMap* TraceFunctionSource::lineMap()
{
#if USE_FIXCOST
if (_lineMapFilled) return _lineMap;
_lineMapFilled = true;
if (!_lineMap)
_lineMap = new TraceLineMap;
TraceLine* l = 0;
TracePartLine* pl = 0;
TraceLineCall* lc = 0;
TracePartLineCall* plc = 0;
/* go over all part objects for this function, and
* - build TraceLines (the line map) using FixCost objects
* - build TraceJumpLines using FixJump objects
*/
foreach(TraceInclusiveCost* ic, _function->deps()) {
TracePartFunction* pf = (TracePartFunction*) ic;
if (0) qDebug("PartFunction %s:%d",
qPrintable(pf->function()->name()),
pf->part()->partNumber());
FixCost* fc = pf->firstFixCost();
for(; fc; fc = fc->nextCostOfPartFunction()) {
if (fc->line() == 0) continue;
if (fc->functionSource() != this) continue;
if (!l || l->lineno() != fc->line()) {
l = &(*_lineMap)[fc->line()];
if (!l->isValid()) {
l->setSourceFile(this);
l->setLineno(fc->line());
}
pl = 0;
}
if (!pl || pl->part() != fc->part())
pl = l->partLine(fc->part(), pf);
fc->addTo(pl);
}
TraceLine* to = 0;
TraceLineJump* lj;
TracePartLineJump* plj;
FixJump* fj = pf->firstFixJump();
for(; fj; fj = fj->nextJumpOfPartFunction()) {
if (fj->line() == 0) continue;
if (fj->source() != this) continue;
if (!fj->targetSource()) {
// be robust against buggy loaders
continue;
}
// do not display jumps to same or following line
if ((fj->line() == fj->targetLine()) ||
(fj->line()+1 == fj->targetLine())) continue;
if (!l || l->lineno() != fj->line()) {
l = &(*_lineMap)[fj->line()];
if (!l->isValid()) {
l->setSourceFile(this);
l->setLineno(fj->line());
}
}
to = fj->targetSource()->line(fj->targetLine(), true);
lj = l->lineJump(to, fj->isCondJump());
plj = lj->partLineJump(fj->part());
fj->addTo(plj);
}
foreach(TracePartCall* pc, pf->partCallings()) {
if (0) qDebug("PartCall %s:%d",
qPrintable(pc->call()->name()),
pf->part()->partNumber());
FixCallCost* fcc = pc->firstFixCallCost();
for(; fcc; fcc = fcc->nextCostOfPartCall()) {
if (fcc->line() == 0) continue;
if (fcc->functionSource() != this) continue;
if (!l || l->lineno() != fcc->line()) {
l = &(*_lineMap)[fcc->line()];
if (!l->isValid()) {
l->setSourceFile(this);
l->setLineno(fcc->line());
}
}
if (!lc || lc->call() != pc->call() || lc->line() != l) {
lc = pc->call()->lineCall(l);
plc = 0;
}
if (!plc || plc->part() != fcc->part())
plc = lc->partLineCall(fcc->part(), pc);
fcc->addTo(plc);
if (0) qDebug("Add FixCallCost %s:%d/0x%s, CallCount %s",
qPrintable(fcc->functionSource()->file()->shortName()),
fcc->line(), qPrintable(fcc->addr().toString()),
qPrintable(fcc->callCount().pretty()));
}
}
}
#endif
return _lineMap;
}
//---------------------------------------------------
// TraceAssociation
TraceAssociation::TraceAssociation()
{
_function = 0;
_valid = false;
}
TraceAssociation::~TraceAssociation()
{
// do not delete from TraceFunction
if (_function) _function->removeAssociation(this);
}
bool TraceAssociation::isAssociated()
{
if (!_function) return false;
return _function->association(rtti())==this;
}
bool TraceAssociation::setFunction(TraceFunction* f)
{
if (_function == f)
return isAssociated();
if (_function) {
// do not delete ourself
_function->removeAssociation(this);
}
_function = f;
if (f && f->association(rtti()) == 0) {
f->addAssociation(this);
return true;
}
return false;
}
void TraceAssociation::clear(TraceData* d, int rtti)
{
TraceFunctionMap::Iterator it;
for ( it = d->functionMap().begin();
it != d->functionMap().end(); ++it )
(*it).removeAssociation(rtti);
}
void TraceAssociation::invalidate(TraceData* d, int rtti)
{
TraceFunctionMap::Iterator it;
for ( it = d->functionMap().begin();
it != d->functionMap().end(); ++it )
(*it).invalidateAssociation(rtti);
}
//---------------------------------------------------
// TraceFunction
TraceFunction::TraceFunction()
: TraceCostItem(ProfileContext::context(ProfileContext::Function))
{
_object = 0;
_file = 0;
_cls = 0;
_cycle = 0;
_calledCount = 0;
_callingCount = 0;
_calledContexts = 0;
_callingContexts = 0;
_instrMap = 0;
_instrMapFilled = false;
}
TraceFunction::~TraceFunction()
{
qDeleteAll(_associations);
// we are the owner of items generated in our factories
qDeleteAll(_deps);
qDeleteAll(_callings);
qDeleteAll(_sourceFiles);
delete _instrMap;
}
// no unique check is done!
void TraceFunction::addAssociation(TraceAssociation* a)
{
if (!a) return;
_associations.append(a);
}
void TraceFunction::removeAssociation(TraceAssociation* a)
{
_associations.removeAll(a);
}
void TraceFunction::removeAssociation(int rtti, bool reallyDelete)
{
if (rtti==0) {
if (reallyDelete)
qDeleteAll(_associations);
_associations.clear();
return;
}
foreach(TraceAssociation* a, _associations) {
if (a->rtti() == rtti) {
if (reallyDelete) delete a;
_associations.removeAll(a);
return;
}
}
}
void TraceFunction::invalidateAssociation(int rtti)
{
foreach(TraceAssociation* a, _associations) {
if ((rtti==0) || (a->rtti() == rtti))
a->invalidate();
}
}
TraceAssociation* TraceFunction::association(int rtti)
{
foreach(TraceAssociation* a, _associations) {
if (a->rtti() == rtti)
return a;
}
return 0;
}
#if 0
// helper for prettyName
bool TraceFunction::isUniquePrefix(const QString& prefix) const
{
TraceFunctionMap::ConstIterator it, it2;
it = it2 = _myMapIterator;
if (it != data()->functionBeginIterator()) {
it2--;
if ((*it2).name().startsWith(prefix)) return false;
}
if (it != data()->functionEndIterator()) {
it++;
if ((*it).name().startsWith(prefix)) return false;
}
return true;
}
#endif
QString TraceFunction::prettyName() const
{
QString res = _name;
if (_name.isEmpty())
return prettyEmptyName();
if (GlobalConfig::hideTemplates()) {
res = QString();
int d = 0;
for(int i=0;i<_name.length();i++) {
switch(_name[i].toLatin1()) {
case '<':
if (d<=0) res.append(_name[i]);
d++;
break;
case '>':
d--;
// fall trough
default:
if (d<=0) res.append(_name[i]);
break;
}
}
}
#if 0
// TODO: make it a configuration, but disabled by default.
//
// Stripping parameter signature of C++ symbols is fine
// if the function name is unique in the whole program.
// However, we only can detect if it is unique in the profile,
// which makes this "beautification" potentially confusing
int p = _name.indexOf('(');
if (p>0) {
// handle C++ "operator()" correct
if ( (p+2 < _name.size()) && (_name[p+1] == ')') && (_name[p+2] == '(')) p+=2;
// we have a C++ symbol with argument types:
// check for unique function name (inclusive '(' !)
if (isUniquePrefix(_name.left(p+1)))
res = _name.left(p);
}
#endif
// cycle members
if (_cycle) {
if (_cycle != this)
res = QStringLiteral("%1 ").arg(res).arg(_cycle->cycleNo());
else
res = QStringLiteral("").arg(_cycle->cycleNo());
}
return res;
}
QString TraceFunction::formattedName() const
{
// produce a "rich" name only if templates are hidden
if (!GlobalConfig::hideTemplates() || _name.isEmpty()) return QString();
// bold, but inside template parameters normal, function arguments italic
QString rich(QStringLiteral(""));
int d = 0;
for(int i=0;i<_name.length();i++) {
switch(_name[i].toLatin1()) {
case '&':
rich.append("&");
break;
case '<':
d++;
rich.append("<");
if (d==1)
rich.append("");
break;
case '>':
d--;
if (d==0)
rich.append("");
rich.append("> "); // add space to allow for line break
break;
case '(':
rich.append("(");
break;
case ')':
rich.append(")");
break;
default:
rich.append(_name[i]);
break;
}
}
rich.append("");
return rich;
}
QString TraceFunction::prettyEmptyName()
{
return QObject::tr("(unknown)");
}
/*
* Returns location string: ELF object and source file(s).
*/
QString TraceFunction::location(int maxFiles) const
{
QString loc;
// add object file with address range
if (_object) {
loc = _object->shortName();
#if 0
uint from = firstAddress();
uint to = lastAddress();
if (from != 0 && to != 0) {
if (from == to)
loc += QString(" (0x%1)").arg(to, 0, 16);
else
loc += QString(" (0x%1-0x%2)").arg(from, 0, 16).arg(to, 0, 16);
}
#endif
}
// add all source files
int filesAdded = 0;
foreach(TraceFunctionSource* sourceFile, _sourceFiles) {
if (!sourceFile->file() ||
(sourceFile->file()->name().isEmpty()) )
continue;
if (!loc.isEmpty())
loc += (filesAdded>0) ? ", " : ": ";
filesAdded++;
if ((maxFiles>0) && (filesAdded>maxFiles)) {
loc += QLatin1String("...");
break;
}
loc += sourceFile->file()->shortName();
#if 0
from = sourceFile->firstLineno();
to = sourceFile->lastLineno();
if (from != 0 && to != 0) {
if (from == to)
loc += QString(" (%1)").arg(to);
else
loc += QString(" (%1-%2)").arg(from).arg(to);
}
#endif
}
return loc;
}
// pretty version is allowed to mangle the string...
QString TraceFunction::prettyLocation(int maxFiles) const
{
QString l = location(maxFiles);
if (l.isEmpty()) return QObject::tr("(unknown)");
return l;
}
void TraceFunction::addPrettyLocation(QString& s, int maxFiles) const
{
QString l = location(maxFiles);
if (l.isEmpty()) return;
s += QStringLiteral(" (%1)").arg(l);
}
QString TraceFunction::prettyNameWithLocation(int maxFiles) const
{
QString l = location(maxFiles);
if (l.isEmpty()) return prettyName();
return QStringLiteral("%1 (%2)").arg(prettyName()).arg(l);
}
QString TraceFunction::info() const
{
QString l = location();
if (l.isEmpty())
return QStringLiteral("Function %1").arg(name());
return QStringLiteral("Function %1 (location %2)")
.arg(name()).arg(l);
}
Addr TraceFunction::firstAddress() const
{
// ignore address 0 here
if (!_instrMap || _instrMap->count() == 0) return 0;
TraceInstrMap::ConstIterator it = _instrMap->constBegin();
return (*it).addr();
}
Addr TraceFunction::lastAddress() const
{
// ignore address 0 here
if (!_instrMap || _instrMap->count() == 0) return 0;
TraceInstrMap::ConstIterator it = _instrMap->constEnd();
--it;
return (*it).addr();
}
/* factory */
TraceInstr* TraceFunction::instr(Addr addr, bool createNew)
{
// address 0 not allowed
if (addr == Addr(0)) return 0;
if (!createNew) {
if (!_instrMap) return 0;
TraceInstrMap::Iterator it = _instrMap->find(addr);
if (it == _instrMap->end())
return 0;
return &(it.value());
}
if (!_instrMap) _instrMap = new TraceInstrMap;
TraceInstr& i = (*_instrMap)[addr];
if (!i.isValid()) {
i.setAddr(addr);
i.setFunction(this);
#if TRACE_DEBUG
qDebug("Created %s [TraceFunction::instr]",
qPrintable(i.fullName()));
#endif
}
return &i;
}
void TraceFunction::addCaller(TraceCall* caller)
{
#if TRACE_ASSERTIONS
if (caller->called() != this) {
qDebug("Can not add call to another line!\n");
return;
}
if (_callers.contains(caller)) return;
#endif
_callers.append(caller);
invalidate();
#if TRACE_DEBUG
qDebug("%s added Caller\n %s (now %d)",
qPrintable( fullName() ), qPrintable(caller->fullName()), _callers.count());
#endif
}
TraceCall* TraceFunction::calling(TraceFunction* called)
{
foreach(TraceCall* calling, _callings)
if (calling->called() == called)
return calling;
TraceCall* calling = new TraceCall(this, called);
_callings.append(calling);
// we have to invalidate ourself so invalidations from item propagate up
invalidate();
#if TRACE_DEBUG
qDebug("Created %s [TraceFunction::calling]", qPrintable(calling->fullName()));
#endif
called->addCaller(calling);
return calling;
}
TraceFunctionSource* TraceFunction::sourceFile(TraceFile* file,
bool createNew)
{
if (!file) file = _file;
foreach(TraceFunctionSource* sourceFile, _sourceFiles)
if (sourceFile->file() == file)
return sourceFile;
if (!createNew) return 0;
TraceFunctionSource* sourceFile = new TraceFunctionSource(this, file);
_sourceFiles.append(sourceFile);
// we have to invalidate ourself so invalidations from item propagate up
invalidate();
#if TRACE_DEBUG
qDebug("Created SourceFile %s [TraceFunction::line]",
qPrintable(file->name()));
#endif
file->addSourceFile(sourceFile);
return sourceFile;
}
TraceLine* TraceFunction::line(TraceFile* file, uint lineno,
bool createNew)
{
Q_ASSERT(file!=0);
TraceFunctionSource* sf = sourceFile(file, createNew);
if (!sf)
return 0;
else
return sf->line(lineno, createNew);
}
TracePartFunction* TraceFunction::partFunction(TracePart* part,
TracePartFile* partFile,
TracePartObject* partObject)
{
TracePartFunction* item = (TracePartFunction*) findDepFromPart(part);
if (!item) {
item = new TracePartFunction(this, partObject, partFile);
item->setPosition(part);
addDep(item);
#if USE_FIXCOST
part->addDep(item);
#endif
if (_cls) {
TracePartClass* partClass = _cls->partClass(part);
partClass->addPartFunction(item);
item->setPartClass(partClass);
}
partFile->addPartFunction(item);
if (partObject)
partObject->addPartFunction(item);
}
else if (item->partObject()==0 && partObject) {
item->setPartObject(partObject);
partObject->addPartFunction(item);
}
return item;
}
SubCost TraceFunction::calledCount()
{
if (_dirty) update();
return _calledCount;
}
int TraceFunction::calledContexts()
{
if (_dirty) update();
return _calledContexts;
}
SubCost TraceFunction::callingCount()
{
if (_dirty) update();
return _callingCount;
}
int TraceFunction::callingContexts()
{
if (_dirty) update();
return _callingContexts;
}
QString TraceFunction::prettyCalledCount()
{
return _calledCount.pretty();
}
QString TraceFunction::prettyCallingCount()
{
return _callingCount.pretty();
}
TraceCallList TraceFunction::callers(bool skipCycle) const
{
if (skipCycle) return _callers;
// fake the callers for cycle members
if (_cycle && (_cycle != this)) {
TraceCallList l;
// inner-cycle-callers
foreach(TraceCall* c, _callers)
if (c->caller()->cycle() == _cycle)
l.append(c);
// call from cycle itself
foreach(TraceCall* c, _cycle->_callings)
if (c->called() == this) {
l.append(c);
return l;
}
}
return _callers;
}
const TraceCallList& TraceFunction::callings(bool /* skipCycle */) const
{
return _callings;
}
void TraceFunction::invalidateDynamicCost()
{
foreach(TraceCall* c, _callings)
c->invalidateDynamicCost();
foreach(TraceFunctionSource* sf, _sourceFiles)
sf->invalidateDynamicCost();
if (_instrMap) {
TraceInstrMap::Iterator iit;
for ( iit = _instrMap->begin();
iit != _instrMap->end(); ++iit )
(*iit).invalidate();
}
invalidate();
}
void TraceFunction::update()
{
if (!_dirty) return;
#if TRACE_DEBUG
qDebug("Update %s (Callers %d, sourceFiles %d, instrs %d)",
qPrintable(_name), _callers.count(),
_sourceFiles.count(), _instrMap ? _instrMap->count():0);
#endif
_calledCount = 0;
_callingCount = 0;
_calledContexts = 0;
_callingContexts = 0;
clear();
// To calculate context counts, we just use first real event type (FIXME?)
EventType* e = data() ? data()->eventTypes()->realType(0) : 0;
// context count is NOT the sum of part contexts
foreach(TraceCall *caller, _callers) {
if (e && (caller->subCost(e) >0))
_calledContexts++;
_calledCount += caller->callCount();
}
foreach(TraceCall* callee, _callings) {
if (e && (callee->subCost(e) >0))
_callingContexts++;
_callingCount += callee->callCount();
}
if (data()->inFunctionCycleUpdate() || !_cycle) {
// usual case (no cycle member)
foreach(TraceInclusiveCost* item, _deps) {
if (!item->part() || !item->part()->isActive()) continue;
addCost(item);
addInclusive(item->inclusive());
}
}
else {
// this is a cycle or cycle member
foreach(TraceCall* callee, _callings) {
// ignore inner-cycle member calls for inclusive cost
if ((_cycle != this) &&
(callee->inCycle()>0)) continue;
addInclusive(callee);
}
// self cost
if (type() == ProfileContext::FunctionCycle) {
// cycle: self cost is sum of cycle member self costs, but
// does not add to inclusive cost
foreach(TraceFunction* m, ((TraceFunctionCycle*)this)->members())
addCost(m);
}
else {
// cycle member
foreach(TraceInclusiveCost* item, _deps) {
if (!item->part() || !item->part()->isActive()) continue;
addCost(item);
}
_dirty = false; // do not recurse
addInclusive(this);
}
}
_dirty = false;
#if TRACE_DEBUG
qDebug("> %s", qPrintable(costString(0)));
#endif
}
bool TraceFunction::isCycle()
{
return _cycle == this;
}
bool TraceFunction::isCycleMember()
{
return _cycle && (_cycle != this);
}
void TraceFunction::cycleReset()
{
_cycle = 0;
_cycleStackDown = 0;
_cycleLow = 0;
}
// this does not mark functions calling themself !
void TraceFunction::cycleDFS(int d, int& pNo, TraceFunction** pTop)
{
if (_cycleLow != 0) return;
// initialize with prefix order
pNo++;
int prefixNo = pNo;
_cycleLow = prefixNo;
// put myself on stack
_cycleStackDown = *pTop;
*pTop = this;
/* cycle cut heuristic:
* skip calls for cycle detection if they make less than _cycleCut
* percent of the cost of the function.
* FIXME: Which cost type to use for this heuristic ?!
*/
Q_ASSERT((data() != 0) && (data()->eventTypes()->realCount()>0));
EventType* e = data()->eventTypes()->realType(0);
SubCost base = 0;
if (_callers.count()>0) {
foreach(TraceCall* caller, _callers)
if (caller->subCost(e) > base)
base = caller->subCost(e);
}
else base = inclusive()->subCost(e);
SubCost cutLimit = SubCost(base * GlobalConfig::cycleCut());
if (0) {
qDebug("%s (%d) Visiting %s",
qPrintable(QString().fill(' ', d)),
pNo, qPrintable(prettyName()));
qDebug("%s Cum. %s, Max Caller %s, cut limit %s",
qPrintable(QString().fill(' ', d)),
qPrintable(inclusive()->subCost(e).pretty()),
qPrintable(base.pretty()),
qPrintable(cutLimit.pretty()));
}
foreach(TraceCall *callee, _callings) {
TraceFunction* called = callee->called();
// cycle cut heuristic
if (callee->subCost(e) < cutLimit) {
if (0) qDebug("%s Cut call to %s (cum. %s)",
qPrintable(QString().fill(' ', d)),
qPrintable(called->prettyName()),
qPrintable(callee->subCost(e).pretty()));
continue;
}
if (called->_cycleLow==0) {
// not visited yet
called->cycleDFS(d+1, pNo, pTop);
if (called->_cycleLow < _cycleLow)
_cycleLow = called->_cycleLow;
}
else if (called->_cycleStackDown) {
// backlink to same SCC (still in stack)
if (called->_cycleLow < _cycleLow)
_cycleLow = called->_cycleLow;
if (0) qDebug("%s (low %d) Back to %s",
qPrintable(QString().fill(' ', d)),
_cycleLow, qPrintable(called->prettyName()));
}
}
if (prefixNo == _cycleLow) {
// this is the base of a SCC.
if (*pTop == this) {
*pTop = _cycleStackDown;
_cycleStackDown = 0;
}
else {
// a SCC with >1 members
TraceFunctionCycle* cycle = data()->functionCycle(this);
if (0) qDebug("Found Cycle %d with base %s:",
cycle->cycleNo(), qPrintable(prettyName()));
while(*pTop) {
TraceFunction* top = *pTop;
cycle->add(top);
// remove from stack
*pTop = top->_cycleStackDown;
top->_cycleStackDown = 0;
if (0) qDebug(" %s", qPrintable(top->prettyName()));
if (top == this) break;
}
}
}
}
TraceInstrMap* TraceFunction::instrMap()
{
#if USE_FIXCOST
if (_instrMapFilled) return _instrMap;
_instrMapFilled = true;
if (!_instrMap)
_instrMap = new TraceInstrMap;
TraceLine* l = 0;
TraceInstr* i = 0;
TracePartInstr* pi = 0;
TraceInstrCall* ic = 0;
TracePartInstrCall* pic = 0;
foreach(TraceInclusiveCost* icost, deps()) {
TracePartFunction* pf = (TracePartFunction*) icost;
if (0) qDebug("PartFunction %s:%d",
qPrintable(pf->function()->name()),
pf->part()->partNumber());
FixCost* fc = pf->firstFixCost();
for(; fc; fc = fc->nextCostOfPartFunction()) {
if (fc->addr() == 0) continue;
if (!l || (l->lineno() != fc->line()) ||
(l->functionSource() != fc->functionSource()))
l = fc->functionSource()->line(fc->line(),true);
if (!i || i->addr() != fc->addr()) {
i = &(*_instrMap)[fc->addr()];
if (!i->isValid()) {
i->setFunction(this);
i->setAddr(fc->addr());
i->setLine(l);
}
pi = 0;
}
if (!pi || pi->part() != fc->part())
pi = i->partInstr(fc->part(), pf);
fc->addTo(pi);
}
TraceInstr* to = 0;
TraceInstrJump* ij;
TracePartInstrJump* pij;
FixJump* fj = pf->firstFixJump();
for(; fj; fj = fj->nextJumpOfPartFunction()) {
if (fj->addr() == 0) continue;
if (!l || (l->lineno() != fj->line()) ||
(l->functionSource() != fj->source()))
l = fj->source()->line(fj->line(),true);
if (!i || i->addr() != fj->addr()) {
i = &(*_instrMap)[fj->addr()];
if (!i->isValid()) {
i->setFunction(this);
i->setAddr(fj->addr());
i->setLine(l);
}
}
to = fj->targetFunction()->instr(fj->targetAddr(), true);
ij = i->instrJump(to, fj->isCondJump());
pij = ij->partInstrJump(fj->part());
fj->addTo(pij);
}
foreach(TracePartCall* pc, pf->partCallings()) {
if (0) qDebug("PartCall %s:%d",
qPrintable(pc->call()->name()),
pf->part()->partNumber());
FixCallCost* fcc = pc->firstFixCallCost();
for(; fcc; fcc = fcc->nextCostOfPartCall()) {
if (fcc->addr() == 0) continue;
if (!l || (l->lineno() != fcc->line()) ||
(l->functionSource() != fcc->functionSource()))
l = fcc->functionSource()->line(fcc->line(),true);
if (!i || i->addr() != fcc->addr()) {
i = &(*_instrMap)[fcc->addr()];
if (!i->isValid()) {
i->setFunction(this);
i->setAddr(fcc->addr());
i->setLine(l);
}
}
if (!ic || ic->call() != pc->call() || ic->instr() != i) {
ic = pc->call()->instrCall(i);
pic = 0;
}
if (!pic || pic->part() != fcc->part())
pic = ic->partInstrCall(fcc->part(), pc);
fcc->addTo(pic);
if (0) qDebug("Add FixCallCost %s:%d/0x%s, CallCount %s",
qPrintable(fcc->functionSource()->file()->shortName()),
fcc->line(), qPrintable(fcc->addr().toString()),
qPrintable(fcc->callCount().pretty()));
}
}
}
#endif
return _instrMap;
}
//---------------------------------------------------
// TraceFunctionCycle
TraceFunctionCycle::TraceFunctionCycle(TraceFunction* f, int n)
{
_base = f;
_cycleNo = n;
_cycle = this;
setContext(ProfileContext::context(ProfileContext::FunctionCycle));
setPosition(f->data());
setName(QStringLiteral("").arg(n));
// reset to attributes of base function
setFile(_base->file());
setClass(_base->cls());
setObject(_base->object());
}
void TraceFunctionCycle::init()
{
_members.clear();
_callers.clear();
// this deletes all TraceCall's to members
_callings.clear();
invalidate();
}
void TraceFunctionCycle::add(TraceFunction* f)
{
_members.append(f);
}
void TraceFunctionCycle::setup()
{
if (_members.count()==0) return;
foreach(TraceFunction* f, _members) {
// the cycle takes all outside callers from its members
foreach(TraceCall* call, f->callers()) {
if (_members.contains(call->caller())) continue;
_callers.append(call);
}
// the cycle has a call to each member
TraceCall* call = new TraceCall(this, f);
call->invalidate();
_callings.append(call);
// now do some faking...
f->setCycle(this);
}
invalidate();
}
//---------------------------------------------------
// TraceClass
TraceClass::TraceClass()
: TraceCostItem(ProfileContext::context(ProfileContext::Class))
{}
TraceClass::~TraceClass()
{
// we are the owner of items generated in our factory
qDeleteAll(_deps);
}
QString TraceClass::prettyName() const
{
if (_name.isEmpty())
return prettyEmptyName();
return _name;
}
QString TraceClass::prettyEmptyName()
{
return QObject::tr("(global)");
}
TracePartClass* TraceClass::partClass(TracePart* part)
{
TracePartClass* item = (TracePartClass*) findDepFromPart(part);
if (!item) {
item = new TracePartClass(this);
item->setPosition(part);
addDep(item);
}
return item;
}
void TraceClass::addFunction(TraceFunction* function)
{
#if TRACE_ASSERTIONS
if (function->cls() != this) {
qDebug("Can not add function to a class not enclosing this function\n");
return;
}
if (_functions.contains(function)) return;
#endif
_functions.append(function);
invalidate();
#if TRACE_DEBUG
qDebug("%s added\n %s (now %d)",
qPrintable( fullName() ),
qPrintable(function->fullName()), _functions.count());
#endif
}
//---------------------------------------------------
// TraceFile
TraceFile::TraceFile()
: TraceCostItem(ProfileContext::context(ProfileContext::File))
{}
TraceFile::~TraceFile()
{
// we are the owner of items generated in our factory
qDeleteAll(_deps);
}
TracePartFile* TraceFile::partFile(TracePart* part)
{
TracePartFile* item = (TracePartFile*) findDepFromPart(part);
if (!item) {
item = new TracePartFile(this);
item->setPosition(part);
addDep(item);
}
return item;
}
void TraceFile::addFunction(TraceFunction* function)
{
#if TRACE_ASSERTIONS
if (function->file() != this) {
qDebug("Can not add function to a file not enclosing this function\n");
return;
}
if (_functions.contains(function)) return;
#endif
_functions.append(function);
invalidate();
#if TRACE_DEBUG
qDebug("%s added\n %s (now %d)",
qPrintable( fullName() ),
qPrintable(function->fullName()), _functions.count());
#endif
}
void TraceFile::addSourceFile(TraceFunctionSource* sourceFile)
{
#if TRACE_ASSERTIONS
if (sourceFile->file() != this) {
qDebug("Can not add sourceFile to a file not having lines for it\n");
return;
}
#endif
_sourceFiles.append(sourceFile);
// not truly needed, as we do not use the sourceFiles for cost update
invalidate();
#if TRACE_DEBUG
qDebug("%s \n added SourceFile %s (now %d)",
qPrintable( fullName() ), qPrintable(sourceFile->fullName()),
_sourceFiles.count());
#endif
}
void TraceFile::setDirectory(const QString& dir)
{
if (dir.endsWith('/'))
_dir = dir.left(dir.length()-1);
else
_dir = dir;
}
QString TraceFile::directory()
{
if (!_dir.isEmpty()) return _dir;
int lastIndex = 0, index;
while ( (index=_name.indexOf(QStringLiteral("/"), lastIndex)) >=0)
lastIndex = index+1;
if (lastIndex==0) return QString();
// without ending "/"
return _name.left(lastIndex-1);
}
QString TraceFile::shortName() const
{
int lastIndex = 0, index;
while ( (index=_name.indexOf(QStringLiteral("/"), lastIndex)) >=0)
lastIndex = index+1;
return _name.mid(lastIndex);
}
QString TraceFile::prettyName() const
{
QString sn = shortName();
if (sn.isEmpty())
return prettyEmptyName();
return sn;
}
QString TraceFile::prettyEmptyName()
{
return QObject::tr("(unknown)");
}
QString TraceFile::prettyLongName() const
{
if (_name.isEmpty())
return prettyEmptyName();
return _name;
}
//---------------------------------------------------
// TraceObject
TraceObject::TraceObject()
: TraceCostItem(ProfileContext::context(ProfileContext::Object))
{}
TraceObject::~TraceObject()
{
// we are the owner of items generated in our factory
qDeleteAll(_deps);
}
TracePartObject* TraceObject::partObject(TracePart* part)
{
TracePartObject* item = (TracePartObject*) findDepFromPart(part);
if (!item) {
item = new TracePartObject(this);
item->setPosition(part);
addDep(item);
}
return item;
}
void TraceObject::addFunction(TraceFunction* function)
{
#if TRACE_ASSERTIONS
if (function->object() != this) {
qDebug("Can not add function to an object not enclosing this function\n");
return;
}
if (_functions.contains(function)) return;
#endif
_functions.append(function);
invalidate();
#if TRACE_DEBUG
qDebug("%s added\n %s (now %d)",
qPrintable( fullName() ),
qPrintable(function->fullName()), _functions.count());
#endif
}
void TraceObject::setDirectory(const QString& dir)
{
if (dir.endsWith('/'))
_dir = dir.left(dir.length()-1);
else
_dir = dir;
}
QString TraceObject::directory()
{
if (!_dir.isEmpty()) return _dir;
int lastIndex = 0, index;
while ( (index=_name.indexOf(QStringLiteral("/"), lastIndex)) >=0)
lastIndex = index+1;
if (lastIndex==0) return QString();
// without ending "/"
return _name.left(lastIndex-1);
}
QString TraceObject::shortName() const
{
int lastIndex = 0, index;
while ( (index=_name.indexOf(QStringLiteral("/"), lastIndex)) >=0)
lastIndex = index+1;
return _name.mid(lastIndex);
}
QString TraceObject::prettyName() const
{
QString sn = shortName();
if (sn.isEmpty())
return prettyEmptyName();
return sn;
}
QString TraceObject::prettyEmptyName()
{
return QObject::tr("(unknown)");
}
//---------------------------------------------------
// TracePart
TracePart::TracePart(TraceData* data)
: TraceListCost(ProfileContext::context(ProfileContext::Part))
{
setPosition(data);
_dep = data;
_active = true;
_number = 0;
_tid = 0;
_pid = 0;
_eventTypeMapping = 0;
}
TracePart::~TracePart()
{
delete _eventTypeMapping;
}
void TracePart::setPartNumber(int n)
{
if (data()->maxPartNumber() setMaxPartNumber(n);
_number = n;
}
void TracePart::setThreadID(int tid)
{
if (data()->maxThreadID() setMaxThreadID(tid);
_tid = tid;
}
void TracePart::setProcessID(int pid)
{
_pid = pid;
}
// strip path
QString TracePart::shortName() const
{
int lastIndex = 0, index;
while ( (index=_name.indexOf(QStringLiteral("/"), lastIndex)) >=0)
lastIndex = index+1;
return _name.mid(lastIndex);
}
QString TracePart::prettyName() const
{
if (_pid==0) return shortName();
QString name = QStringLiteral("PID %1").arg(_pid);
if (_number>0)
name += QStringLiteral(", section %2").arg(_number);
if ((data()->maxThreadID()>1) && (_tid>0))
name += QStringLiteral(", thread %3").arg(_tid);
return name;
}
bool TracePart::activate(bool active)
{
if (_active == active) return false;
_active = active;
// to be done by the client of this function
// data()->invalidateDynamicCost();
// So better use the TraceData functions...
return true;
}
bool TracePart::operator<(const TracePart& p2) const
{
if (processID() < p2.processID()) return true;
if (processID() == p2.processID()) {
if (partNumber() < p2.partNumber()) return true;
if (partNumber() == p2.partNumber())
return (threadID() < p2.threadID());
}
return false;
}
//---------------------------------------------------
// TraceData
// create vectors with reasonable default sizes, but not wasting memory
TraceData::TraceData(Logger* l)
: ProfileCostArray(ProfileContext::context(ProfileContext::Data))
{
_logger = l;
init();
}
void TraceData::init()
{
_functionCycleCount = 0;
_inFunctionCycleUpdate = false;
_maxThreadID = 0;
_maxPartNumber = 0;
_fixPool = 0;
_dynPool = 0;
_arch = ArchUnknown;
}
TraceData::~TraceData()
{
qDeleteAll(_parts);
delete _fixPool;
delete _dynPool;
}
QString TraceData::shortTraceName() const
{
int lastIndex = 0, index;
while ( (index=_traceName.indexOf(QStringLiteral("/"), lastIndex)) >=0)
lastIndex = index+1;
return _traceName.mid(lastIndex);
}
FixPool* TraceData::fixPool()
{
if (!_fixPool)
_fixPool = new FixPool();
return _fixPool;
}
DynPool* TraceData::dynPool()
{
if (!_dynPool)
_dynPool = new DynPool();
return _dynPool;
}
bool partLessThan(const TracePart* p1, const TracePart* p2)
{
return *p1 < *p2;
}
/**
* Load a list of files.
* If only one file is given, it is assumed to be a prefix, and all
* existing files with that prefix are loaded.
*
* Returns 0 if nothing found to load
*/
int TraceData::load(QStringList files)
{
if (files.isEmpty()) return 0;
_traceName = files[0];
if (files.count() == 1) {
QFileInfo finfo(_traceName);
QString prefix = finfo.fileName();
QDir dir = finfo.dir();
if (finfo.isDir()) {
prefix = QStringLiteral("callgrind.out");
_traceName += QLatin1String("/callgrind.out");
}
files = dir.entryList(QStringList() << prefix + "*", QDir::Files);
QStringList::Iterator it = files.begin();
for (; it != files.end(); ++it ) {
*it = dir.path() + "/" + *it;
}
}
if (files.isEmpty()) {
_traceName += ' ' + QObject::tr("(not found)");
return 0;
}
QStringList::const_iterator it;
int partsLoaded = 0;
for (it = files.constBegin(); it != files.constEnd(); ++it ) {
QFile file(*it);
partsLoaded += internalLoad(&file, *it);
}
if (partsLoaded == 0) return 0;
qSort(_parts.begin(), _parts.end(), partLessThan);
invalidateDynamicCost();
updateFunctionCycles();
return partsLoaded;
}
int TraceData::load(QString file)
{
return load(QStringList(file));
}
int TraceData::load(QIODevice* file, const QString& filename)
{
_traceName = filename;
int partsLoaded = internalLoad(file, filename);
if (partsLoaded>0) {
invalidateDynamicCost();
updateFunctionCycles();
}
return partsLoaded;
}
int TraceData::internalLoad(QIODevice* device, const QString& filename)
{
if (!device->open( QIODevice::ReadOnly ) ) {
_logger->loadStart(filename);
- _logger->loadFinished(QString(strerror( errno )));
+ _logger->loadFinished(QString::fromLocal8Bit(strerror( errno )));
return 0;
}
Loader* l = Loader::matchingLoader(device);
if (!l) {
// special case emtpy file: ignore...
if (device->size() == 0) return 0;
_logger->loadStart(filename);
_logger->loadFinished(QStringLiteral("Unknown file format"));
return 0;
}
l->setLogger(_logger);
int partsLoaded = l->load(this, device, filename);
l->setLogger(0);
return partsLoaded;
}
bool TraceData::activateParts(const TracePartList& l)
{
bool changed = false;
foreach(TracePart* part, _parts)
if (part->activate(l.contains(part)))
changed = true;
if (changed) {
// because active parts have changed, throw away calculated
// costs...
invalidateDynamicCost();
updateFunctionCycles();
}
return changed;
}
bool TraceData::activateParts(TracePartList l, bool active)
{
bool changed = false;
foreach(TracePart* part, l) {
if (_parts.contains(part))
if (part->activate(active))
changed = true;
}
if (changed) {
invalidateDynamicCost();
updateFunctionCycles();
}
return changed;
}
bool TraceData::activatePart(TracePart* p, bool active)
{
return p->activate(active);
}
bool TraceData::activateAll(bool active)
{
return activateParts(_parts, active);
}
void TraceData::addPart(TracePart* part)
{
if (_parts.contains(part)>0) return;
if ((part->partNumber()==0) &&
(part->processID()==0)) {
_maxPartNumber++;
part->setPartNumber(_maxPartNumber);
}
_parts.append(part);
}
TracePart* TraceData::partWithName(const QString& name)
{
foreach(TracePart* part, _parts)
if (part->name() == name)
return part;
return 0;
}
QString TraceData::activePartRange()
{
QString res;
int r1=-1, r2=-1, count=0;
foreach(TracePart* part, _parts) {
count++;
if (part->isActive()) {
if (r1<0) { r1 = r2 = count; }
else if (r2 == count-1) { r2 = count; }
else {
if (!res.isEmpty()) res += ';';
if (r1==r2) res += QString::number(r1);
else res += QStringLiteral("%1-%2").arg(r1).arg(r2);
r1 = r2 = count;
}
}
}
if (r1>=0) {
if (!res.isEmpty()) res += ';';
if (r1==r2) res += QString::number(r1);
else res += QStringLiteral("%1-%2").arg(r1).arg(r2);
}
return res;
}
void TraceData::invalidateDynamicCost()
{
// invalidate all dynamic costs
TraceObjectMap::Iterator oit;
for ( oit = _objectMap.begin();
oit != _objectMap.end(); ++oit )
(*oit).invalidate();
TraceClassMap::Iterator cit;
for ( cit = _classMap.begin();
cit != _classMap.end(); ++cit )
(*cit).invalidate();
TraceFileMap::Iterator fit;
for ( fit = _fileMap.begin();
fit != _fileMap.end(); ++fit )
(*fit).invalidate();
TraceFunctionMap::Iterator it;
for ( it = _functionMap.begin();
it != _functionMap.end(); ++it ) {
(*it).invalidateDynamicCost();
}
invalidate();
}
TraceObject* TraceData::object(const QString& name)
{
TraceObject& o = _objectMap[name];
if (!o.data()) {
// was created
o.setPosition(this);
o.setName(name);
#if TRACE_DEBUG
qDebug("Created %s [TraceData::object]",
qPrintable(o.fullName()));
#endif
}
return &o;
}
TraceFile* TraceData::file(const QString& name)
{
TraceFile& f = _fileMap[name];
if (!f.data()) {
// was created
f.setPosition(this);
f.setName(name);
#if TRACE_DEBUG
qDebug("Created %s [TraceData::file]",
qPrintable(f.fullName()));
#endif
}
return &f;
}
// usually only called by function()
TraceClass* TraceData::cls(const QString& fnName, QString& shortName)
{
int lastIndex = 0, index, pIndex;
// we ignore any "::" after a '(' or a space
pIndex=fnName.indexOf('(', 0);
#if 0
int sIndex=fnName.find(" ", 0);
if (sIndex>=0)
if ((pIndex == -1) || (sIndex < pIndex))
pIndex = sIndex;
#endif
while ((index=fnName.indexOf(QStringLiteral("::"), lastIndex)) >=0) {
if (pIndex>=0 && pIndexshortName() + object->shortName();
TraceFunctionMap::Iterator it;
it = _functionMap.find(key);
if (it == _functionMap.end()) {
it = _functionMap.insert(key, TraceFunction());
TraceFunction& f = it.value();
f.setPosition(this);
f.setName(name);
f.setClass(c);
f.setObject(object);
f.setFile(file);
//f.setMapIterator(it);
#if TRACE_DEBUG
qDebug("Created %s [TraceData::function]\n for %s, %s, %s",
qPrintable(f.fullName()),
qPrintable(c->fullName()), qPrintable(file->fullName()),
object ? qPrintable(object->fullName()) : "(unknown object)");
#endif
c->addFunction(&f);
object->addFunction(&f);
file->addFunction(&f);
}
return &(it.value());
}
TraceFunctionMap::Iterator TraceData::functionIterator(TraceFunction* f)
{
// IMPORTANT: build as SAME key as used in function() above !!
QString key;
if (f->cls()) key = f->cls()->name() + "::";
key += f->name();
key += f->object()->shortName();
return _functionMap.find(key);
}
TraceFunctionMap::ConstIterator TraceData::functionBeginIterator() const
{
return _functionMap.begin();
}
TraceFunctionMap::ConstIterator TraceData::functionEndIterator() const
{
return _functionMap.end();
}
// _maxCallCount is maintained globally, and only updated at loading
void TraceData::updateMaxCallCount(SubCost c)
{
if (_maxCallCount < c)
_maxCallCount = c;
}
void TraceData::resetSourceDirs()
{
TraceFileMap::Iterator fit;
for ( fit = _fileMap.begin();
fit != _fileMap.end(); ++fit )
(*fit).resetDirectory();
}
void TraceData::update()
{
if (!_dirty) return;
clear();
_totals.clear();
foreach(TracePart* part, _parts) {
_totals.addCost(part->totals());
if (part->isActive())
addCost(part->totals());
}
_dirty = false;
}
ProfileCostArray* TraceData::search(ProfileContext::Type t, QString name,
EventType* ct, ProfileCostArray* parent)
{
ProfileCostArray* result = 0;
ProfileContext::Type pt;
SubCost sc, scTop = 0;
pt = parent ? parent->type() : ProfileContext::InvalidType;
switch(t) {
case ProfileContext::Function:
{
TraceFunction *f;
TraceFunctionMap::Iterator it;
for ( it = _functionMap.begin();
it != _functionMap.end(); ++it ) {
f = &(*it);
if (f->name() != name) continue;
if ((pt == ProfileContext::Class) && (parent != f->cls())) continue;
if ((pt == ProfileContext::File) && (parent != f->file())) continue;
if ((pt == ProfileContext::Object) && (parent != f->object())) continue;
if (ct) {
sc = f->inclusive()->subCost(ct);
if (sc <= scTop) continue;
scTop = sc;
}
result = f;
}
}
break;
case ProfileContext::File:
{
TraceFile *f;
TraceFileMap::Iterator it;
for ( it = _fileMap.begin();
it != _fileMap.end(); ++it ) {
f = &(*it);
if (f->name() != name) continue;
if (ct) {
sc = f->subCost(ct);
if (sc <= scTop) continue;
scTop = sc;
}
result = f;
}
}
break;
case ProfileContext::Class:
{
TraceClass *c;
TraceClassMap::Iterator it;
for ( it = _classMap.begin();
it != _classMap.end(); ++it ) {
c = &(*it);
if (c->name() != name) continue;
if (ct) {
sc = c->subCost(ct);
if (sc <= scTop) continue;
scTop = sc;
}
result = c;
}
}
break;
case ProfileContext::Object:
{
TraceObject *o;
TraceObjectMap::Iterator it;
for ( it = _objectMap.begin();
it != _objectMap.end(); ++it ) {
o = &(*it);
if (o->name() != name) continue;
if (ct) {
sc = o->subCost(ct);
if (sc <= scTop) continue;
scTop = sc;
}
result = o;
}
}
break;
case ProfileContext::Instr:
if (pt == ProfileContext::Function) {
TraceInstrMap* instrMap = ((TraceFunction*)parent)->instrMap();
if (!instrMap) break;
TraceInstr *instr;
TraceInstrMap::Iterator it;
for ( it = instrMap->begin();
it != instrMap->end(); ++it ) {
instr = &(*it);
if (instr->name() != name) continue;
result = instr;
}
}
break;
case ProfileContext::Line:
{
TraceFunctionSourceList sList;
if (pt == ProfileContext::Function)
sList = ((TraceFunction*)parent)->sourceFiles();
else if (pt == ProfileContext::FunctionSource)
sList.append((TraceFunctionSource*) parent);
else break;
TraceLineMap* lineMap;
TraceLine* line;
TraceLineMap::Iterator it;
foreach(TraceFunctionSource* fs, sList) {
lineMap = fs->lineMap();
if (!lineMap) continue;
for ( it = lineMap->begin();
it != lineMap->end(); ++it ) {
line = &(*it);
if (line->name() != name) continue;
result = line;
}
}
}
break;
default:
break;
}
return result;
}
TraceFunctionCycle* TraceData::functionCycle(TraceFunction* f)
{
TraceFunctionCycle* cycle;
foreach(cycle, _functionCycles)
if (cycle->base() == f)
return cycle;
_functionCycleCount++;
cycle = new TraceFunctionCycle(f, _functionCycleCount);
_functionCycles.append(cycle);
return cycle;
}
void TraceData::updateFunctionCycles()
{
//qDebug("Updating cycles...");
// init cycle info
foreach(TraceFunctionCycle* cycle, _functionCycles)
cycle->init();
TraceFunctionMap::Iterator it;
for ( it = _functionMap.begin(); it != _functionMap.end(); ++it )
(*it).cycleReset();
if (!GlobalConfig::showCycles()) return;
_inFunctionCycleUpdate = true;
#if 0
int fCount = _functionMap.size(), fNo = 0, progress=0, p;
QString msg = tr("Recalculating Function Cycles...");
if (_topLevel) _topLevel->showStatus(msg,0);
#endif
// DFS and collapse strong connected components (Tarjan)
int pNo = 0;
TraceFunction* stackTop;
for ( it = _functionMap.begin(); it != _functionMap.end(); ++it ) {
#if 0
if (_topLevel) {
fNo++;
p = 100*fNo/fCount;
if (p> progress) {
progress = p;
_topLevel->showStatus(msg, p);
}
}
#endif
stackTop = 0;
(*it).cycleDFS(1, pNo, &stackTop);
}
// postprocess cycles
foreach(TraceFunctionCycle* cycle, _functionCycles)
cycle->setup();
_inFunctionCycleUpdate = false;
// we have to invalidate costs because cycles are now taken into account
invalidateDynamicCost();
#if 0
if (0) if (_topLevel) _topLevel->showStatus(QString(), 0);
#endif
}
void TraceData::updateObjectCycles()
{
}
void TraceData::updateClassCycles()
{
}
void TraceData::updateFileCycles()
{
}
diff --git a/libviews/callgraphview.cpp b/libviews/callgraphview.cpp
index 3d6b5ba..1b967d0 100644
--- a/libviews/callgraphview.cpp
+++ b/libviews/callgraphview.cpp
@@ -1,3189 +1,3189 @@
/* This file is part of KCachegrind.
Copyright (c) 2007-2016 Josef Weidendorfer
KCachegrind 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, version 2.
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
/*
* Callgraph View
*/
#include "callgraphview.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "config.h"
#include "globalguiconfig.h"
#include "listutils.h"
#define DEBUG_GRAPH 0
// CallGraphView defaults
#define DEFAULT_FUNCLIMIT .05
#define DEFAULT_CALLLIMIT 1.
#define DEFAULT_MAXCALLER 2
#define DEFAULT_MAXCALLEE -1
#define DEFAULT_SHOWSKIPPED false
#define DEFAULT_EXPANDCYCLES false
#define DEFAULT_CLUSTERGROUPS false
#define DEFAULT_DETAILLEVEL 1
#define DEFAULT_LAYOUT GraphOptions::TopDown
#define DEFAULT_ZOOMPOS Auto
// LessThen functors as helpers for sorting of graph edges
// for keyboard navigation. Sorting is done according to
// the angle at which a edge spline goes out or in of a function.
// Sort angles of outgoing edges (edge seen as attached to the caller)
class CallerGraphEdgeLessThan
{
public:
bool operator()(const GraphEdge* ge1, const GraphEdge* ge2) const
{
const CanvasEdge* ce1 = ge1->canvasEdge();
const CanvasEdge* ce2 = ge2->canvasEdge();
// sort invisible edges (ie. without matching CanvasEdge) in front
if (!ce1) return true;
if (!ce2) return false;
QPolygon p1 = ce1->controlPoints();
QPolygon p2 = ce2->controlPoints();
QPoint d1 = p1.point(1) - p1.point(0);
QPoint d2 = p2.point(1) - p2.point(0);
double angle1 = atan2(double(d1.y()), double(d1.x()));
double angle2 = atan2(double(d2.y()), double(d2.x()));
return (angle1 < angle2);
}
};
// Sort angles of ingoing edges (edge seen as attached to the callee)
class CalleeGraphEdgeLessThan
{
public:
bool operator()(const GraphEdge* ge1, const GraphEdge* ge2) const
{
const CanvasEdge* ce1 = ge1->canvasEdge();
const CanvasEdge* ce2 = ge2->canvasEdge();
// sort invisible edges (ie. without matching CanvasEdge) in front
if (!ce1) return true;
if (!ce2) return false;
QPolygon p1 = ce1->controlPoints();
QPolygon p2 = ce2->controlPoints();
QPoint d1 = p1.point(p1.count()-2) - p1.point(p1.count()-1);
QPoint d2 = p2.point(p2.count()-2) - p2.point(p2.count()-1);
double angle1 = atan2(double(d1.y()), double(d1.x()));
double angle2 = atan2(double(d2.y()), double(d2.x()));
// for ingoing edges sort according to descending angles
return (angle2 < angle1);
}
};
//
// GraphNode
//
GraphNode::GraphNode()
{
_f=0;
self = incl = 0;
_cn = 0;
_visible = false;
_lastCallerIndex = _lastCalleeIndex = -1;
_lastFromCaller = true;
}
void GraphNode::clearEdges()
{
callees.clear();
callers.clear();
}
CallerGraphEdgeLessThan callerGraphEdgeLessThan;
CalleeGraphEdgeLessThan calleeGraphEdgeLessThan;
void GraphNode::sortEdges()
{
qSort(callers.begin(), callers.end(), callerGraphEdgeLessThan);
qSort(callees.begin(), callees.end(), calleeGraphEdgeLessThan);
}
void GraphNode::addCallee(GraphEdge* e)
{
if (e)
callees.append(e);
}
void GraphNode::addCaller(GraphEdge* e)
{
if (e)
callers.append(e);
}
void GraphNode::addUniqueCallee(GraphEdge* e)
{
if (e && (callees.count(e) == 0))
callees.append(e);
}
void GraphNode::addUniqueCaller(GraphEdge* e)
{
if (e && (callers.count(e) == 0))
callers.append(e);
}
void GraphNode::removeEdge(GraphEdge* e)
{
callers.removeAll(e);
callees.removeAll(e);
}
double GraphNode::calleeCostSum()
{
double sum = 0.0;
foreach(GraphEdge* e, callees)
sum += e->cost;
return sum;
}
double GraphNode::calleeCountSum()
{
double sum = 0.0;
foreach(GraphEdge* e, callees)
sum += e->count;
return sum;
}
double GraphNode::callerCostSum()
{
double sum = 0.0;
foreach(GraphEdge* e, callers)
sum += e->cost;
return sum;
}
double GraphNode::callerCountSum()
{
double sum = 0.0;
foreach(GraphEdge* e, callers)
sum += e->count;
return sum;
}
TraceCall* GraphNode::visibleCaller()
{
if (0)
qDebug("GraphNode::visibleCaller %s: last %d, count %d",
qPrintable(_f->prettyName()), _lastCallerIndex, callers.count());
// can not use at(): index can be -1 (out of bounds), result is 0 then
GraphEdge* e = callers.value(_lastCallerIndex);
if (e && !e->isVisible())
e = 0;
if (!e) {
double maxCost = 0.0;
GraphEdge* maxEdge = 0;
for(int i = 0; iisVisible() && (e->cost > maxCost)) {
maxCost = e->cost;
maxEdge = e;
_lastCallerIndex = i;
}
}
e = maxEdge;
}
return e ? e->call() : 0;
}
TraceCall* GraphNode::visibleCallee()
{
if (0)
qDebug("GraphNode::visibleCallee %s: last %d, count %d",
qPrintable(_f->prettyName()), _lastCalleeIndex, callees.count());
GraphEdge* e = callees.value(_lastCalleeIndex);
if (e && !e->isVisible())
e = 0;
if (!e) {
double maxCost = 0.0;
GraphEdge* maxEdge = 0;
for(int i = 0; iisVisible() && (e->cost > maxCost)) {
maxCost = e->cost;
maxEdge = e;
_lastCalleeIndex = i;
}
}
e = maxEdge;
}
return e ? e->call() : 0;
}
void GraphNode::setCallee(GraphEdge* e)
{
_lastCalleeIndex = callees.indexOf(e);
_lastFromCaller = false;
}
void GraphNode::setCaller(GraphEdge* e)
{
_lastCallerIndex = callers.indexOf(e);
_lastFromCaller = true;
}
TraceFunction* GraphNode::nextVisible()
{
TraceCall* c;
if (_lastFromCaller) {
c = nextVisibleCaller();
if (c)
return c->called(true);
c = nextVisibleCallee();
if (c)
return c->caller(true);
} else {
c = nextVisibleCallee();
if (c)
return c->caller(true);
c = nextVisibleCaller();
if (c)
return c->called(true);
}
return 0;
}
TraceFunction* GraphNode::priorVisible()
{
TraceCall* c;
if (_lastFromCaller) {
c = priorVisibleCaller();
if (c)
return c->called(true);
c = priorVisibleCallee();
if (c)
return c->caller(true);
} else {
c = priorVisibleCallee();
if (c)
return c->caller(true);
c = priorVisibleCaller();
if (c)
return c->called(true);
}
return 0;
}
TraceCall* GraphNode::nextVisibleCaller(GraphEdge* e)
{
int idx = e ? callers.indexOf(e) : _lastCallerIndex;
idx++;
while(idx < callers.size()) {
if (callers[idx]->isVisible()) {
_lastCallerIndex = idx;
return callers[idx]->call();
}
idx++;
}
return 0;
}
TraceCall* GraphNode::nextVisibleCallee(GraphEdge* e)
{
int idx = e ? callees.indexOf(e) : _lastCalleeIndex;
idx++;
while(idx < callees.size()) {
if (callees[idx]->isVisible()) {
_lastCalleeIndex = idx;
return callees[idx]->call();
}
idx++;
}
return 0;
}
TraceCall* GraphNode::priorVisibleCaller(GraphEdge* e)
{
int idx = e ? callers.indexOf(e) : _lastCallerIndex;
idx = (idx<0) ? callers.size()-1 : idx-1;
while(idx >= 0) {
if (callers[idx]->isVisible()) {
_lastCallerIndex = idx;
return callers[idx]->call();
}
idx--;
}
return 0;
}
TraceCall* GraphNode::priorVisibleCallee(GraphEdge* e)
{
int idx = e ? callees.indexOf(e) : _lastCalleeIndex;
idx = (idx<0) ? callees.size()-1 : idx-1;
while(idx >= 0) {
if (callees[idx]->isVisible()) {
_lastCalleeIndex = idx;
return callees[idx]->call();
}
idx--;
}
return 0;
}
//
// GraphEdge
//
GraphEdge::GraphEdge()
{
_c=0;
_from = _to = 0;
_fromNode = _toNode = 0;
cost = count = 0;
_ce = 0;
_visible = false;
_lastFromCaller = true;
}
QString GraphEdge::prettyName()
{
if (_c)
return _c->prettyName();
if (_from)
return QObject::tr("Call(s) from %1").arg(_from->prettyName());
if (_to)
return QObject::tr("Call(s) to %1").arg(_to->prettyName());
return QObject::tr("(unknown call)");
}
TraceFunction* GraphEdge::visibleCaller()
{
if (_from) {
_lastFromCaller = true;
if (_fromNode)
_fromNode->setCallee(this);
return _from;
}
return 0;
}
TraceFunction* GraphEdge::visibleCallee()
{
if (_to) {
_lastFromCaller = false;
if (_toNode)
_toNode->setCaller(this);
return _to;
}
return 0;
}
TraceCall* GraphEdge::nextVisible()
{
TraceCall* res = 0;
if (_lastFromCaller && _fromNode) {
res = _fromNode->nextVisibleCallee(this);
if (!res && _toNode)
res = _toNode->nextVisibleCaller(this);
} else if (_toNode) {
res = _toNode->nextVisibleCaller(this);
if (!res && _fromNode)
res = _fromNode->nextVisibleCallee(this);
}
return res;
}
TraceCall* GraphEdge::priorVisible()
{
TraceCall* res = 0;
if (_lastFromCaller && _fromNode) {
res = _fromNode->priorVisibleCallee(this);
if (!res && _toNode)
res = _toNode->priorVisibleCaller(this);
} else if (_toNode) {
res = _toNode->priorVisibleCaller(this);
if (!res && _fromNode)
res = _fromNode->priorVisibleCallee(this);
}
return res;
}
//
// GraphOptions
//
QString GraphOptions::layoutString(Layout l)
{
if (l == Circular)
return QStringLiteral("Circular");
if (l == LeftRight)
return QStringLiteral("LeftRight");
return QStringLiteral("TopDown");
}
GraphOptions::Layout GraphOptions::layout(QString s)
{
if (s == QStringLiteral("Circular"))
return Circular;
if (s == QStringLiteral("LeftRight"))
return LeftRight;
return TopDown;
}
//
// StorableGraphOptions
//
StorableGraphOptions::StorableGraphOptions()
{
// default options
_funcLimit = DEFAULT_FUNCLIMIT;
_callLimit = DEFAULT_CALLLIMIT;
_maxCallerDepth = DEFAULT_MAXCALLER;
_maxCalleeDepth = DEFAULT_MAXCALLEE;
_showSkipped = DEFAULT_SHOWSKIPPED;
_expandCycles = DEFAULT_EXPANDCYCLES;
_detailLevel = DEFAULT_DETAILLEVEL;
_layout = DEFAULT_LAYOUT;
}
//
// GraphExporter
//
GraphExporter::GraphExporter()
{
_go = this;
_tmpFile = 0;
_item = 0;
reset(0, 0, 0, ProfileContext::InvalidType, QString());
}
GraphExporter::GraphExporter(TraceData* d, TraceFunction* f,
EventType* ct, ProfileContext::Type gt,
QString filename)
{
_go = this;
_tmpFile = 0;
_item = 0;
reset(d, f, ct, gt, filename);
}
GraphExporter::~GraphExporter()
{
if (_item && _tmpFile) {
#if DEBUG_GRAPH
_tmpFile->setAutoRemove(true);
#endif
delete _tmpFile;
}
}
void GraphExporter::reset(TraceData*, CostItem* i, EventType* ct,
ProfileContext::Type gt, QString filename)
{
_graphCreated = false;
_nodeMap.clear();
_edgeMap.clear();
if (_item && _tmpFile) {
_tmpFile->setAutoRemove(true);
delete _tmpFile;
}
if (i) {
switch (i->type()) {
case ProfileContext::Function:
case ProfileContext::FunctionCycle:
case ProfileContext::Call:
break;
default:
i = 0;
}
}
_item = i;
_eventType = ct;
_groupType = gt;
if (!i)
return;
if (filename.isEmpty()) {
_tmpFile = new QTemporaryFile();
//_tmpFile->setSuffix(".dot");
_tmpFile->setAutoRemove(false);
_tmpFile->open();
_dotName = _tmpFile->fileName();
_useBox = true;
} else {
_tmpFile = 0;
_dotName = filename;
_useBox = false;
}
}
void GraphExporter::setGraphOptions(GraphOptions* go)
{
if (go == 0)
go = this;
_go = go;
}
void GraphExporter::createGraph()
{
if (!_item)
return;
if (_graphCreated)
return;
_graphCreated = true;
if ((_item->type() == ProfileContext::Function) ||(_item->type()
== ProfileContext::FunctionCycle)) {
TraceFunction* f = (TraceFunction*) _item;
double incl = f->inclusive()->subCost(_eventType);
_realFuncLimit = incl * _go->funcLimit();
_realCallLimit = _realFuncLimit * _go->callLimit();
buildGraph(f, 0, true, 1.0); // down to callees
// set costs of function back to 0, as it will be added again
GraphNode& n = _nodeMap[f];
n.self = n.incl = 0.0;
buildGraph(f, 0, false, 1.0); // up to callers
} else {
TraceCall* c = (TraceCall*) _item;
double incl = c->subCost(_eventType);
_realFuncLimit = incl * _go->funcLimit();
_realCallLimit = _realFuncLimit * _go->callLimit();
// create edge
TraceFunction *caller, *called;
caller = c->caller(false);
called = c->called(false);
QPair p(caller, called);
GraphEdge& e = _edgeMap[p];
e.setCall(c);
e.setCaller(p.first);
e.setCallee(p.second);
e.cost = c->subCost(_eventType);
e.count = c->callCount();
SubCost s = called->inclusive()->subCost(_eventType);
buildGraph(called, 0, true, e.cost / s); // down to callees
s = caller->inclusive()->subCost(_eventType);
buildGraph(caller, 0, false, e.cost / s); // up to callers
}
}
void GraphExporter::writeDot(QIODevice* device)
{
if (!_item)
return;
QFile* file = 0;
QTextStream* stream = 0;
if (device)
stream = new QTextStream(device);
else {
if (_tmpFile)
stream = new QTextStream(_tmpFile);
else {
file = new QFile(_dotName);
if ( !file->open(QIODevice::WriteOnly ) ) {
qDebug() << "Can not write dot file '"<< _dotName << "'";
delete file;
return;
}
stream = new QTextStream(file);
}
}
if (!_graphCreated)
createGraph();
/* Generate dot format...
* When used for the CallGraphView (in contrast to "Export Callgraph..."),
* the labels are only dummy placeholders to reserve space for our own
* drawings.
*/
*stream << "digraph \"callgraph\" {\n";
if (_go->layout() == LeftRight) {
*stream << QStringLiteral(" rankdir=LR;\n");
} else if (_go->layout() == Circular) {
TraceFunction *f = 0;
switch (_item->type()) {
case ProfileContext::Function:
case ProfileContext::FunctionCycle:
f = (TraceFunction*) _item;
break;
case ProfileContext::Call:
f = ((TraceCall*)_item)->caller(true);
break;
default:
break;
}
if (f)
*stream << QStringLiteral(" center=F%1;\n").arg((qptrdiff)f, 0, 16);
*stream << QStringLiteral(" overlap=false;\n splines=true;\n");
}
// for clustering
QMap > nLists;
GraphNodeMap::Iterator nit;
for (nit = _nodeMap.begin(); nit != _nodeMap.end(); ++nit ) {
GraphNode& n = *nit;
if (n.incl <= _realFuncLimit)
continue;
// for clustering: get cost item group of function
TraceCostItem* g;
TraceFunction* f = n.function();
switch (_groupType) {
case ProfileContext::Object:
g = f->object();
break;
case ProfileContext::Class:
g = f->cls();
break;
case ProfileContext::File:
g = f->file();
break;
case ProfileContext::FunctionCycle:
g = f->cycle();
break;
default:
g = 0;
break;
}
nLists[g].append(&n);
}
QMap >::Iterator lit;
int cluster = 0;
for (lit = nLists.begin(); lit != nLists.end(); ++lit, cluster++) {
QList& l = lit.value();
TraceCostItem* i = lit.key();
if (_go->clusterGroups() && i) {
QString iabr = GlobalConfig::shortenSymbol(i->prettyName());
// escape quotation marks in symbols to avoid invalid dot syntax
iabr.replace("\"", "\\\"");
*stream << QStringLiteral("subgraph \"cluster%1\" { label=\"%2\";\n")
.arg(cluster).arg(iabr);
}
foreach(GraphNode* np, l) {
TraceFunction* f = np->function();
QString abr = GlobalConfig::shortenSymbol(f->prettyName());
// escape quotation marks to avoid invalid dot syntax
abr.replace("\"", "\\\"");
*stream << QStringLiteral(" F%1 [").arg((qptrdiff)f, 0, 16);
if (_useBox) {
// we want a minimal size for cost display
if ((int)abr.length() < 8) abr = abr + QString(8 - abr.length(),'_');
// make label 3 lines for CallGraphView
*stream << QStringLiteral("shape=box,label=\"** %1 **\\n**\\n%2\"];\n")
.arg(abr)
.arg(SubCost(np->incl).pretty());
} else
*stream << QStringLiteral("label=\"%1\\n%2\"];\n")
.arg(abr)
.arg(SubCost(np->incl).pretty());
}
if (_go->clusterGroups() && i)
*stream << QStringLiteral("}\n");
}
GraphEdgeMap::Iterator eit;
for (eit = _edgeMap.begin(); eit != _edgeMap.end(); ++eit ) {
GraphEdge& e = *eit;
if (e.cost < _realCallLimit)
continue;
if (!_go->expandCycles()) {
// do not show inner cycle calls
if (e.call()->inCycle()>0)
continue;
}
GraphNode& from = _nodeMap[e.from()];
GraphNode& to = _nodeMap[e.to()];
e.setCallerNode(&from);
e.setCalleeNode(&to);
if ((from.incl <= _realFuncLimit) ||(to.incl <= _realFuncLimit))
continue;
// remove dumped edges from n.callers/n.callees
from.removeEdge(&e);
to.removeEdge(&e);
*stream << QStringLiteral(" F%1 -> F%2 [weight=%3")
.arg((qptrdiff)e.from(), 0, 16)
.arg((qptrdiff)e.to(), 0, 16)
.arg((long)log(log(e.cost)));
if (_go->detailLevel() ==1) {
*stream << QStringLiteral(",label=\"%1 (%2x)\"")
.arg(SubCost(e.cost).pretty())
.arg(SubCost(e.count).pretty());
}
else if (_go->detailLevel() ==2)
*stream << QStringLiteral(",label=\"%3\\n%4 x\"")
.arg(SubCost(e.cost).pretty())
.arg(SubCost(e.count).pretty());
*stream << QStringLiteral("];\n");
}
if (_go->showSkipped()) {
// Create sum-edges for skipped edges
GraphEdge* e;
double costSum, countSum;
for (nit = _nodeMap.begin(); nit != _nodeMap.end(); ++nit ) {
GraphNode& n = *nit;
if (n.incl <= _realFuncLimit)
continue;
// add edge for all skipped callers if cost sum is high enough
costSum = n.callerCostSum();
countSum = n.callerCountSum();
if (costSum > _realCallLimit) {
QPair p(0, n.function());
e = &(_edgeMap[p]);
e->setCallee(p.second);
e->cost = costSum;
e->count = countSum;
*stream << QStringLiteral(" R%1 [shape=point,label=\"\"];\n")
.arg((qptrdiff)n.function(), 0, 16);
*stream << QStringLiteral(" R%1 -> F%2 [label=\"%3\\n%4 x\",weight=%5];\n")
.arg((qptrdiff)n.function(), 0, 16)
.arg((qptrdiff)n.function(), 0, 16)
.arg(SubCost(costSum).pretty())
.arg(SubCost(countSum).pretty())
.arg((int)log(costSum));
}
// add edge for all skipped callees if cost sum is high enough
costSum = n.calleeCostSum();
countSum = n.calleeCountSum();
if (costSum > _realCallLimit) {
QPair p(n.function(), 0);
e = &(_edgeMap[p]);
e->setCaller(p.first);
e->cost = costSum;
e->count = countSum;
*stream << QStringLiteral(" S%1 [shape=point,label=\"\"];\n")
.arg((qptrdiff)n.function(), 0, 16);
*stream << QStringLiteral(" F%1 -> S%2 [label=\"%3\\n%4 x\",weight=%5];\n")
.arg((qptrdiff)n.function(), 0, 16)
.arg((qptrdiff)n.function(), 0, 16)
.arg(SubCost(costSum).pretty())
.arg(SubCost(countSum).pretty())
.arg((int)log(costSum));
}
}
}
// clear edges here completely.
// Visible edges are inserted again on parsing in CallGraphView::refresh
for (nit = _nodeMap.begin(); nit != _nodeMap.end(); ++nit ) {
GraphNode& n = *nit;
n.clearEdges();
}
*stream << "}\n";
if (!device) {
if (_tmpFile) {
stream->flush();
_tmpFile->seek(0);
} else {
file->close();
delete file;
}
}
delete stream;
}
void GraphExporter::sortEdges()
{
GraphNodeMap::Iterator nit;
for (nit = _nodeMap.begin(); nit != _nodeMap.end(); ++nit ) {
GraphNode& n = *nit;
n.sortEdges();
}
}
TraceFunction* GraphExporter::toFunc(QString s)
{
if (s[0] != 'F')
return 0;
bool ok;
TraceFunction* f = (TraceFunction*) s.midRef(1).toULongLong(&ok, 16);
if (!ok)
return 0;
return f;
}
GraphNode* GraphExporter::node(TraceFunction* f)
{
if (!f)
return 0;
GraphNodeMap::Iterator it = _nodeMap.find(f);
if (it == _nodeMap.end())
return 0;
return &(*it);
}
GraphEdge* GraphExporter::edge(TraceFunction* f1, TraceFunction* f2)
{
GraphEdgeMap::Iterator it = _edgeMap.find(qMakePair(f1, f2));
if (it == _edgeMap.end())
return 0;
return &(*it);
}
/**
* We do a DFS and do not stop on already visited nodes/edges,
* but add up costs. We only stop if limits/max depth is reached.
*
* For a node/edge, it can happen that the first time visited the
* cost will below the limit, so the search is stopped.
* If on a further visit of the node/edge the limit is reached,
* we use the whole node/edge cost and continue search.
*/
void GraphExporter::buildGraph(TraceFunction* f, int depth, bool toCallees,
double factor)
{
#if DEBUG_GRAPH
qDebug() << "buildGraph(" << f->prettyName() << "," << d << "," << factor
<< ") [to " << (toCallees ? "Callees":"Callers") << "]";
#endif
double oldIncl = 0.0;
GraphNode& n = _nodeMap[f];
if (n.function() == 0) {
n.setFunction(f);
} else
oldIncl = n.incl;
double incl = f->inclusive()->subCost(_eventType) * factor;
n.incl += incl;
n.self += f->subCost(_eventType) * factor;
if (0)
qDebug(" Added Incl. %f, now %f", incl, n.incl);
// A negative depth limit means "unlimited"
int maxDepth = toCallees ? _go->maxCalleeDepth()
: _go->maxCallerDepth();
// Never go beyound a depth of 100
if ((maxDepth < 0) || (maxDepth>100)) maxDepth = 100;
if (depth >= maxDepth) {
if (0)
qDebug(" Cutoff, max depth reached");
return;
}
// if we just reached the limit by summing, do a DFS
// from here with full incl. cost because of previous cutoffs
if ((n.incl >= _realFuncLimit) && (oldIncl < _realFuncLimit))
incl = n.incl;
if (f->cycle()) {
// for cycles members, we never stop on first visit, but always on 2nd
// note: a 2nd visit never should happen, as we do not follow inner-cycle
// calls
if (oldIncl > 0.0) {
if (0)
qDebug(" Cutoff, 2nd visit to Cycle Member");
// and takeback cost addition, as it is added twice
n.incl = oldIncl;
n.self -= f->subCost(_eventType) * factor;
return;
}
} else if (incl <= _realFuncLimit) {
if (0)
qDebug(" Cutoff, below limit");
return;
}
TraceFunction* f2;
// on entering a cycle, only go the FunctionCycle
TraceCallList l = toCallees ? f->callings(false) : f->callers(false);
foreach(TraceCall* call, l) {
f2 = toCallees ? call->called(false) : call->caller(false);
double count = call->callCount() * factor;
double cost = call->subCost(_eventType) * factor;
// ignore function calls with absolute cost < 3 per call
// No: This would skip a lot of functions e.g. with L2 cache misses
// if (count>0.0 && (cost/count < 3)) continue;
double oldCost = 0.0;
QPair p(toCallees ? f : f2,
toCallees ? f2 : f);
GraphEdge& e = _edgeMap[p];
if (e.call() == 0) {
e.setCall(call);
e.setCaller(p.first);
e.setCallee(p.second);
} else
oldCost = e.cost;
e.cost += cost;
e.count += count;
if (0)
qDebug(" Edge to %s, added cost %f, now %f",
qPrintable(f2->prettyName()), cost, e.cost);
// if this call goes into a FunctionCycle, we also show the real call
if (f2->cycle() == f2) {
TraceFunction* realF;
realF = toCallees ? call->called(true) : call->caller(true);
QPair
realP(toCallees ? f : realF, toCallees ? realF : f);
GraphEdge& e = _edgeMap[realP];
if (e.call() == 0) {
e.setCall(call);
e.setCaller(realP.first);
e.setCallee(realP.second);
}
e.cost += cost;
e.count += count;
}
// - do not do a DFS on calls in recursion/cycle
if (call->inCycle()>0)
continue;
if (call->isRecursion())
continue;
if (toCallees)
n.addUniqueCallee(&e);
else
n.addUniqueCaller(&e);
// if we just reached the call limit (=func limit by summing, do a DFS
// from here with full incl. cost because of previous cutoffs
if ((e.cost >= _realCallLimit) && (oldCost < _realCallLimit))
cost = e.cost;
if ((cost <= 0) || (cost <= _realCallLimit)) {
if (0)
qDebug(" Edge Cutoff, limit not reached");
continue;
}
SubCost s;
if (call->inCycle())
s = f2->cycle()->inclusive()->subCost(_eventType);
else
s = f2->inclusive()->subCost(_eventType);
SubCost v = call->subCost(_eventType);
// Never recurse if s or v is 0 (can happen with bogus input)
if ((v == 0) || (s== 0)) continue;
buildGraph(f2, depth+1, toCallees, factor * v / s);
}
}
//
// PannerView
//
PanningView::PanningView(QWidget * parent)
: QGraphicsView(parent)
{
_movingZoomRect = false;
// FIXME: Why does this not work?
viewport()->setFocusPolicy(Qt::NoFocus);
}
void PanningView::setZoomRect(const QRectF& r)
{
_zoomRect = r;
viewport()->update();
}
void PanningView::drawForeground(QPainter * p, const QRectF&)
{
if (!_zoomRect.isValid())
return;
QColor red(Qt::red);
QPen pen(red.dark());
pen.setWidthF(2.0 / matrix().m11());
p->setPen(pen);
QColor c(red.dark());
c.setAlphaF(0.05);
p->setBrush(QBrush(c));
p->drawRect(QRectF(_zoomRect.x(), _zoomRect.y(),
_zoomRect.width()-1, _zoomRect.height()-1));
}
void PanningView::mousePressEvent(QMouseEvent* e)
{
QPointF sPos = mapToScene(e->pos());
if (_zoomRect.isValid()) {
if (!_zoomRect.contains(sPos))
emit zoomRectMoved(sPos.x() - _zoomRect.center().x(),
sPos.y() - _zoomRect.center().y());
_movingZoomRect = true;
_lastPos = sPos;
}
}
void PanningView::mouseMoveEvent(QMouseEvent* e)
{
QPointF sPos = mapToScene(e->pos());
if (_movingZoomRect) {
emit zoomRectMoved(sPos.x() - _lastPos.x(),
sPos.y() - _lastPos.y());
_lastPos = sPos;
}
}
void PanningView::mouseReleaseEvent(QMouseEvent*)
{
_movingZoomRect = false;
emit zoomRectMoveFinished();
}
//
// CanvasNode
//
CanvasNode::CanvasNode(CallGraphView* v, GraphNode* n, int x, int y, int w,
int h) :
QGraphicsRectItem(QRect(x, y, w, h)), _node(n), _view(v)
{
setPosition(0, DrawParams::TopCenter);
setPosition(1, DrawParams::BottomCenter);
updateGroup();
if (!_node || !_view)
return;
if (_node->function())
setText(0, _node->function()->prettyName());
ProfileCostArray* totalCost;
if (GlobalConfig::showExpanded()) {
if (_view->activeFunction()) {
if (_view->activeFunction()->cycle())
totalCost = _view->activeFunction()->cycle()->inclusive();
else
totalCost = _view->activeFunction()->inclusive();
} else
totalCost = (ProfileCostArray*) _view->activeItem();
} else
totalCost = ((TraceItemView*)_view)->data();
double total = totalCost->subCost(_view->eventType());
double inclP = 100.0 * n->incl/ total;
if (GlobalConfig::showPercentage())
setText(1, QStringLiteral("%1 %")
.arg(inclP, 0, 'f', GlobalConfig::percentPrecision()));
else
setText(1, SubCost(n->incl).pretty());
setPixmap(1, percentagePixmap(25, 10, (int)(inclP+.5), Qt::blue, true));
setToolTip(QStringLiteral("%1 (%2)").arg(text(0)).arg(text(1)));
}
void CanvasNode::setSelected(bool s)
{
StoredDrawParams::setSelected(s);
update();
}
void CanvasNode::updateGroup()
{
if (!_view || !_node)
return;
QColor c = GlobalGUIConfig::functionColor(_view->groupType(),
_node->function());
setBackColor(c);
update();
}
void CanvasNode::paint(QPainter* p,
const QStyleOptionGraphicsItem* option,
QWidget*)
{
QRect r = rect().toRect(), origRect = r;
r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2);
RectDrawing d(r);
d.drawBack(p, this);
r.setRect(r.x()+2, r.y()+2, r.width()-4, r.height()-4);
#if 0
if (StoredDrawParams::selected() && _view->hasFocus()) {
_view->style().drawPrimitive( QStyle::PE_FocusRect, &p, r,
_view->colorGroup());
}
#endif
// draw afterwards to always get a frame even when zoomed
p->setPen(StoredDrawParams::selected() ? Qt::red : Qt::black);
p->drawRect(QRect(origRect.x(), origRect.y(), origRect.width()-1,
origRect.height()-1));
#if QT_VERSION >= 0x040600
if (option->levelOfDetailFromTransform(p->transform()) < .5)
return;
#else
if (option->levelOfDetail < .5)
return;
#endif
d.setRect(r);
d.drawField(p, 0, this);
d.drawField(p, 1, this);
}
//
// CanvasEdgeLabel
//
CanvasEdgeLabel::CanvasEdgeLabel(CallGraphView* v, CanvasEdge* ce, int x,
int y, int w, int h) :
QGraphicsRectItem(QRect(x, y, w, h)), _ce(ce), _view(v), _percentage(0.0)
{
GraphEdge* e = ce->edge();
if (!e)
return;
setPosition(1, DrawParams::BottomCenter);
ProfileCostArray* totalCost;
if (GlobalConfig::showExpanded()) {
if (_view->activeFunction()) {
if (_view->activeFunction()->cycle())
totalCost = _view->activeFunction()->cycle()->inclusive();
else
totalCost = _view->activeFunction()->inclusive();
} else
totalCost = (ProfileCostArray*) _view->activeItem();
} else
totalCost = ((TraceItemView*)_view)->data();
double total = totalCost->subCost(_view->eventType());
double inclP = 100.0 * e->cost/ total;
if (GlobalConfig::showPercentage())
setText(1, QStringLiteral("%1 %")
.arg(inclP, 0, 'f', GlobalConfig::percentPrecision()));
else
setText(1, SubCost(e->cost).pretty());
int pixPos = 1;
if (((TraceItemView*)_view)->data()->maxCallCount() > 0) {
setPosition(0, DrawParams::TopCenter);
SubCost count((e->count < 1.0) ? 1.0 : e->count);
setText(0, QStringLiteral("%1 x").arg(count.pretty()));
pixPos = 0;
setToolTip(QStringLiteral("%1 (%2)").arg(text(0)).arg(text(1)));
}
setPixmap(pixPos, percentagePixmap(25, 10, (int)(inclP+.5), Qt::blue, true));
_percentage = inclP;
if (_percentage > 100.0) _percentage = 100.0;
if (e->call() && (e->call()->isRecursion() || e->call()->inCycle())) {
QFontMetrics fm(font());
QPixmap p = QIcon::fromTheme(QStringLiteral("edit-undo")).pixmap(fm.height());
setPixmap(pixPos, p); // replace percentage pixmap
}
}
void CanvasEdgeLabel::paint(QPainter* p,
const QStyleOptionGraphicsItem* option, QWidget*)
{
// draw nothing in PanningView
#if QT_VERSION >= 0x040600
if (option->levelOfDetailFromTransform(p->transform()) < .5)
return;
#else
if (option->levelOfDetail < .5)
return;
#endif
QRect r = rect().toRect();
RectDrawing d(r);
d.drawField(p, 0, this);
d.drawField(p, 1, this);
}
//
// CanvasEdgeArrow
CanvasEdgeArrow::CanvasEdgeArrow(CanvasEdge* ce)
: _ce(ce)
{}
void CanvasEdgeArrow::paint(QPainter* p,
const QStyleOptionGraphicsItem *, QWidget *)
{
p->setRenderHint(QPainter::Antialiasing);
p->setBrush(_ce->isSelected() ? Qt::red : Qt::black);
p->drawPolygon(polygon(), Qt::OddEvenFill);
}
//
// CanvasEdge
//
CanvasEdge::CanvasEdge(GraphEdge* e) :
_edge(e)
{
_label = 0;
_arrow = 0;
_thickness = 0;
setFlag(QGraphicsItem::ItemIsSelectable);
}
void CanvasEdge::setLabel(CanvasEdgeLabel* l)
{
_label = l;
if (l) {
QString tip = QStringLiteral("%1 (%2)").arg(l->text(0)).arg(l->text(1));
setToolTip(tip);
if (_arrow) _arrow->setToolTip(tip);
_thickness = log(l->percentage());
if (_thickness < .9) _thickness = .9;
}
}
void CanvasEdge::setArrow(CanvasEdgeArrow* a)
{
_arrow = a;
if (a && _label) a->setToolTip(QStringLiteral("%1 (%2)")
.arg(_label->text(0)).arg(_label->text(1)));
}
void CanvasEdge::setSelected(bool s)
{
QGraphicsItem::setSelected(s);
update();
}
void CanvasEdge::setControlPoints(const QPolygon& pa)
{
_points = pa;
QPainterPath path;
path.moveTo(pa[0]);
for (int i = 1; i < pa.size(); i += 3)
path.cubicTo(pa[i], pa[(i + 1) % pa.size()], pa[(i + 2) % pa.size()]);
setPath(path);
}
void CanvasEdge::paint(QPainter* p,
const QStyleOptionGraphicsItem* option, QWidget*)
{
p->setRenderHint(QPainter::Antialiasing);
qreal levelOfDetail;
#if QT_VERSION >= 0x040600
levelOfDetail = option->levelOfDetailFromTransform(p->transform());
#else
levelOfDetail = option->levelOfDetail;
#endif
QPen mypen = pen();
mypen.setWidthF(1.0/levelOfDetail * _thickness);
p->setPen(mypen);
p->drawPath(path());
if (isSelected()) {
mypen.setColor(Qt::red);
mypen.setWidthF(1.0/levelOfDetail * _thickness/2.0);
p->setPen(mypen);
p->drawPath(path());
}
}
//
// CanvasFrame
//
QPixmap* CanvasFrame::_p = 0;
CanvasFrame::CanvasFrame(CanvasNode* n)
{
if (!_p) {
int d = 5;
float v1 = 130.0f, v2 = 10.0f, v = v1, f = 1.03f;
// calculate pix size
QRect r(0, 0, 30, 30);
while (v>v2) {
r.setRect(r.x()-d, r.y()-d, r.width()+2*d, r.height()+2*d);
v /= f;
}
_p = new QPixmap(r.size());
_p->fill(Qt::white);
QPainter p(_p);
p.setPen(Qt::NoPen);
r.translate(-r.x(), -r.y());
while (vrect().center().x() - _p->width()/2,
n->rect().center().y() - _p->height()/2, _p->width(), _p->height()) );
}
void CanvasFrame::paint(QPainter* p,
const QStyleOptionGraphicsItem* option, QWidget*)
{
qreal levelOfDetail;
#if QT_VERSION >= 0x040600
levelOfDetail = option->levelOfDetailFromTransform(p->transform());
#else
levelOfDetail = option->levelOfDetail;
#endif
if (levelOfDetail < .5) {
QRadialGradient g(rect().center(), rect().width()/3);
g.setColorAt(0.0, Qt::gray);
g.setColorAt(1.0, Qt::white);
p->setBrush(QBrush(g));
p->setPen(Qt::NoPen);
p->drawRect(rect());
return;
}
p->drawPixmap(int( rect().x()),int( rect().y()), *_p );
}
//
// CallGraphView
//
CallGraphView::CallGraphView(TraceItemView* parentView, QWidget* parent,
const char* name) :
QGraphicsView(parent), TraceItemView(parentView)
{
setObjectName(name);
_zoomPosition = DEFAULT_ZOOMPOS;
_lastAutoPosition = TopLeft;
_scene = 0;
_xMargin = _yMargin = 0;
_panningView = new PanningView(this);
_panningZoom = 1;
_selectedNode = 0;
_selectedEdge = 0;
_isMoving = false;
_exporter.setGraphOptions(this);
_panningView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
_panningView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
_panningView->raise();
_panningView->hide();
setFocusPolicy(Qt::StrongFocus);
setAttribute(Qt::WA_NoSystemBackground, true);
connect(_panningView, &PanningView::zoomRectMoved, this, &CallGraphView::zoomRectMoved);
connect(_panningView, &PanningView::zoomRectMoveFinished, this, &CallGraphView::zoomRectMoveFinished);
this->setWhatsThis(whatsThis() );
// tooltips...
//_tip = new CallGraphTip(this);
_renderProcess = 0;
_prevSelectedNode = 0;
connect(&_renderTimer, &QTimer::timeout,
this, &CallGraphView::showRenderWarning);
}
CallGraphView::~CallGraphView()
{
clear();
delete _panningView;
}
QString CallGraphView::whatsThis() const
{
return tr("Call Graph around active Function"
"Depending on configuration, this view shows "
"the call graph environment of the active function. "
"Note: the shown cost is only the cost which is "
"spent while the active function was actually running; "
"i.e. the cost shown for main() - if it is visible - should "
"be the same as the cost of the active function, as that is "
"the part of inclusive cost of main() spent while the active "
"function was running.
"
"For cycles, blue call arrows indicate that this is an "
"artificial call added for correct drawing which "
"actually never happened.
"
"If the graph is larger than the widget area, an overview "
"panner is shown in one edge. "
"There are similar visualization options to the "
"Call Treemap; the selected function is highlighted.
");
}
void CallGraphView::updateSizes(QSize s)
{
if (!_scene)
return;
if (s == QSize(0, 0))
s = size();
// the part of the scene that should be visible
int cWidth = (int)_scene->width() - 2*_xMargin + 100;
int cHeight = (int)_scene->height() - 2*_yMargin + 100;
// hide birds eye view if no overview needed
if (!_data || !_activeItem ||
((cWidth < s.width()) && (cHeight < s.height())) ) {
_panningView->hide();
return;
}
// first, assume use of 1/3 of width/height (possible larger)
double zoom = .33 * s.width() / cWidth;
if (zoom * cHeight < .33 * s.height())
zoom = .33 * s.height() / cHeight;
// fit to widget size
if (cWidth * zoom > s.width())
zoom = s.width() / (double)cWidth;
if (cHeight * zoom > s.height())
zoom = s.height() / (double)cHeight;
// scale to never use full height/width
zoom = zoom * 3/4;
// at most a zoom of 1/3
if (zoom > .33)
zoom = .33;
if (zoom != _panningZoom) {
_panningZoom = zoom;
if (0)
qDebug("Canvas Size: %fx%f, Content: %dx%d, Zoom: %f",
_scene->width(), _scene->height(), cWidth, cHeight, zoom);
QMatrix m;
_panningView->setMatrix(m.scale(zoom, zoom));
// make it a little bigger to compensate for widget frame
_panningView->resize(int(cWidth * zoom) + 4, int(cHeight * zoom) + 4);
// update ZoomRect in panningView
scrollContentsBy(0, 0);
}
_panningView->centerOn(_scene->width()/2, _scene->height()/2);
int cvW = _panningView->width();
int cvH = _panningView->height();
int x = width()- cvW - verticalScrollBar()->width() -2;
int y = height()-cvH - horizontalScrollBar()->height() -2;
QPoint oldZoomPos = _panningView->pos();
QPoint newZoomPos = QPoint(0, 0);
ZoomPosition zp = _zoomPosition;
if (zp == Auto) {
int tlCols = items(QRect(0,0, cvW,cvH)).count();
int trCols = items(QRect(x,0, cvW,cvH)).count();
int blCols = items(QRect(0,y, cvW,cvH)).count();
int brCols = items(QRect(x,y, cvW,cvH)).count();
int minCols = tlCols;
zp = _lastAutoPosition;
switch (zp) {
case TopRight:
minCols = trCols;
break;
case BottomLeft:
minCols = blCols;
break;
case BottomRight:
minCols = brCols;
break;
default:
case TopLeft:
minCols = tlCols;
break;
}
if (minCols > tlCols) {
minCols = tlCols;
zp = TopLeft;
}
if (minCols > trCols) {
minCols = trCols;
zp = TopRight;
}
if (minCols > blCols) {
minCols = blCols;
zp = BottomLeft;
}
if (minCols > brCols) {
minCols = brCols;
zp = BottomRight;
}
_lastAutoPosition = zp;
}
switch (zp) {
case TopLeft:
newZoomPos = QPoint(0, 0);
break;
case TopRight:
newZoomPos = QPoint(x, 0);
break;
case BottomLeft:
newZoomPos = QPoint(0, y);
break;
case BottomRight:
newZoomPos = QPoint(x, y);
break;
default:
break;
}
if (newZoomPos != oldZoomPos)
_panningView->move(newZoomPos);
if (zp == Hide)
_panningView->hide();
else
_panningView->show();
}
void CallGraphView::focusInEvent(QFocusEvent*)
{
if (!_scene) return;
if (_selectedNode && _selectedNode->canvasNode()) {
_selectedNode->canvasNode()->setSelected(true); // requests item update
_scene->update();
}
}
void CallGraphView::focusOutEvent(QFocusEvent* e)
{
// trigger updates as in focusInEvent
focusInEvent(e);
}
void CallGraphView::keyPressEvent(QKeyEvent* e)
{
if (!_scene) {
e->ignore();
return;
}
if ((e->key() == Qt::Key_Return) ||(e->key() == Qt::Key_Space)) {
if (_selectedNode)
activated(_selectedNode->function());
else if (_selectedEdge && _selectedEdge->call())
activated(_selectedEdge->call());
return;
}
// move selected node/edge
if (!(e->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier))
&&(_selectedNode || _selectedEdge)&&((e->key() == Qt::Key_Up)
||(e->key() == Qt::Key_Down)||(e->key() == Qt::Key_Left)||(e->key()
== Qt::Key_Right))) {
TraceFunction* f = 0;
TraceCall* c = 0;
// rotate arrow key meaning for LeftRight layout
int key = e->key();
if (_layout == LeftRight) {
switch (key) {
case Qt::Key_Up:
key = Qt::Key_Left;
break;
case Qt::Key_Down:
key = Qt::Key_Right;
break;
case Qt::Key_Left:
key = Qt::Key_Up;
break;
case Qt::Key_Right:
key = Qt::Key_Down;
break;
default:
break;
}
}
if (_selectedNode) {
if (key == Qt::Key_Up)
c = _selectedNode->visibleCaller();
if (key == Qt::Key_Down)
c = _selectedNode->visibleCallee();
if (key == Qt::Key_Right)
f = _selectedNode->nextVisible();
if (key == Qt::Key_Left)
f = _selectedNode->priorVisible();
} else if (_selectedEdge) {
if (key == Qt::Key_Up)
f = _selectedEdge->visibleCaller();
if (key == Qt::Key_Down)
f = _selectedEdge->visibleCallee();
if (key == Qt::Key_Right)
c = _selectedEdge->nextVisible();
if (key == Qt::Key_Left)
c = _selectedEdge->priorVisible();
}
if (c)
selected(c);
if (f)
selected(f);
return;
}
// move canvas...
QPointF center = mapToScene(viewport()->rect().center());
if (e->key() == Qt::Key_Home)
centerOn(center + QPointF(-_scene->width(), 0));
else if (e->key() == Qt::Key_End)
centerOn(center + QPointF(_scene->width(), 0));
else if (e->key() == Qt::Key_PageUp) {
QPointF dy = mapToScene(0, height()) - mapToScene(0, 0);
centerOn(center + QPointF(-dy.x()/2, -dy.y()/2));
} else if (e->key() == Qt::Key_PageDown) {
QPointF dy = mapToScene(0, height()) - mapToScene(0, 0);
centerOn(center + QPointF(dy.x()/2, dy.y()/2));
} else if (e->key() == Qt::Key_Left) {
QPointF dx = mapToScene(width(), 0) - mapToScene(0, 0);
centerOn(center + QPointF(-dx.x()/10, -dx.y()/10));
} else if (e->key() == Qt::Key_Right) {
QPointF dx = mapToScene(width(), 0) - mapToScene(0, 0);
centerOn(center + QPointF(dx.x()/10, dx.y()/10));
} else if (e->key() == Qt::Key_Down) {
QPointF dy = mapToScene(0, height()) - mapToScene(0, 0);
centerOn(center + QPointF(dy.x()/10, dy.y()/10));
} else if (e->key() == Qt::Key_Up) {
QPointF dy = mapToScene(0, height()) - mapToScene(0, 0);
centerOn(center + QPointF(-dy.x()/10, -dy.y()/10));
} else
e->ignore();
}
void CallGraphView::resizeEvent(QResizeEvent* e)
{
QGraphicsView::resizeEvent(e);
if (_scene)
updateSizes(e->size());
}
CostItem* CallGraphView::canShow(CostItem* i)
{
if (i) {
switch (i->type()) {
case ProfileContext::Function:
case ProfileContext::FunctionCycle:
case ProfileContext::Call:
return i;
default:
break;
}
}
return 0;
}
void CallGraphView::doUpdate(int changeType, bool)
{
// Special case ?
if (changeType == eventType2Changed)
return;
if (changeType == selectedItemChanged) {
if (!_scene)
return;
if (!_selectedItem)
return;
GraphNode* n = 0;
GraphEdge* e = 0;
if ((_selectedItem->type() == ProfileContext::Function)
||(_selectedItem->type() == ProfileContext::FunctionCycle)) {
n = _exporter.node((TraceFunction*)_selectedItem);
if (n == _selectedNode)
return;
} else if (_selectedItem->type() == ProfileContext::Call) {
TraceCall* c = (TraceCall*)_selectedItem;
e = _exporter.edge(c->caller(false), c->called(false));
if (e == _selectedEdge)
return;
}
// unselect any selected item
if (_selectedNode && _selectedNode->canvasNode()) {
_selectedNode->canvasNode()->setSelected(false);
}
_selectedNode = 0;
if (_selectedEdge && _selectedEdge->canvasEdge()) {
_selectedEdge->canvasEdge()->setSelected(false);
}
_selectedEdge = 0;
// select
CanvasNode* sNode = 0;
if (n && n->canvasNode()) {
_selectedNode = n;
_selectedNode->canvasNode()->setSelected(true);
if (!_isMoving)
sNode = _selectedNode->canvasNode();
}
if (e && e->canvasEdge()) {
_selectedEdge = e;
_selectedEdge->canvasEdge()->setSelected(true);
#if 0 // do not change position when selecting edge
if (!_isMoving) {
if (_selectedEdge->fromNode())
sNode = _selectedEdge->fromNode()->canvasNode();
if (!sNode && _selectedEdge->toNode())
sNode = _selectedEdge->toNode()->canvasNode();
}
#endif
}
if (sNode)
ensureVisible(sNode);
_scene->update();
return;
}
if (changeType == groupTypeChanged) {
if (!_scene)
return;
if (_clusterGroups) {
refresh();
return;
}
QList l = _scene->items();
for (int i = 0; i < l.size(); ++i)
if (l[i]->type() == CANVAS_NODE)
((CanvasNode*)l[i])->updateGroup();
_scene->update();
return;
}
if (changeType & dataChanged) {
// invalidate old selection and graph part
_exporter.reset(_data, _activeItem, _eventType, _groupType);
_selectedNode = 0;
_selectedEdge = 0;
}
refresh();
}
void CallGraphView::clear()
{
if (!_scene)
return;
_panningView->setScene(0);
setScene(0);
delete _scene;
_scene = 0;
}
void CallGraphView::showText(QString s)
{
clear();
_renderTimer.stop();
_scene = new QGraphicsScene;
_scene->addSimpleText(s);
centerOn(0, 0);
setScene(_scene);
_scene->update();
_panningView->hide();
}
void CallGraphView::showRenderWarning()
{
QString s;
if (_renderProcess)
s = tr("Warning: a long lasting graph layouting is in progress.\n"
"Reduce node/edge limits for speedup.\n");
else
s = tr("Layouting stopped.\n");
s.append(tr("The call graph has %1 nodes and %2 edges.\n")
.arg(_exporter.nodeCount()).arg(_exporter.edgeCount()));
showText(s);
}
void CallGraphView::showRenderError(QString s)
{
QString err;
err = tr("No graph available because the layouting process failed.\n");
if (_renderProcess)
err += tr("Trying to run the following command did not work:\n"
"'%1'\n").arg(_renderProcessCmdLine);
err += tr("Please check that 'dot' is installed (package GraphViz).");
if (!s.isEmpty())
err += QStringLiteral("\n\n%1").arg(s);
showText(err);
}
void CallGraphView::stopRendering()
{
if (!_renderProcess)
return;
qDebug("CallGraphView::stopRendering: Killing QProcess %p",
_renderProcess);
_renderProcess->kill();
// forget about this process, not interesting any longer
_renderProcess->deleteLater();
_renderProcess = 0;
_unparsedOutput = QString();
_renderTimer.setSingleShot(true);
_renderTimer.start(200);
}
void CallGraphView::refresh()
{
// trigger start of new layouting via 'dot'
if (_renderProcess)
stopRendering();
// we want to keep a selected node item at the same global position
_prevSelectedNode = _selectedNode;
_prevSelectedPos = QPoint(-1, -1);
if (_selectedNode) {
QPointF center = _selectedNode->canvasNode()->rect().center();
_prevSelectedPos = mapFromScene(center);
}
if (!_data || !_activeItem) {
showText(tr("No item activated for which to "
"draw the call graph."));
return;
}
ProfileContext::Type t = _activeItem->type();
switch (t) {
case ProfileContext::Function:
case ProfileContext::FunctionCycle:
case ProfileContext::Call:
break;
default:
showText(tr("No call graph can be drawn for "
"the active item."));
return;
}
if (1)
qDebug() << "CallGraphView::refresh";
_selectedNode = 0;
_selectedEdge = 0;
/*
* Call 'dot' asynchronoulsy in the background with the aim to
* - have responsive GUI while layout task runs (potentially long!)
* - notify user about a long run, using a timer
* - kill long running 'dot' processes when another layout is
* requested, as old data is not needed any more
*
* Even after killing a process, the QProcess needs some time
* to make sure the process is destroyed; also, stdout data
* still can be delivered after killing. Thus, there can/should be
* multiple QProcess's at one time.
* The QProcess we currently wait for data from is <_renderProcess>
* Signals from other QProcesses are ignored with the exception of
* the finished() signal, which triggers QProcess destruction.
*/
QString renderProgram;
QStringList renderArgs;
if (_layout == GraphOptions::Circular)
renderProgram = QStringLiteral("twopi");
else
renderProgram = QStringLiteral("dot");
renderArgs << QStringLiteral("-Tplain");
_unparsedOutput = QString();
// display warning if layouting takes > 1s
_renderTimer.setSingleShot(true);
_renderTimer.start(1000);
_renderProcess = new QProcess(this);
connect(_renderProcess, &QProcess::readyReadStandardOutput,
this, &CallGraphView::readDotOutput);
connect(_renderProcess, SIGNAL(error(QProcess::ProcessError)),
this, SLOT(dotError()));
connect(_renderProcess, SIGNAL(finished(int,QProcess::ExitStatus)),
this, SLOT(dotExited()));
_renderProcessCmdLine = renderProgram + " " + renderArgs.join(QStringLiteral(" "));
qDebug("CallGraphView::refresh: Starting process %p, '%s'",
_renderProcess, qPrintable(_renderProcessCmdLine));
// _renderProcess can be set to 0 on error after start().
// thus, we use a local copy afterwards
QProcess* p = _renderProcess;
p->start(renderProgram, renderArgs);
_exporter.reset(_data, _activeItem, _eventType, _groupType);
_exporter.writeDot(p);
p->closeWriteChannel();
}
void CallGraphView::readDotOutput()
{
QProcess* p = qobject_cast(sender());
qDebug("CallGraphView::readDotOutput: QProcess %p", p);
// signal from old/uninteresting process?
if ((_renderProcess == 0) || (p != _renderProcess)) {
p->deleteLater();
return;
}
- _unparsedOutput.append(_renderProcess->readAllStandardOutput());
+ _unparsedOutput.append(QString::fromLocal8Bit(_renderProcess->readAllStandardOutput()));
}
void CallGraphView::dotError()
{
QProcess* p = qobject_cast(sender());
qDebug("CallGraphView::dotError: Got %d from QProcess %p",
p->error(), p);
// signal from old/uninteresting process?
if ((_renderProcess == 0) || (p != _renderProcess)) {
p->deleteLater();
return;
}
- showRenderError(_renderProcess->readAllStandardError());
+ showRenderError(QString::fromLocal8Bit(_renderProcess->readAllStandardError()));
// not interesting any longer
_renderProcess->deleteLater();
_renderProcess = 0;
}
void CallGraphView::dotExited()
{
QProcess* p = qobject_cast(sender());
qDebug("CallGraphView::dotExited: QProcess %p", p);
// signal from old/uninteresting process?
if ((_renderProcess == 0) || (p != _renderProcess)) {
p->deleteLater();
return;
}
- _unparsedOutput.append(_renderProcess->readAllStandardOutput());
+ _unparsedOutput.append(QString::fromLocal8Bit(_renderProcess->readAllStandardOutput()));
_renderProcess->deleteLater();
_renderProcess = 0;
QString line, cmd;
CanvasNode *rItem;
QGraphicsEllipseItem* eItem;
CanvasEdge* sItem;
CanvasEdgeLabel* lItem;
QTextStream* dotStream;
double scale = 1.0, scaleX = 1.0, scaleY = 1.0;
double dotWidth = 0, dotHeight = 0;
GraphNode* activeNode = 0;
GraphEdge* activeEdge = 0;
_renderTimer.stop();
viewport()->setUpdatesEnabled(false);
clear();
dotStream = new QTextStream(&_unparsedOutput, QIODevice::ReadOnly);
// First pass to adjust coordinate scaling by node height given from dot
// Normal detail level (=1) should be 3 lines using general KDE font
double nodeHeight = 0.0;
while(1) {
line = dotStream->readLine();
if (line.isNull()) break;
if (line.isEmpty()) continue;
QTextStream lineStream(&line, QIODevice::ReadOnly);
lineStream >> cmd;
if (cmd != QLatin1String("node")) continue;
QString s, h;
lineStream >> s /*name*/ >> s /*x*/ >> s /*y*/ >> s /*width*/ >> h /*height*/;
nodeHeight = h.toDouble();
break;
}
if (nodeHeight > 0.0) {
scaleY = (8 + (1 + 2 * _detailLevel) * fontMetrics().height()) / nodeHeight;
scaleX = 80;
}
dotStream->seek(0);
int lineno = 0;
while (1) {
line = dotStream->readLine();
if (line.isNull())
break;
lineno++;
if (line.isEmpty())
continue;
QTextStream lineStream(&line, QIODevice::ReadOnly);
lineStream >> cmd;
if (0)
qDebug("%s:%d - line '%s', cmd '%s'",
qPrintable(_exporter.filename()),
lineno, qPrintable(line), qPrintable(cmd));
if (cmd == QLatin1String("stop"))
break;
if (cmd == QLatin1String("graph")) {
QString dotWidthString, dotHeightString;
// scale will not be used
lineStream >> scale >> dotWidthString >> dotHeightString;
dotWidth = dotWidthString.toDouble();
dotHeight = dotHeightString.toDouble();
if (!_scene) {
int w = (int)(scaleX * dotWidth);
int h = (int)(scaleY * dotHeight);
// We use as minimum canvas size the desktop size.
// Otherwise, the canvas would have to be resized on widget resize.
_xMargin = 50;
if (w < QApplication::desktop()->width())
_xMargin += (QApplication::desktop()->width()-w)/2;
_yMargin = 50;
if (h < QApplication::desktop()->height())
_yMargin += (QApplication::desktop()->height()-h)/2;
_scene = new QGraphicsScene( 0.0, 0.0,
qreal(w+2*_xMargin), qreal(h+2*_yMargin));
// Change background color for call graph from default system color to
// white. It has to blend into the gradient for the selected function.
_scene->setBackgroundBrush(Qt::white);
#if DEBUG_GRAPH
qDebug() << qPrintable(_exporter.filename()) << ":" << lineno
<< " - graph (" << dotWidth << " x " << dotHeight
<< ") => (" << w << " x " << h << ")";
#endif
} else
qDebug() << "Ignoring 2nd 'graph' from dot ("
<< _exporter.filename() << ":"<< lineno << ")";
continue;
}
if ((cmd != QLatin1String("node")) && (cmd != QLatin1String("edge"))) {
qDebug() << "Ignoring unknown command '"<< cmd
<< "' from dot ("<< _exporter.filename() << ":"<< lineno
<< ")";
continue;
}
if (_scene == 0) {
qDebug() << "Ignoring '"<< cmd
<< "' without 'graph' from dot ("<< _exporter.filename()
<< ":"<< lineno << ")";
continue;
}
if (cmd == QLatin1String("node")) {
// x, y are centered in node
QString nodeName, nodeX, nodeY, nodeWidth, nodeHeight;
double x, y, width, height;
lineStream >> nodeName >> nodeX >> nodeY >> nodeWidth
>> nodeHeight;
x = nodeX.toDouble();
y = nodeY.toDouble();
width = nodeWidth.toDouble();
height = nodeHeight.toDouble();
GraphNode* n = _exporter.node(_exporter.toFunc(nodeName));
int xx = (int)(scaleX * x + _xMargin);
int yy = (int)(scaleY * (dotHeight - y)+ _yMargin);
int w = (int)(scaleX * width);
int h = (int)(scaleY * height);
#if DEBUG_GRAPH
qDebug() << _exporter.filename() << ":" << lineno
<< " - node '" << nodeName << "' ( "
<< x << "/" << y << " - "
<< width << "x" << height << " ) => ("
<< xx-w/2 << "/" << yy-h/2 << " - "
<< w << "x" << h << ")" << endl;
#endif
// Unnamed nodes with collapsed edges (with 'R' and 'S')
if (nodeName[0] == 'R'|| nodeName[0] == 'S') {
w = 10, h = 10;
eItem = new QGraphicsEllipseItem( QRectF(xx-w/2, yy-h/2, w, h) );
_scene->addItem(eItem);
eItem->setBrush(Qt::gray);
eItem->setZValue(1.0);
eItem->show();
continue;
}
if (!n) {
qDebug("Warning: Unknown function '%s' ?!",
qPrintable(nodeName));
continue;
}
n->setVisible(true);
rItem = new CanvasNode(this, n, xx-w/2, yy-h/2, w, h);
// limit symbol space to a maximal number of lines depending on detail level
if (_detailLevel>0) rItem->setMaxLines(0, 2*_detailLevel);
_scene->addItem(rItem);
n->setCanvasNode(rItem);
if (n) {
if (n->function() == activeItem())
activeNode = n;
if (n->function() == selectedItem())
_selectedNode = n;
rItem->setSelected(n == _selectedNode);
}
rItem->setZValue(1.0);
rItem->show();
continue;
}
// edge
QString node1Name, node2Name, label, edgeX, edgeY;
double x, y;
QPolygon poly;
int points, i;
lineStream >> node1Name >> node2Name >> points;
GraphEdge* e = _exporter.edge(_exporter.toFunc(node1Name),
_exporter.toFunc(node2Name));
if (!e) {
qDebug() << "Unknown edge '"<< node1Name << "'-'"<< node2Name
<< "' from dot ("<< _exporter.filename() << ":"<< lineno
<< ")";
continue;
}
e->setVisible(true);
if (e->fromNode())
e->fromNode()->addCallee(e);
if (e->toNode())
e->toNode()->addCaller(e);
if (0)
qDebug(" Edge with %d points:", points);
poly.resize(points);
for (i=0; i> edgeX >> edgeY;
x = edgeX.toDouble();
y = edgeY.toDouble();
int xx = (int)(scaleX * x + _xMargin);
int yy = (int)(scaleY * (dotHeight - y)+ _yMargin);
if (0)
qDebug(" P %d: ( %f / %f ) => ( %d / %d)", i, x, y, xx, yy);
poly.setPoint(i, xx, yy);
}
if (i < points) {
qDebug("CallGraphView: Can not read %d spline points (%s:%d)",
points, qPrintable(_exporter.filename()), lineno);
continue;
}
// calls into/out of cycles are special: make them blue
QColor arrowColor = Qt::black;
TraceFunction* caller = e->fromNode() ? e->fromNode()->function() : 0;
TraceFunction* called = e->toNode() ? e->toNode()->function() : 0;
if ( (caller && (caller->cycle() == caller)) ||
(called && (called->cycle() == called)) ) arrowColor = Qt::blue;
sItem = new CanvasEdge(e);
_scene->addItem(sItem);
e->setCanvasEdge(sItem);
sItem->setControlPoints(poly);
// width of pen will be adjusted in CanvasEdge::paint()
sItem->setPen(QPen(arrowColor));
sItem->setZValue(0.5);
sItem->show();
if (e->call() == selectedItem())
_selectedEdge = e;
if (e->call() == activeItem())
activeEdge = e;
sItem->setSelected(e == _selectedEdge);
// Arrow head
QPoint arrowDir;
int indexHead = -1;
// check if head is at start of spline...
// this is needed because dot always gives points from top to bottom
CanvasNode* fromNode = e->fromNode() ? e->fromNode()->canvasNode() : 0;
if (fromNode) {
QPointF toCenter = fromNode->rect().center();
qreal dx0 = poly.point(0).x() - toCenter.x();
qreal dy0 = poly.point(0).y() - toCenter.y();
qreal dx1 = poly.point(points-1).x() - toCenter.x();
qreal dy1 = poly.point(points-1).y() - toCenter.y();
if (dx0*dx0+dy0*dy0 > dx1*dx1+dy1*dy1) {
// start of spline is nearer to call target node
indexHead=-1;
while (arrowDir.isNull() && (indexHead1)) {
indexHead--;
arrowDir = poly.point(indexHead) - poly.point(indexHead-1);
}
}
if (!arrowDir.isNull()) {
// arrow around pa.point(indexHead) with direction arrowDir
arrowDir *= 10.0/sqrt(double(arrowDir.x()*arrowDir.x() +
arrowDir.y()*arrowDir.y()));
QPolygonF a;
a << QPointF(poly.point(indexHead) + arrowDir);
a << QPointF(poly.point(indexHead) + QPoint(arrowDir.y()/2,
-arrowDir.x()/2));
a << QPointF(poly.point(indexHead) + QPoint(-arrowDir.y()/2,
arrowDir.x()/2));
if (0)
qDebug(" Arrow: ( %f/%f, %f/%f, %f/%f)", a[0].x(), a[0].y(),
a[1].x(), a[1].y(), a[2].x(), a[1].y());
CanvasEdgeArrow* aItem = new CanvasEdgeArrow(sItem);
_scene->addItem(aItem);
aItem->setPolygon(a);
aItem->setBrush(arrowColor);
aItem->setZValue(1.5);
aItem->show();
sItem->setArrow(aItem);
}
if (lineStream.atEnd())
continue;
// parse quoted label
QChar c;
lineStream >> c;
while (c.isSpace())
lineStream >> c;
if (c != '\"') {
lineStream >> label;
label = c + label;
} else {
lineStream >> c;
while (!c.isNull() && (c != '\"')) {
//if (c == '\\') lineStream >> c;
label += c;
lineStream >> c;
}
}
lineStream >> edgeX >> edgeY;
x = edgeX.toDouble();
y = edgeY.toDouble();
int xx = (int)(scaleX * x + _xMargin);
int yy = (int)(scaleY * (dotHeight - y)+ _yMargin);
if (0)
qDebug(" Label '%s': ( %f / %f ) => ( %d / %d)",
qPrintable(label), x, y, xx, yy);
// Fixed Dimensions for Label: 100 x 40
int w = 100;
int h = _detailLevel * 20;
lItem = new CanvasEdgeLabel(this, sItem, xx-w/2, yy-h/2, w, h);
_scene->addItem(lItem);
// edge labels above nodes
lItem->setZValue(1.5);
sItem->setLabel(lItem);
if (h>0)
lItem->show();
}
delete dotStream;
// for keyboard navigation
_exporter.sortEdges();
if (!_scene) {
_scene = new QGraphicsScene;
QString s = tr("Error running the graph layouting tool.\n");
s += tr("Please check that 'dot' is installed (package GraphViz).");
_scene->addSimpleText(s);
centerOn(0, 0);
} else if (!activeNode && !activeEdge) {
QString s = tr("There is no call graph available for function\n"
"\t'%1'\n"
"because it has no cost of the selected event type.")
.arg(_activeItem->name());
_scene->addSimpleText(s);
centerOn(0, 0);
}
_panningView->setScene(_scene);
setScene(_scene);
// if we do not have a selection, or the old selection is not
// in visible graph, make active function selected for this view
if ((!_selectedNode || !_selectedNode->canvasNode()) &&
(!_selectedEdge || !_selectedEdge->canvasEdge())) {
if (activeNode) {
_selectedNode = activeNode;
_selectedNode->canvasNode()->setSelected(true);
} else if (activeEdge) {
_selectedEdge = activeEdge;
_selectedEdge->canvasEdge()->setSelected(true);
}
}
CanvasNode* sNode = 0;
if (_selectedNode)
sNode = _selectedNode->canvasNode();
else if (_selectedEdge) {
if (_selectedEdge->fromNode())
sNode = _selectedEdge->fromNode()->canvasNode();
if (!sNode && _selectedEdge->toNode())
sNode = _selectedEdge->toNode()->canvasNode();
}
if (sNode) {
if (_prevSelectedNode) {
QPointF prevPos = mapToScene(_prevSelectedPos);
if (rect().contains(_prevSelectedPos)) {
QPointF wCenter = mapToScene(viewport()->rect().center());
centerOn(sNode->rect().center() +
wCenter - prevPos);
}
else
ensureVisible(sNode);
} else
centerOn(sNode);
}
if (activeNode) {
CanvasNode* cn = activeNode->canvasNode();
CanvasFrame* f = new CanvasFrame(cn);
_scene->addItem(f);
f->setPos(cn->pos());
f->setZValue(-1);
}
_panningZoom = 0;
updateSizes();
_scene->update();
viewport()->setUpdatesEnabled(true);
delete _renderProcess;
_renderProcess = 0;
}
// Called by QAbstractScrollArea to notify about scrollbar changes
void CallGraphView::scrollContentsBy(int dx, int dy)
{
// call QGraphicsView implementation
QGraphicsView::scrollContentsBy(dx, dy);
QPointF topLeft = mapToScene(QPoint(0, 0));
QPointF bottomRight = mapToScene(QPoint(width(), height()));
QRectF z(topLeft, bottomRight);
if (0)
qDebug("CallGraphView::scrollContentsBy(dx %d, dy %d) - to (%f,%f - %f,%f)",
dx, dy, topLeft.x(), topLeft.y(),
bottomRight.x(), bottomRight.y());
_panningView->setZoomRect(z);
}
void CallGraphView::zoomRectMoved(qreal dx, qreal dy)
{
//FIXME if (leftMargin()>0) dx = 0;
//FIXME if (topMargin()>0) dy = 0;
QScrollBar *hBar = horizontalScrollBar();
QScrollBar *vBar = verticalScrollBar();
hBar->setValue(hBar->value() + int(dx));
vBar->setValue(vBar->value() + int(dy));
}
void CallGraphView::zoomRectMoveFinished()
{
if (_zoomPosition == Auto)
updateSizes();
}
void CallGraphView::mousePressEvent(QMouseEvent* e)
{
// clicking on the viewport sets focus
setFocus();
// activate scroll mode on left click
if (e->button() == Qt::LeftButton) _isMoving = true;
QGraphicsItem* i = itemAt(e->pos());
if (i) {
if (i->type() == CANVAS_NODE) {
GraphNode* n = ((CanvasNode*)i)->node();
if (0)
qDebug("CallGraphView: Got Node '%s'",
qPrintable(n->function()->prettyName()));
selected(n->function());
}
// redirect from label / arrow to edge
if (i->type() == CANVAS_EDGELABEL)
i = ((CanvasEdgeLabel*)i)->canvasEdge();
if (i->type() == CANVAS_EDGEARROW)
i = ((CanvasEdgeArrow*)i)->canvasEdge();
if (i->type() == CANVAS_EDGE) {
GraphEdge* e = ((CanvasEdge*)i)->edge();
if (0)
qDebug("CallGraphView: Got Edge '%s'",
qPrintable(e->prettyName()));
if (e->call())
selected(e->call());
}
}
_lastPos = e->pos();
}
void CallGraphView::mouseMoveEvent(QMouseEvent* e)
{
if (_isMoving) {
QPoint delta = e->pos() - _lastPos;
QScrollBar *hBar = horizontalScrollBar();
QScrollBar *vBar = verticalScrollBar();
hBar->setValue(hBar->value() - delta.x());
vBar->setValue(vBar->value() - delta.y());
_lastPos = e->pos();
}
}
void CallGraphView::mouseReleaseEvent(QMouseEvent*)
{
_isMoving = false;
if (_zoomPosition == Auto)
updateSizes();
}
void CallGraphView::mouseDoubleClickEvent(QMouseEvent* e)
{
QGraphicsItem* i = itemAt(e->pos());
if (i == 0)
return;
if (i->type() == CANVAS_NODE) {
GraphNode* n = ((CanvasNode*)i)->node();
if (0)
qDebug("CallGraphView: Double Clicked on Node '%s'",
qPrintable(n->function()->prettyName()));
activated(n->function());
}
// redirect from label / arrow to edge
if (i->type() == CANVAS_EDGELABEL)
i = ((CanvasEdgeLabel*)i)->canvasEdge();
if (i->type() == CANVAS_EDGEARROW)
i = ((CanvasEdgeArrow*)i)->canvasEdge();
if (i->type() == CANVAS_EDGE) {
GraphEdge* e = ((CanvasEdge*)i)->edge();
if (e->call()) {
if (0)
qDebug("CallGraphView: Double Clicked On Edge '%s'",
qPrintable(e->call()->prettyName()));
activated(e->call());
}
}
}
// helper functions for context menu:
// submenu builders and trigger handlers
QAction* CallGraphView::addCallerDepthAction(QMenu* m, QString s, int d)
{
QAction* a;
a = m->addAction(s);
a->setData(d);
a->setCheckable(true);
a->setChecked(_maxCallerDepth == d);
return a;
}
QMenu* CallGraphView::addCallerDepthMenu(QMenu* menu)
{
QAction* a;
QMenu* m;
m = menu->addMenu(tr("Caller Depth"));
a = addCallerDepthAction(m, tr("Unlimited"), -1);
a->setEnabled(_funcLimit>0.005);
m->addSeparator();
addCallerDepthAction(m, tr("Depth 0", "None"), 0);
addCallerDepthAction(m, tr("max. 2"), 2);
addCallerDepthAction(m, tr("max. 5"), 5);
addCallerDepthAction(m, tr("max. 10"), 10);
addCallerDepthAction(m, tr("max. 15"), 15);
connect(m, &QMenu::triggered,
this, &CallGraphView::callerDepthTriggered );
return m;
}
void CallGraphView::callerDepthTriggered(QAction* a)
{
_maxCallerDepth = a->data().toInt(0);
refresh();
}
QAction* CallGraphView::addCalleeDepthAction(QMenu* m, QString s, int d)
{
QAction* a;
a = m->addAction(s);
a->setData(d);
a->setCheckable(true);
a->setChecked(_maxCalleeDepth == d);
return a;
}
QMenu* CallGraphView::addCalleeDepthMenu(QMenu* menu)
{
QAction* a;
QMenu* m;
m = menu->addMenu(tr("Callee Depth"));
a = addCalleeDepthAction(m, tr("Unlimited"), -1);
a->setEnabled(_funcLimit>0.005);
m->addSeparator();
addCalleeDepthAction(m, tr("Depth 0", "None"), 0);
addCalleeDepthAction(m, tr("max. 2"), 2);
addCalleeDepthAction(m, tr("max. 5"), 5);
addCalleeDepthAction(m, tr("max. 10"), 10);
addCalleeDepthAction(m, tr("max. 15"), 15);
connect(m, &QMenu::triggered,
this, &CallGraphView::calleeDepthTriggered );
return m;
}
void CallGraphView::calleeDepthTriggered(QAction* a)
{
_maxCalleeDepth = a->data().toInt(0);
refresh();
}
QAction* CallGraphView::addNodeLimitAction(QMenu* m, QString s, double l)
{
QAction* a;
a = m->addAction(s);
a->setData(l);
a->setCheckable(true);
a->setChecked(_funcLimit == l);
return a;
}
QMenu* CallGraphView::addNodeLimitMenu(QMenu* menu)
{
QAction* a;
QMenu* m;
m = menu->addMenu(tr("Min. Node Cost"));
a = addNodeLimitAction(m, tr("No Minimum"), 0.0);
// Unlimited node cost easily produces huge graphs such that 'dot'
// would need a long time to layout. For responsiveness, we only allow
// for unlimited node cost if a caller and callee depth limit is set.
a->setEnabled((_maxCallerDepth>=0) && (_maxCalleeDepth>=0));
m->addSeparator();
addNodeLimitAction(m, tr("50 %"), .5);
addNodeLimitAction(m, tr("20 %"), .2);
addNodeLimitAction(m, tr("10 %"), .1);
addNodeLimitAction(m, tr("5 %"), .05);
addNodeLimitAction(m, tr("2 %"), .02);
addNodeLimitAction(m, tr("1 %"), .01);
connect(m, &QMenu::triggered,
this, &CallGraphView::nodeLimitTriggered );
return m;
}
void CallGraphView::nodeLimitTriggered(QAction* a)
{
_funcLimit = a->data().toDouble(0);
refresh();
}
QAction* CallGraphView::addCallLimitAction(QMenu* m, QString s, double l)
{
QAction* a;
a = m->addAction(s);
a->setData(l);
a->setCheckable(true);
a->setChecked(_callLimit == l);
return a;
}
QMenu* CallGraphView::addCallLimitMenu(QMenu* menu)
{
QMenu* m;
m = menu->addMenu(tr("Min. Call Cost"));
addCallLimitAction(m, tr("Same as Node"), 1.0);
// xgettext: no-c-format
addCallLimitAction(m, tr("50 % of Node"), .5);
// xgettext: no-c-format
addCallLimitAction(m, tr("20 % of Node"), .2);
// xgettext: no-c-format
addCallLimitAction(m, tr("10 % of Node"), .1);
connect(m, &QMenu::triggered,
this, &CallGraphView::callLimitTriggered );
return m;
}
void CallGraphView::callLimitTriggered(QAction* a)
{
_callLimit = a->data().toDouble(0);
refresh();
}
QAction* CallGraphView::addZoomPosAction(QMenu* m, QString s,
CallGraphView::ZoomPosition p)
{
QAction* a;
a = m->addAction(s);
a->setData((int)p);
a->setCheckable(true);
a->setChecked(_zoomPosition == p);
return a;
}
QMenu* CallGraphView::addZoomPosMenu(QMenu* menu)
{
QMenu* m = menu->addMenu(tr("Birds-eye View"));
addZoomPosAction(m, tr("Top Left"), TopLeft);
addZoomPosAction(m, tr("Top Right"), TopRight);
addZoomPosAction(m, tr("Bottom Left"), BottomLeft);
addZoomPosAction(m, tr("Bottom Right"), BottomRight);
addZoomPosAction(m, tr("Automatic"), Auto);
addZoomPosAction(m, tr("Hide"), Hide);
connect(m, &QMenu::triggered,
this, &CallGraphView::zoomPosTriggered );
return m;
}
void CallGraphView::zoomPosTriggered(QAction* a)
{
_zoomPosition = (ZoomPosition) a->data().toInt(0);
updateSizes();
}
QAction* CallGraphView::addLayoutAction(QMenu* m, QString s,
GraphOptions::Layout l)
{
QAction* a;
a = m->addAction(s);
a->setData((int)l);
a->setCheckable(true);
a->setChecked(_layout == l);
return a;
}
QMenu* CallGraphView::addLayoutMenu(QMenu* menu)
{
QMenu* m = menu->addMenu(tr("Layout"));
addLayoutAction(m, tr("Top to Down"), TopDown);
addLayoutAction(m, tr("Left to Right"), LeftRight);
addLayoutAction(m, tr("Circular"), Circular);
connect(m, &QMenu::triggered,
this, &CallGraphView::layoutTriggered );
return m;
}
void CallGraphView::layoutTriggered(QAction* a)
{
_layout = (Layout) a->data().toInt(0);
refresh();
}
void CallGraphView::contextMenuEvent(QContextMenuEvent* e)
{
_isMoving = false;
QGraphicsItem* i = itemAt(e->pos());
QMenu popup;
TraceFunction *f = 0, *cycle = 0;
TraceCall* c = 0;
QAction* activateFunction = 0;
QAction* activateCycle = 0;
QAction* activateCall = 0;
if (i) {
if (i->type() == CANVAS_NODE) {
GraphNode* n = ((CanvasNode*)i)->node();
if (0)
qDebug("CallGraphView: Menu on Node '%s'",
qPrintable(n->function()->prettyName()));
f = n->function();
cycle = f->cycle();
QString name = f->prettyName();
QString menuStr = tr("Go to '%1'")
.arg(GlobalConfig::shortenSymbol(name));
activateFunction = popup.addAction(menuStr);
if (cycle && (cycle != f)) {
name = GlobalConfig::shortenSymbol(cycle->prettyName());
activateCycle = popup.addAction(tr("Go to '%1'").arg(name));
}
popup.addSeparator();
}
// redirect from label / arrow to edge
if (i->type() == CANVAS_EDGELABEL)
i = ((CanvasEdgeLabel*)i)->canvasEdge();
if (i->type() == CANVAS_EDGEARROW)
i = ((CanvasEdgeArrow*)i)->canvasEdge();
if (i->type() == CANVAS_EDGE) {
GraphEdge* e = ((CanvasEdge*)i)->edge();
if (0)
qDebug("CallGraphView: Menu on Edge '%s'",
qPrintable(e->prettyName()));
c = e->call();
if (c) {
QString name = c->prettyName();
QString menuStr = tr("Go to '%1'")
.arg(GlobalConfig::shortenSymbol(name));
activateCall = popup.addAction(menuStr);
popup.addSeparator();
}
}
}
QAction* stopLayout = 0;
if (_renderProcess) {
stopLayout = popup.addAction(tr("Stop Layouting"));
popup.addSeparator();
}
addGoMenu(&popup);
popup.addSeparator();
QMenu* epopup = popup.addMenu(tr("Export Graph"));
QAction* exportAsDot = epopup->addAction(tr("As DOT file..."));
QAction* exportAsImage = epopup->addAction(tr("As Image..."));
popup.addSeparator();
QMenu* gpopup = popup.addMenu(tr("Graph"));
addCallerDepthMenu(gpopup);
addCalleeDepthMenu(gpopup);
addNodeLimitMenu(gpopup);
addCallLimitMenu(gpopup);
gpopup->addSeparator();
QAction* toggleSkipped;
toggleSkipped = gpopup->addAction(tr("Arrows for Skipped Calls"));
toggleSkipped->setCheckable(true);
toggleSkipped->setChecked(_showSkipped);
QAction* toggleExpand;
toggleExpand = gpopup->addAction(tr("Inner-cycle Calls"));
toggleExpand->setCheckable(true);
toggleExpand->setChecked(_expandCycles);
QAction* toggleCluster;
toggleCluster = gpopup->addAction(tr("Cluster Groups"));
toggleCluster->setCheckable(true);
toggleCluster->setChecked(_clusterGroups);
QMenu* vpopup = popup.addMenu(tr("Visualization"));
QAction* layoutCompact = vpopup->addAction(tr("Compact"));
layoutCompact->setCheckable(true);
layoutCompact->setChecked(_detailLevel == 0);
QAction* layoutNormal = vpopup->addAction(tr("Normal"));
layoutNormal->setCheckable(true);
layoutNormal->setChecked(_detailLevel == 1);
QAction* layoutTall = vpopup->addAction(tr("Tall"));
layoutTall->setCheckable(true);
layoutTall->setChecked(_detailLevel == 2);
addLayoutMenu(&popup);
addZoomPosMenu(&popup);
QAction* a = popup.exec(e->globalPos());
if (a == activateFunction)
activated(f);
else if (a == activateCycle)
activated(cycle);
else if (a == activateCall)
activated(c);
else if (a == stopLayout)
stopRendering();
else if (a == exportAsDot) {
TraceFunction* f = activeFunction();
if (!f) return;
QString n;
n = QFileDialog::getSaveFileName(this,
tr("Export Graph As DOT file"),
QString(), tr("Graphviz (*.dot)"));
if (!n.isEmpty()) {
GraphExporter ge(TraceItemView::data(), f, eventType(),
groupType(), n);
ge.setGraphOptions(this);
ge.writeDot();
}
}
else if (a == exportAsImage) {
// write current content of canvas as image to file
if (!_scene) return;
QString n;
n = QFileDialog::getSaveFileName(this,
tr("Export Graph As Image"),
QString(),
tr("Images (*.png *.jpg)"));
if (!n.isEmpty()) {
QRect r = _scene->sceneRect().toRect();
QPixmap pix(r.width(), r.height());
QPainter p(&pix);
_scene->render( &p );
pix.save(n);
}
}
else if (a == toggleSkipped) {
_showSkipped = !_showSkipped;
refresh();
}
else if (a == toggleExpand) {
_expandCycles = !_expandCycles;
refresh();
}
else if (a == toggleCluster) {
_clusterGroups = !_clusterGroups;
refresh();
}
else if (a == layoutCompact) {
_detailLevel = 0;
refresh();
}
else if (a == layoutNormal) {
_detailLevel = 1;
refresh();
}
else if (a == layoutTall) {
_detailLevel = 2;
refresh();
}
}
CallGraphView::ZoomPosition CallGraphView::zoomPos(QString s)
{
if (s == QStringLiteral("TopLeft"))
return TopLeft;
if (s == QStringLiteral("TopRight"))
return TopRight;
if (s == QStringLiteral("BottomLeft"))
return BottomLeft;
if (s == QStringLiteral("BottomRight"))
return BottomRight;
if (s == QStringLiteral("Automatic"))
return Auto;
if (s == QStringLiteral("Hide"))
return Hide;
return DEFAULT_ZOOMPOS;
}
QString CallGraphView::zoomPosString(ZoomPosition p)
{
if (p == TopLeft)
return QStringLiteral("TopLeft");
if (p == TopRight)
return QStringLiteral("TopRight");
if (p == BottomLeft)
return QStringLiteral("BottomLeft");
if (p == BottomRight)
return QStringLiteral("BottomRight");
if (p == Auto)
return QStringLiteral("Automatic");
if (p == Hide)
return QStringLiteral("Hide");
return QString();
}
void CallGraphView::restoreOptions(const QString& prefix, const QString& postfix)
{
ConfigGroup* g = ConfigStorage::group(prefix, postfix);
_maxCallerDepth = g->value(QStringLiteral("MaxCaller"), DEFAULT_MAXCALLER).toInt();
_maxCalleeDepth = g->value(QStringLiteral("MaxCallee"), DEFAULT_MAXCALLEE).toInt();
_funcLimit = g->value(QStringLiteral("FuncLimit"), DEFAULT_FUNCLIMIT).toDouble();
_callLimit = g->value(QStringLiteral("CallLimit"), DEFAULT_CALLLIMIT).toDouble();
_showSkipped = g->value(QStringLiteral("ShowSkipped"), DEFAULT_SHOWSKIPPED).toBool();
_expandCycles = g->value(QStringLiteral("ExpandCycles"), DEFAULT_EXPANDCYCLES).toBool();
_clusterGroups = g->value(QStringLiteral("ClusterGroups"), DEFAULT_CLUSTERGROUPS).toBool();
_detailLevel = g->value(QStringLiteral("DetailLevel"), DEFAULT_DETAILLEVEL).toInt();
_layout = GraphOptions::layout(g->value(QStringLiteral("Layout"),
layoutString(DEFAULT_LAYOUT)).toString());
_zoomPosition = zoomPos(g->value(QStringLiteral("ZoomPosition"),
zoomPosString(DEFAULT_ZOOMPOS)).toString());
delete g;
}
void CallGraphView::saveOptions(const QString& prefix, const QString& postfix)
{
ConfigGroup* g = ConfigStorage::group(prefix + postfix);
g->setValue(QStringLiteral("MaxCaller"), _maxCallerDepth, DEFAULT_MAXCALLER);
g->setValue(QStringLiteral("MaxCallee"), _maxCalleeDepth, DEFAULT_MAXCALLEE);
g->setValue(QStringLiteral("FuncLimit"), _funcLimit, DEFAULT_FUNCLIMIT);
g->setValue(QStringLiteral("CallLimit"), _callLimit, DEFAULT_CALLLIMIT);
g->setValue(QStringLiteral("ShowSkipped"), _showSkipped, DEFAULT_SHOWSKIPPED);
g->setValue(QStringLiteral("ExpandCycles"), _expandCycles, DEFAULT_EXPANDCYCLES);
g->setValue(QStringLiteral("ClusterGroups"), _clusterGroups, DEFAULT_CLUSTERGROUPS);
g->setValue(QStringLiteral("DetailLevel"), _detailLevel, DEFAULT_DETAILLEVEL);
g->setValue(QStringLiteral("Layout"), layoutString(_layout), layoutString(DEFAULT_LAYOUT));
g->setValue(QStringLiteral("ZoomPosition"), zoomPosString(_zoomPosition),
zoomPosString(DEFAULT_ZOOMPOS));
delete g;
}