Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[api] Rework QgsRuntimeProfiler for flexibility
- Make profiler thread safe
- Allow profiling of other, non-startup task groups (e.g. project
load times, map render times)
  • Loading branch information
nyalldawson committed Jun 28, 2020
1 parent 3815438 commit 5c842a6
Show file tree
Hide file tree
Showing 9 changed files with 896 additions and 160 deletions.
60 changes: 53 additions & 7 deletions python/core/auto_generated/qgsruntimeprofiler.sip.in
Expand Up @@ -8,8 +8,19 @@



class QgsRuntimeProfiler
class QgsRuntimeProfiler : QAbstractItemModel
{
%Docstring

Provides a method of recording run time profiles of operations, allowing
easy recording of their overall run time.

QgsRuntimeProfiler is not usually instantied manually, but rather accessed
through :py:func:`QgsApplication.profiler()`.

This class is thread-safe only if accessed through :py:func:`QgsApplication.profiler()`.
If accessed in this way, operations can be profiled from non-main threads.
%End

%TypeHeaderCode
#include "qgsruntimeprofiler.h"
Expand All @@ -19,6 +30,11 @@ class QgsRuntimeProfiler
QgsRuntimeProfiler();
%Docstring
Constructor to create a new runtime profiler.

.. warning::

QgsRuntimeProfiler is not usually instantied manually, but rather accessed
through :py:func:`QgsApplication.profiler()`.
%End

void beginGroup( const QString &name ) /Deprecated/;
Expand All @@ -40,43 +56,73 @@ End the current active group.
use end() instead
%End

QStringList childGroups( const QString &parent = QString() ) const;
QStringList childGroups( const QString &parent = QString(), const QString &group = "startup" ) const;
%Docstring
Returns a list of all child groups with the specified ``parent``.

.. versionadded:: 3.14
%End

void start( const QString &name );
void start( const QString &name, const QString &group = "startup" );
%Docstring
Start a profile event with the given name.

:param name: The name of the profile event. Will have the name of
the active group appended after ending.
%End

void end();
void end( const QString &group = "startup" );
%Docstring
End the current profile event.
%End

double profileTime( const QString &name ) const;
double profileTime( const QString &name, const QString &group = "startup" ) const;
%Docstring
Returns the profile time for the specified ``name``.

.. versionadded:: 3.14
%End

void clear();
void clear( const QString &group = "startup" );
%Docstring
clear Clear all profile data.
%End

double totalTime();
double totalTime( const QString &group = "startup" );
%Docstring
The current total time collected in the profiler.

:return: The current total time collected in the profiler.
%End

QSet< QString > groups() const;
%Docstring
Returns the set of known groups.
%End

static QString translateGroupName( const QString &group );
%Docstring
Returns the translated name of a standard profile ``group``.
%End


virtual int rowCount( const QModelIndex &parent = QModelIndex() ) const;

virtual int columnCount( const QModelIndex &parent = QModelIndex() ) const;

virtual QModelIndex index( int row, int column, const QModelIndex &parent = QModelIndex() ) const;

virtual QModelIndex parent( const QModelIndex &child ) const;

virtual QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const;

virtual QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const;



void groupAdded( const QString &group );
%Docstring
Emitted when a new group has started being profiled.
%End

};
Expand Down
81 changes: 56 additions & 25 deletions src/app/devtools/profiler/qgsprofilerpanelwidget.cpp
Expand Up @@ -16,6 +16,7 @@
#include "qgsprofilerpanelwidget.h"
#include "qgsruntimeprofiler.h"
#include "qgslogger.h"
#include "qgis.h"
#include <QPainter>
#include <cmath>

