Skip to content
Permalink
Browse files

[FEATURE] Add project load profile times to debugging tools dock

Now it's possible to get a breakdown of the various stages of
project load, in order to identify the causes of slow project
load times.

TODO: in 99% of cases this will be caused by one or more layers,
so it would be nice to further break down the individual layer load
times, e.g. so that for a WFS layer we see the time taken for
the initial server capabilities queries, etc...
For now, you just get the overall load time of each individual
layer.
  • Loading branch information
nyalldawson committed Jun 28, 2020
1 parent 42a1ed1 commit 005ac8af2bf8898af24c9b053e15596124cbce88
@@ -161,6 +161,16 @@ and starts recording the run time of the operation.

~QgsScopedRuntimeProfile();

void switchTask( const QString &name );
%Docstring
Switches the current task managed by the scoped profile to a new task with the given ``name``.
The current task will be finalised before switching.

This is useful for reusing an existing scoped runtime profiler with multi-step processes.

.. versionadded:: 3.14
%End

};


@@ -23,6 +23,7 @@
#include "qgscompositionconverter.h"
#include "qgsreadwritecontext.h"
#include "qgsstyleentityvisitor.h"
#include "qgsruntimeprofiler.h"
#include <QMessageBox>

QgsLayoutManager::QgsLayoutManager( QgsProject *project )
@@ -148,6 +149,7 @@ bool QgsLayoutManager::readXml( const QDomElement &element, const QDomDocument &
//restore each composer
bool result = true;
QDomNodeList composerNodes = element.elementsByTagName( QStringLiteral( "Composer" ) );
QgsScopedRuntimeProfile profile( tr( "Loading QGIS 2.x compositions" ), QStringLiteral( "projectload" ) );
for ( int i = 0; i < composerNodes.size(); ++i )
{
// This legacy title is the Composer "title" (that can be overridden by the Composition "name")
@@ -195,13 +197,18 @@ bool QgsLayoutManager::readXml( const QDomElement &element, const QDomDocument &
QgsReadWriteContext context;
context.setPathResolver( mProject->pathResolver() );

profile.switchTask( tr( "Creating layouts" ) );

// restore layouts
const QDomNodeList layoutNodes = layoutsElem.childNodes();
for ( int i = 0; i < layoutNodes.size(); ++i )
{
if ( layoutNodes.at( i ).nodeName() != QStringLiteral( "Layout" ) )
continue;

const QString layoutName = layoutNodes.at( i ).toElement().attribute( QStringLiteral( "name" ) );
QgsScopedRuntimeProfile profile( layoutName, QStringLiteral( "projectload" ) );

std::unique_ptr< QgsPrintLayout > l = qgis::make_unique< QgsPrintLayout >( mProject );
l->undoStack()->blockCommands( true );
if ( !l->readLayoutXml( layoutNodes.at( i ).toElement(), doc, context ) )
@@ -216,9 +223,13 @@ bool QgsLayoutManager::readXml( const QDomElement &element, const QDomDocument &
}
}
//reports
profile.switchTask( tr( "Creating reports" ) );
const QDomNodeList reportNodes = element.elementsByTagName( QStringLiteral( "Report" ) );
for ( int i = 0; i < reportNodes.size(); ++i )
{
const QString layoutName = reportNodes.at( i ).toElement().attribute( QStringLiteral( "name" ) );
QgsScopedRuntimeProfile profile( layoutName, QStringLiteral( "projectload" ) );

std::unique_ptr< QgsReport > r = qgis::make_unique< QgsReport >( mProject );
if ( !r->readLayoutXml( reportNodes.at( i ).toElement(), doc, context ) )
{
@@ -61,6 +61,7 @@
#include "qgsprojectdisplaysettings.h"
#include "qgsprojecttimesettings.h"
#include "qgsvectortilelayer.h"
#include "qgsruntimeprofiler.h"

#include <algorithm>
#include <QApplication>
@@ -1059,6 +1060,7 @@ bool QgsProject::_getMapLayers( const QDomDocument &doc, QList<QDomNode> &broken
emit layerLoaded( 0, nl.count() );

// order layers based on their dependencies
QgsScopedRuntimeProfile profile( tr( "Sorting layers" ), QStringLiteral( "projectload" ) );
QgsLayerDefinition::DependencySorter depSorter( doc );
if ( depSorter.hasCycle() || depSorter.hasMissingDependency() )
return false;
@@ -1075,6 +1077,8 @@ bool QgsProject::_getMapLayers( const QDomDocument &doc, QList<QDomNode> &broken
if ( !name.isNull() )
emit loadingLayer( tr( "Loading layer %1" ).arg( name ) );

profile.switchTask( name );

if ( element.attribute( QStringLiteral( "embedded" ) ) == QLatin1String( "1" ) )
{
createEmbeddedLayer( element.attribute( QStringLiteral( "id" ) ), readPath( element.attribute( QStringLiteral( "project" ) ) ), brokenNodes, true, flags );
@@ -1109,6 +1113,7 @@ bool QgsProject::addLayer( const QDomElement &layerElem, QList<QDomNode> &broken
QgsDebugMsgLevel( "Layer type is " + type, 4 );
std::unique_ptr<QgsMapLayer> mapLayer;

QgsScopedRuntimeProfile profile( tr( "Create layer" ), QStringLiteral( "projectload" ) );
if ( type == QLatin1String( "vector" ) )
{
mapLayer = qgis::make_unique<QgsVectorLayer>();
@@ -1154,6 +1159,8 @@ bool QgsProject::addLayer( const QDomElement &layerElem, QList<QDomNode> &broken
QgsMapLayer::ReadFlags layerFlags = QgsMapLayer::ReadFlags();
if ( flags & QgsProject::ReadFlag::FlagDontResolveLayers )
layerFlags |= QgsMapLayer::FlagDontResolveLayers;

profile.switchTask( tr( "Load layer source" ) );
bool layerIsValid = mapLayer->readLayerXml( layerElem, context, layerFlags ) && mapLayer->isValid();
QList<QgsMapLayer *> newLayers;
newLayers << mapLayer.get();
@@ -1251,6 +1258,9 @@ bool QgsProject::readProjectFile( const QString &filename, QgsProject::ReadFlags
QFile projectFile( filename );
clearError();

QgsApplication::profiler()->clear( QStringLiteral( "projectload" ) );
QgsScopedRuntimeProfile profile( tr( "Setting up translations" ), QStringLiteral( "projectload" ) );

QgsSettings settings;

QString localeFileName = QStringLiteral( "%1_%2" ).arg( QFileInfo( projectFile.fileName() ).baseName(), settings.value( QStringLiteral( "locale/userLocale" ), QString() ).toString() );
@@ -1261,6 +1271,7 @@ bool QgsProject::readProjectFile( const QString &filename, QgsProject::ReadFlags
mTranslator->load( localeFileName, QFileInfo( projectFile.fileName() ).absolutePath() );
}

profile.switchTask( tr( "Reading project file" ) );
std::unique_ptr<QDomDocument> doc( new QDomDocument( QStringLiteral( "qgis" ) ) );

if ( !projectFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
@@ -1304,6 +1315,7 @@ bool QgsProject::readProjectFile( const QString &filename, QgsProject::ReadFlags
QgsProjectVersion fileVersion = getVersion( *doc );
const QgsProjectVersion thisVersion( Qgis::version() );

profile.switchTask( tr( "Updating project file" ) );
if ( thisVersion > fileVersion )
{
QgsLogger::warning( "Loading a file that was saved with an older "
@@ -1320,6 +1332,7 @@ bool QgsProject::readProjectFile( const QString &filename, QgsProject::ReadFlags
}

// start new project, just keep the file name and auxiliary storage
profile.switchTask( tr( "Creating auxiliary storage" ) );
QString fileName = mFile.fileName();
std::unique_ptr<QgsAuxiliaryStorage> aStorage = std::move( mAuxiliaryStorage );
clear();
@@ -1330,6 +1343,7 @@ bool QgsProject::readProjectFile( const QString &filename, QgsProject::ReadFlags
mSaveVersion = fileVersion;

// now get any properties
profile.switchTask( tr( "Reading properties" ) );
_getProperties( *doc, mProperties );

// now get the data defined server properties
@@ -1485,7 +1499,7 @@ bool QgsProject::readProjectFile( const QString &filename, QgsProject::ReadFlags
}

// read the layer tree from project file

profile.switchTask( tr( "Loading layer tree" ) );
mRootGroup->setCustomProperty( QStringLiteral( "loading" ), 1 );

QDomElement layerTreeElem = doc->documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
@@ -1501,6 +1515,8 @@ bool QgsProject::readProjectFile( const QString &filename, QgsProject::ReadFlags
mLayerTreeRegistryBridge->setEnabled( false );

// get the map layers
profile.switchTask( tr( "Reading map layers" ) );

QList<QDomNode> brokenNodes;
bool clean = _getMapLayers( *doc, brokenNodes, flags );

@@ -1521,6 +1537,7 @@ bool QgsProject::readProjectFile( const QString &filename, QgsProject::ReadFlags

// Resolve references to other layers
// Needs to be done here once all dependent layers are loaded
profile.switchTask( tr( "Resolving layer references" ) );
QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
for ( QMap<QString, QgsMapLayer *>::iterator it = layers.begin(); it != layers.end(); ++it )
{
@@ -1530,12 +1547,13 @@ bool QgsProject::readProjectFile( const QString &filename, QgsProject::ReadFlags
mLayerTreeRegistryBridge->setEnabled( true );

// load embedded groups and layers
profile.switchTask( tr( "Loading embedded layers" ) );
loadEmbeddedNodes( mRootGroup, flags );

// now that layers are loaded, we can resolve layer tree's references to the layers
profile.switchTask( tr( "Resolving references" ) );
mRootGroup->resolveReferences( this );


if ( !layerTreeElem.isNull() )
{
mRootGroup->readLayerOrderFromXml( layerTreeElem );
@@ -1576,16 +1594,23 @@ bool QgsProject::readProjectFile( const QString &filename, QgsProject::ReadFlags

mRootGroup->removeCustomProperty( QStringLiteral( "loading" ) );

profile.switchTask( tr( "Loading map themes" ) );
mMapThemeCollection.reset( new QgsMapThemeCollection( this ) );
emit mapThemeCollectionChanged();
mMapThemeCollection->readXml( *doc );

profile.switchTask( tr( "Loading label settings" ) );
mLabelingEngineSettings->readSettingsFromProject( this );
emit labelingEngineSettingsChanged();

profile.switchTask( tr( "Loading annotations" ) );
mAnnotationManager->readXml( doc->documentElement(), context );
if ( !( flags & QgsProject::ReadFlag::FlagDontLoadLayouts ) )
{
profile.switchTask( tr( "Loading layouts" ) );
mLayoutManager->readXml( doc->documentElement(), *doc );
}
profile.switchTask( tr( "Loading bookmarks" ) );
mBookmarkManager->readXml( doc->documentElement(), *doc );

// reassign change dependencies now that all layers are loaded
@@ -1595,9 +1620,11 @@ bool QgsProject::readProjectFile( const QString &filename, QgsProject::ReadFlags
it.value()->setDependencies( it.value()->dependencies() );
}

profile.switchTask( tr( "Loading snapping settings" ) );
mSnappingConfig.readProject( *doc );
mAvoidIntersectionsMode = static_cast<AvoidIntersectionsMode>( readNumEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsMode" ), static_cast<int>( AvoidIntersectionsMode::AvoidIntersectionsLayers ) ) );

profile.switchTask( tr( "Loading view settings" ) );
// restore older project scales settings
mViewSettings->setUseProjectScales( readBoolEntry( QStringLiteral( "Scales" ), QStringLiteral( "/useProjectScales" ) ) );
const QStringList scales = readListEntry( QStringLiteral( "Scales" ), QStringLiteral( "/ScalesList" ) );
@@ -1621,21 +1648,28 @@ bool QgsProject::readProjectFile( const QString &filename, QgsProject::ReadFlags
mViewSettings->readXml( viewSettingsElement, context );

// restore time settings
profile.switchTask( tr( "Loading temporal settings" ) );
QDomElement timeSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectTimeSettings" ) );
if ( !timeSettingsElement.isNull() )
mTimeSettings->readXml( timeSettingsElement, context );

profile.switchTask( tr( "Loading display settings" ) );
QDomElement displaySettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectDisplaySettings" ) );
if ( !displaySettingsElement.isNull() )
mDisplaySettings->readXml( displaySettingsElement, context );

profile.switchTask( tr( "Updating variables" ) );
emit customVariablesChanged();
profile.switchTask( tr( "Updating CRS" ) );
emit crsChanged();
emit ellipsoidChanged( ellipsoid() );

// read the project: used by map canvas and legend
profile.switchTask( tr( "Reading external settings" ) );
emit readProject( *doc );
emit readProjectWithContext( *doc, context );

profile.switchTask( tr( "Updating interface" ) );
emit snappingConfigChanged( mSnappingConfig );
emit avoidIntersectionsModeChanged();
emit topologicalEditingChanged();
@@ -570,11 +570,18 @@ QgsRuntimeProfilerNode *QgsRuntimeProfiler::index2node( const QModelIndex &index
//

QgsScopedRuntimeProfile::QgsScopedRuntimeProfile( const QString &name, const QString &group )
: mGroup( group )
{
QgsApplication::profiler()->start( name, group );
QgsApplication::profiler()->start( name, mGroup );
}

QgsScopedRuntimeProfile::~QgsScopedRuntimeProfile()
{
QgsApplication::profiler()->end();
QgsApplication::profiler()->end( mGroup );
}

void QgsScopedRuntimeProfile::switchTask( const QString &name )
{
QgsApplication::profiler()->end( mGroup );
QgsApplication::profiler()->start( name, mGroup );
}
@@ -323,6 +323,20 @@ class CORE_EXPORT QgsScopedRuntimeProfile
*/
~QgsScopedRuntimeProfile();

/**
* Switches the current task managed by the scoped profile to a new task with the given \a name.
* The current task will be finalised before switching.
*
* This is useful for reusing an existing scoped runtime profiler with multi-step processes.
*
* \since QGIS 3.14
*/
void switchTask( const QString &name );

private:

QString mGroup;

};


0 comments on commit 005ac8a

Please sign in to comment.
You can’t perform that action at this time.