1 /***************************************************************************
2 qgsruntimeprofiler.cpp
3 ---------------------
4 begin : June 2016
5 copyright : (C) 2016 by Nathan Woodrow
6 email : woodrow dot nathan at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15 #include "qgsruntimeprofiler.h"
16 #include "qgslogger.h"
17 #include "qgis.h"
18 #include "qgsapplication.h"
19 #include <QSet>
20 #include <QThreadStorage>
21
22 QgsRuntimeProfiler *QgsRuntimeProfiler::sMainProfiler = nullptr;
23
24
25 //
26 // QgsRuntimeProfilerNode
27 //
28
QgsRuntimeProfilerNode(const QString & group,const QString & name)29 QgsRuntimeProfilerNode::QgsRuntimeProfilerNode( const QString &group, const QString &name )
30 : mName( name )
31 , mGroup( group )
32 {
33
34 }
35
36 QgsRuntimeProfilerNode::~QgsRuntimeProfilerNode() = default;
37
fullParentPath() const38 QStringList QgsRuntimeProfilerNode::fullParentPath() const
39 {
40 QStringList res;
41 if ( mParent )
42 {
43 res = mParent->fullParentPath();
44 const QString parentName = mParent->data( Name ).toString();
45 if ( !parentName.isEmpty() )
46 res << parentName;
47 }
48 return res;
49 }
50
data(int role) const51 QVariant QgsRuntimeProfilerNode::data( int role ) const
52 {
53 switch ( role )
54 {
55 case Qt::DisplayRole:
56 case Qt::ToolTipRole:
57 case Name:
58 return mName;
59
60 case Group:
61 return mGroup;
62
63 case Elapsed:
64 return mElapsed;
65
66 case ParentElapsed:
67 return mParent ? ( mParent->elapsed() > 0 ? mParent->elapsed() : mParent->totalElapsedTimeForChildren( mGroup ) ) : 0;
68 }
69 return QVariant();
70 }
71
addChild(std::unique_ptr<QgsRuntimeProfilerNode> child)72 void QgsRuntimeProfilerNode::addChild( std::unique_ptr<QgsRuntimeProfilerNode> child )
73 {
74 if ( !child )
75 return;
76
77 Q_ASSERT( !child->mParent );
78 child->mParent = this;
79
80 mChildren.emplace_back( std::move( child ) );
81 }
82
indexOf(QgsRuntimeProfilerNode * child) const83 int QgsRuntimeProfilerNode::indexOf( QgsRuntimeProfilerNode *child ) const
84 {
85 Q_ASSERT( child->mParent == this );
86 const auto it = std::find_if( mChildren.begin(), mChildren.end(), [&]( const std::unique_ptr<QgsRuntimeProfilerNode> &p )
87 {
88 return p.get() == child;
89 } );
90 if ( it != mChildren.end() )
91 return std::distance( mChildren.begin(), it );
92 return -1;
93 }
94
child(const QString & group,const QString & name)95 QgsRuntimeProfilerNode *QgsRuntimeProfilerNode::child( const QString &group, const QString &name )
96 {
97 for ( auto &it : mChildren )
98 {
99 if ( it->data( Group ).toString() == group && it->data( Name ).toString() == name )
100 return it.get();
101 }
102 return nullptr;
103 }
104
childAt(int index)105 QgsRuntimeProfilerNode *QgsRuntimeProfilerNode::childAt( int index )
106 {
107 Q_ASSERT( static_cast< std::size_t >( index ) < mChildren.size() );
108 return mChildren[ index ].get();
109 }
110
clear()111 void QgsRuntimeProfilerNode::clear()
112 {
113 mChildren.clear();
114 }
115
removeChildAt(int index)116 void QgsRuntimeProfilerNode::removeChildAt( int index )
117 {
118 Q_ASSERT( static_cast< std::size_t >( index ) < mChildren.size() );
119 mChildren.erase( mChildren.begin() + index );
120 }
121
start()122 void QgsRuntimeProfilerNode::start()
123 {
124 mProfileTime.restart();
125 }
126
stop()127 void QgsRuntimeProfilerNode::stop()
128 {
129 mElapsed = mProfileTime.elapsed() / 1000.0;
130 }
131
setElapsed(double time)132 void QgsRuntimeProfilerNode::setElapsed( double time )
133 {
134 mElapsed = time;
135 }
136
elapsed() const137 double QgsRuntimeProfilerNode::elapsed() const
138 {
139 return mElapsed;
140 }
141
totalElapsedTimeForChildren(const QString & group) const142 double QgsRuntimeProfilerNode::totalElapsedTimeForChildren( const QString &group ) const
143 {
144 double total = 0;
145 for ( auto &it : mChildren )
146 {
147 if ( it->data( Group ).toString() == group )
148 total += it->elapsed();
149 }
150 return total;
151 }
152
153 //
154 // QgsRuntimeProfiler
155 //
156
QgsRuntimeProfiler()157 QgsRuntimeProfiler::QgsRuntimeProfiler()
158 : mRootNode( std::make_unique< QgsRuntimeProfilerNode >( QString(), QString() ) )
159 {
160
161 }
162
163 QgsRuntimeProfiler::~QgsRuntimeProfiler() = default;
164
threadLocalInstance()165 QgsRuntimeProfiler *QgsRuntimeProfiler::threadLocalInstance()
166 {
167 static QThreadStorage<QgsRuntimeProfiler> sInstances;
168 QgsRuntimeProfiler *profiler = &sInstances.localData();
169
170 if ( !qApp || profiler->thread() == qApp->thread() )
171 sMainProfiler = profiler;
172
173 if ( !profiler->mInitialized )
174 profiler->setupConnections();
175
176 return profiler;
177 }
178
beginGroup(const QString & name)179 void QgsRuntimeProfiler::beginGroup( const QString &name )
180 {
181 start( name );
182 }
183
endGroup()184 void QgsRuntimeProfiler::endGroup()
185 {
186 end();
187 }
188
childGroups(const QString & parent,const QString & group) const189 QStringList QgsRuntimeProfiler::childGroups( const QString &parent, const QString &group ) const
190 {
191 QgsRuntimeProfilerNode *parentNode = pathToNode( group, parent );
192 if ( !parentNode )
193 return QStringList();
194
195 QStringList res;
196 res.reserve( parentNode->childCount() );
197 for ( int i = 0; i < parentNode->childCount(); ++i )
198 {
199 QgsRuntimeProfilerNode *child = parentNode->childAt( i );
200 if ( child->data( QgsRuntimeProfilerNode::Group ).toString() == group )
201 res << child->data( QgsRuntimeProfilerNode::Name ).toString();
202 }
203 return res;
204 }
205
start(const QString & name,const QString & group)206 void QgsRuntimeProfiler::start( const QString &name, const QString &group )
207 {
208 std::unique_ptr< QgsRuntimeProfilerNode > node = std::make_unique< QgsRuntimeProfilerNode >( group, name );
209 node->start();
210
211 QgsRuntimeProfilerNode *child = node.get();
212 if ( !mCurrentStack[ group ].empty() )
213 {
214 QgsRuntimeProfilerNode *parent = mCurrentStack[group ].top();
215
216 const QModelIndex parentIndex = node2index( parent );
217 beginInsertRows( parentIndex, parent->childCount(), parent->childCount() );
218 parent->addChild( std::move( node ) );
219 endInsertRows();
220 }
221 else
222 {
223 beginInsertRows( QModelIndex(), mRootNode->childCount(), mRootNode->childCount() );
224 mRootNode->addChild( std::move( node ) );
225 endInsertRows();
226 }
227
228 mCurrentStack[group].push( child );
229 emit started( group, child->fullParentPath(), name );
230
231 if ( !mGroups.contains( group ) )
232 {
233 mGroups.insert( group );
234 emit groupAdded( group );
235 }
236 }
237
end(const QString & group)238 void QgsRuntimeProfiler::end( const QString &group )
239 {
240 if ( mCurrentStack[group].empty() )
241 return;
242
243 QgsRuntimeProfilerNode *node = mCurrentStack[group].top();
244 mCurrentStack[group].pop();
245 node->stop();
246
247 const QModelIndex nodeIndex = node2index( node );
248 const QModelIndex col2Index = index( nodeIndex.row(), 1, nodeIndex.parent() );
249 emit dataChanged( nodeIndex, nodeIndex );
250 emit dataChanged( col2Index, col2Index );
251 // parent item has data changed too, cos the overall time elapsed will have changed!
252 QModelIndex parentIndex = nodeIndex.parent();
253 while ( parentIndex.isValid() )
254 {
255 const QModelIndex parentCol2Index = index( parentIndex.row(), 1, parentIndex.parent() );
256 emit dataChanged( parentIndex, parentIndex );
257 emit dataChanged( parentCol2Index, parentCol2Index );
258 parentIndex = parentIndex.parent();
259 }
260
261 emit ended( group, node->fullParentPath(), node->data( QgsRuntimeProfilerNode::Name ).toString(), node->data( QgsRuntimeProfilerNode::Elapsed ).toDouble() );
262 }
263
profileTime(const QString & name,const QString & group) const264 double QgsRuntimeProfiler::profileTime( const QString &name, const QString &group ) const
265 {
266 QgsRuntimeProfilerNode *node = pathToNode( group, name );
267 if ( !node )
268 return 0;
269
270 return node->data( QgsRuntimeProfilerNode::Elapsed ).toDouble();
271 }
272
clear(const QString & group)273 void QgsRuntimeProfiler::clear( const QString &group )
274 {
275 for ( int row = mRootNode->childCount() - 1; row >= 0; row-- )
276 {
277 if ( mRootNode->childAt( row )->data( QgsRuntimeProfilerNode::Group ).toString() == group )
278 {
279 beginRemoveRows( QModelIndex(), row, row );
280 mRootNode->removeChildAt( row );
281 endRemoveRows();
282 }
283 }
284 }
285
totalTime(const QString & group)286 double QgsRuntimeProfiler::totalTime( const QString &group )
287 {
288 if ( QgsRuntimeProfilerNode *node = pathToNode( group, QString() ) )
289 return node->elapsed();
290
291 return 0;
292 }
293
groupIsActive(const QString & group) const294 bool QgsRuntimeProfiler::groupIsActive( const QString &group ) const
295 {
296 return !mCurrentStack.value( group ).empty();
297 }
298
translateGroupName(const QString & group)299 QString QgsRuntimeProfiler::translateGroupName( const QString &group )
300 {
301 if ( group == QLatin1String( "startup" ) )
302 return tr( "Startup" );
303 else if ( group == QLatin1String( "projectload" ) )
304 return tr( "Project Load" );
305 else if ( group == QLatin1String( "render" ) )
306 return tr( "Map Render" );
307 return QString();
308 }
309
rowCount(const QModelIndex & parent) const310 int QgsRuntimeProfiler::rowCount( const QModelIndex &parent ) const
311 {
312 QgsRuntimeProfilerNode *n = index2node( parent );
313 if ( !n )
314 return 0;
315
316 return n->childCount();
317 }
318
columnCount(const QModelIndex & parent) const319 int QgsRuntimeProfiler::columnCount( const QModelIndex &parent ) const
320 {
321 Q_UNUSED( parent )
322 return 2;
323 }
324
index(int row,int column,const QModelIndex & parent) const325 QModelIndex QgsRuntimeProfiler::index( int row, int column, const QModelIndex &parent ) const
326 {
327 if ( column < 0 || column >= columnCount( parent ) ||
328 row < 0 || row >= rowCount( parent ) )
329 return QModelIndex();
330
331 QgsRuntimeProfilerNode *n = index2node( parent );
332 if ( !n )
333 return QModelIndex(); // have no children
334
335 return createIndex( row, column, n->childAt( row ) );
336 }
337
parent(const QModelIndex & child) const338 QModelIndex QgsRuntimeProfiler::parent( const QModelIndex &child ) const
339 {
340 if ( !child.isValid() )
341 return QModelIndex();
342
343 if ( QgsRuntimeProfilerNode *n = index2node( child ) )
344 {
345 return indexOfParentNode( n->parent() ); // must not be null
346 }
347 else
348 {
349 Q_ASSERT( false );
350 return QModelIndex();
351 }
352 }
353
data(const QModelIndex & index,int role) const354 QVariant QgsRuntimeProfiler::data( const QModelIndex &index, int role ) const
355 {
356 if ( !index.isValid() || index.column() > 2 )
357 return QVariant();
358
359 QgsRuntimeProfilerNode *node = index2node( index );
360 if ( !node )
361 return QVariant();
362
363 switch ( index.column() )
364 {
365 case 0:
366 return node->data( role );
367
368 case 1:
369 {
370 switch ( role )
371 {
372 case Qt::DisplayRole:
373 case Qt::InitialSortOrderRole:
374 return node->data( QgsRuntimeProfilerNode::Elapsed );
375
376 default:
377 break;
378 }
379 return node->data( role );
380 }
381 }
382 return QVariant();
383 }
384
headerData(int section,Qt::Orientation orientation,int role) const385 QVariant QgsRuntimeProfiler::headerData( int section, Qt::Orientation orientation, int role ) const
386 {
387 switch ( role )
388 {
389 case Qt::DisplayRole:
390 {
391 if ( orientation == Qt::Horizontal )
392 {
393 switch ( section )
394 {
395 case 0:
396 return tr( "Task" );
397 case 1:
398 return tr( "Time (seconds)" );
399 default:
400 return QVariant();
401 }
402 }
403 else
404 {
405 return QVariant();
406 }
407 }
408
409 default:
410 return QAbstractItemModel::headerData( section, orientation, role );
411 }
412 }
413
otherProfilerStarted(const QString & group,const QStringList & path,const QString & name)414 void QgsRuntimeProfiler::otherProfilerStarted( const QString &group, const QStringList &path, const QString &name )
415 {
416 QgsRuntimeProfilerNode *parentNode = mRootNode.get();
417 for ( const QString &part : path )
418 {
419 QgsRuntimeProfilerNode *child = parentNode->child( group, part );
420 if ( !child )
421 {
422 std::unique_ptr< QgsRuntimeProfilerNode > newChild = std::make_unique< QgsRuntimeProfilerNode >( group, part );
423
424 const QModelIndex parentIndex = node2index( parentNode );
425 beginInsertRows( parentIndex, parentNode->childCount(), parentNode->childCount() );
426 QgsRuntimeProfilerNode *next = newChild.get();
427 parentNode->addChild( std::move( newChild ) );
428 endInsertRows();
429 parentNode = next;
430 }
431 else
432 {
433 parentNode = child;
434 }
435 }
436
437 if ( parentNode->child( group, name ) )
438 return;
439
440 const QModelIndex parentIndex = node2index( parentNode );
441 beginInsertRows( parentIndex, parentNode->childCount(), parentNode->childCount() );
442 parentNode->addChild( std::make_unique< QgsRuntimeProfilerNode >( group, name ) );
443 endInsertRows();
444
445 if ( !mGroups.contains( group ) )
446 {
447 mGroups.insert( group );
448 emit groupAdded( group );
449 }
450 }
451
otherProfilerEnded(const QString & group,const QStringList & path,const QString & name,double elapsed)452 void QgsRuntimeProfiler::otherProfilerEnded( const QString &group, const QStringList &path, const QString &name, double elapsed )
453 {
454 QgsRuntimeProfilerNode *parentNode = mRootNode.get();
455 for ( const QString &part : path )
456 {
457 QgsRuntimeProfilerNode *child = parentNode->child( group, part );
458 if ( !child )
459 {
460 std::unique_ptr< QgsRuntimeProfilerNode > newChild = std::make_unique< QgsRuntimeProfilerNode >( group, part );
461
462 const QModelIndex parentIndex = node2index( parentNode );
463 beginInsertRows( parentIndex, parentNode->childCount(), parentNode->childCount() );
464 QgsRuntimeProfilerNode *next = newChild.get();
465 parentNode->addChild( std::move( newChild ) );
466 endInsertRows();
467 parentNode = next;
468 }
469 else
470 {
471 parentNode = child;
472 }
473 }
474
475 QgsRuntimeProfilerNode *destNode = parentNode->child( group, name );
476 if ( !destNode )
477 {
478 std::unique_ptr< QgsRuntimeProfilerNode > node = std::make_unique< QgsRuntimeProfilerNode >( group, name );
479 destNode = node.get();
480 const QModelIndex parentIndex = node2index( parentNode );
481 beginInsertRows( parentIndex, parentNode->childCount(), parentNode->childCount() );
482 parentNode->addChild( std::move( node ) );
483 endInsertRows();
484 }
485
486 destNode->setElapsed( elapsed );
487
488 const QModelIndex nodeIndex = node2index( destNode );
489 const QModelIndex col2Index = index( nodeIndex.row(), 1, nodeIndex.parent() );
490 emit dataChanged( nodeIndex, nodeIndex );
491 emit dataChanged( col2Index, col2Index );
492 // parent item has data changed too, cos the overall time elapsed will have changed!
493 QModelIndex parentIndex = nodeIndex.parent();
494 while ( parentIndex.isValid() )
495 {
496 const QModelIndex parentCol2Index = index( parentIndex.row(), 1, parentIndex.parent() );
497 emit dataChanged( parentIndex, parentIndex );
498 emit dataChanged( parentCol2Index, parentCol2Index );
499 parentIndex = parentIndex.parent();
500 }
501 }
502
setupConnections()503 void QgsRuntimeProfiler::setupConnections()
504 {
505 mInitialized = true;
506
507 Q_ASSERT( sMainProfiler );
508
509 if ( sMainProfiler != this )
510 {
511 connect( this, &QgsRuntimeProfiler::started, sMainProfiler, &QgsRuntimeProfiler::otherProfilerStarted );
512 connect( this, &QgsRuntimeProfiler::ended, sMainProfiler, &QgsRuntimeProfiler::otherProfilerEnded );
513 }
514 }
515
pathToNode(const QString & group,const QString & path) const516 QgsRuntimeProfilerNode *QgsRuntimeProfiler::pathToNode( const QString &group, const QString &path ) const
517 {
518 const QStringList parts = path.split( '/' );
519 QgsRuntimeProfilerNode *res = mRootNode.get();
520 for ( const QString &part : parts )
521 {
522 if ( part.isEmpty() )
523 continue;
524
525 res = res->child( group, part );
526 if ( !res )
527 break;
528 }
529 return res;
530 }
531
pathToNode(const QString & group,const QStringList & path) const532 QgsRuntimeProfilerNode *QgsRuntimeProfiler::pathToNode( const QString &group, const QStringList &path ) const
533 {
534 QgsRuntimeProfilerNode *res = mRootNode.get();
535 for ( const QString &part : path )
536 {
537 res = res->child( group, part );
538 if ( !res )
539 break;
540 }
541 return res;
542 }
543
node2index(QgsRuntimeProfilerNode * node) const544 QModelIndex QgsRuntimeProfiler::node2index( QgsRuntimeProfilerNode *node ) const
545 {
546 if ( !node || !node->parent() )
547 return QModelIndex(); // this is the only root item -> invalid index
548
549 const QModelIndex parentIndex = node2index( node->parent() );
550
551 const int row = node->parent()->indexOf( node );
552 Q_ASSERT( row >= 0 );
553 return index( row, 0, parentIndex );
554 }
555
indexOfParentNode(QgsRuntimeProfilerNode * parentNode) const556 QModelIndex QgsRuntimeProfiler::indexOfParentNode( QgsRuntimeProfilerNode *parentNode ) const
557 {
558 Q_ASSERT( parentNode );
559
560 QgsRuntimeProfilerNode *grandParentNode = parentNode->parent();
561 if ( !grandParentNode )
562 return QModelIndex(); // root node -> invalid index
563
564 const int row = grandParentNode->indexOf( parentNode );
565 Q_ASSERT( row >= 0 );
566
567 return createIndex( row, 0, parentNode );
568 }
569
index2node(const QModelIndex & index) const570 QgsRuntimeProfilerNode *QgsRuntimeProfiler::index2node( const QModelIndex &index ) const
571 {
572 if ( !index.isValid() )
573 return mRootNode.get();
574
575 return reinterpret_cast<QgsRuntimeProfilerNode *>( index.internalPointer() );
576 }
577
578
579 //
580 // QgsScopedRuntimeProfile
581 //
582
QgsScopedRuntimeProfile(const QString & name,const QString & group)583 QgsScopedRuntimeProfile::QgsScopedRuntimeProfile( const QString &name, const QString &group )
584 : mGroup( group )
585 {
586 QgsApplication::profiler()->start( name, mGroup );
587 }
588
~QgsScopedRuntimeProfile()589 QgsScopedRuntimeProfile::~QgsScopedRuntimeProfile()
590 {
591 QgsApplication::profiler()->end( mGroup );
592 }
593
switchTask(const QString & name)594 void QgsScopedRuntimeProfile::switchTask( const QString &name )
595 {
596 QgsApplication::profiler()->end( mGroup );
597 QgsApplication::profiler()->start( name, mGroup );
598 }
599