Expand All @@ -29,35 +30,64 @@ QgsProfilerPanelWidget::QgsProfilerPanelWidget( QgsRuntimeProfiler *profiler, QW
{
setupUi( this );

mTreeWidget->setColumnCount( 2 );
mTreeWidget->setHeaderLabels( QStringList() << tr( "Task" ) << tr( "Time (seconds)" ) );
mTreeWidget->setSortingEnabled( true );
mProxyModel = new QgsProfilerProxyModel( profiler, this );

std::function< void( const QString &topLevel, QTreeWidgetItem *parentItem, double parentCost ) > addGroup;
addGroup = [&]( const QString & topLevel, QTreeWidgetItem * parentItem, double parentCost )
mTreeView->setModel( mProxyModel );
mTreeView->setSortingEnabled( true );

mTreeView->resizeColumnToContents( 0 );
mTreeView->resizeColumnToContents( 1 );

mTreeView->setItemDelegateForColumn( 1, new CostDelegate( QgsRuntimeProfilerNode::Elapsed, QgsRuntimeProfilerNode::ParentElapsed ) );

connect( mProfiler, &QgsRuntimeProfiler::groupAdded, this, [ = ]( const QString & group )
{
const QStringList children = mProfiler->childGroups( topLevel );
for ( const QString &child : children )
mCategoryComboBox->addItem( QgsRuntimeProfiler::translateGroupName( group ).isEmpty() ? group : QgsRuntimeProfiler::translateGroupName( group ), group );
if ( mCategoryComboBox->count() == 1 )
{
double profileTime = mProfiler->profileTime( topLevel.isEmpty() ? child : topLevel + '/' + child );
QTreeWidgetItem *item = new QTreeWidgetItem( QStringList() << child << QString::number( profileTime ) );
item->setData( 1, Qt::UserRole + 1, parentCost * 1000 );
item->setData( 1, Qt::InitialSortOrderRole, profileTime * 1000 );
if ( !parentItem )
mTreeWidget->addTopLevelItem( item );
else
parentItem->addChild( item );

addGroup( topLevel.isEmpty() ? child : topLevel + '/' + child, item, profileTime );
mCategoryComboBox->setCurrentIndex( 0 );
mProxyModel->setGroup( mCategoryComboBox->currentData().toString() );
}
};
const double totalTime = mProfiler->profileTime( QString() );
addGroup( QString(), nullptr, totalTime );
} );

connect( mCategoryComboBox, qgis::overload< int >::of( &QComboBox::currentIndexChanged ), this, [ = ]( int )
{
mProxyModel->setGroup( mCategoryComboBox->currentData().toString() );
} );

const QSet<QString> groups = mProfiler->groups();
for ( const QString &group : groups )
{
mCategoryComboBox->addItem( QgsRuntimeProfiler::translateGroupName( group ).isEmpty() ? group : QgsRuntimeProfiler::translateGroupName( group ), group );
}

if ( mCategoryComboBox->count() > 0 )
{
mCategoryComboBox->setCurrentIndex( 0 );
mProxyModel->setGroup( mCategoryComboBox->currentData().toString() );
}
}

mTreeWidget->resizeColumnToContents( 0 );
mTreeWidget->resizeColumnToContents( 1 );
//
// QgsProfilerProxyModel
//

mTreeWidget->setItemDelegateForColumn( 1, new CostDelegate( Qt::InitialSortOrderRole, Qt::UserRole + 1 ) );
QgsProfilerProxyModel::QgsProfilerProxyModel( QgsRuntimeProfiler *profiler, QObject *parent )
: QSortFilterProxyModel( parent )
{
setSourceModel( profiler );
}

void QgsProfilerProxyModel::setGroup( const QString &group )
{
mGroup = group;
invalidateFilter();
}

bool QgsProfilerProxyModel::filterAcceptsRow( int row, const QModelIndex &source_parent ) const
{
QModelIndex index = sourceModel()->index( row, 0, source_parent );
return sourceModel()->data( index, QgsRuntimeProfilerNode::Group ).toString() == mGroup;
}


Expand All @@ -79,14 +109,14 @@ CostDelegate::~CostDelegate() = default;
void CostDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
{
// TODO: handle negative values
const auto cost = index.data( m_sortRole ).toULongLong();
const auto cost = index.data( m_sortRole ).toDouble();
if ( cost == 0 )
{
QStyledItemDelegate::paint( painter, option, index );
return;
}

const auto totalCost = index.data( m_totalCostRole ).toULongLong();
const auto totalCost = index.data( m_totalCostRole ).toDouble();
const auto fraction = std::abs( float( cost ) / totalCost );

auto rect = option.rect;
Expand Down Expand Up @@ -123,3 +153,4 @@ void CostDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option,
QStyledItemDelegate::paint( painter, option, index );
}
}

23 changes: 23 additions & 0 deletions src/app/devtools/profiler/qgsprofilerpanelwidget.h
Expand Up @@ -19,9 +19,31 @@
#include "ui_qgsprofilerpanelbase.h"
#include <QTreeView>
#include <QStyledItemDelegate>
#include <QSortFilterProxyModel>

class QgsRuntimeProfiler;

class QgsProfilerProxyModel : public QSortFilterProxyModel
{
Q_OBJECT

public:

QgsProfilerProxyModel( QgsRuntimeProfiler *profiler, QObject *parent );

void setGroup( const QString &group );

protected:
bool filterAcceptsRow( int row, const QModelIndex &source_parent ) const override;

private:

QString mGroup;



};

/**
* \ingroup app
* \class QgsProfilerPanelWidget
Expand All @@ -43,6 +65,7 @@ class QgsProfilerPanelWidget : public QgsDevToolWidget, private Ui::QgsProfilerP
private:

QgsRuntimeProfiler *mProfiler = nullptr;
QgsProfilerProxyModel *mProxyModel = nullptr;

};

Expand Down
2 changes: 1 addition & 1 deletion src/app/devtools/profiler/qgsprofilerwidgetfactory.cpp
Expand Up @@ -18,7 +18,7 @@
#include "qgsapplication.h"

QgsProfilerWidgetFactory::QgsProfilerWidgetFactory( QgsRuntimeProfiler *profiler )
: QgsDevToolWidgetFactory( QObject::tr( "Startup Profiler" ), QgsApplication::getThemeIcon( QStringLiteral( "mIconStopwatch.svg" ) ) )
: QgsDevToolWidgetFactory( QObject::tr( "Profiler" ), QgsApplication::getThemeIcon( QStringLiteral( "mIconStopwatch.svg" ) ) )
, mProfiler( profiler )
{
}
Expand Down

0 comments on commit 5c842a6

Please sign in to comment.