QGIS API Documentation 4.1.0-Master (26185ffb827)
Loading...
Searching...
No Matches
qgseventtracing.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgseventtracing.cpp
3 --------------------------------------
4 Date : October 2019
5 Copyright : (C) 2019 by Martin Dobias
6 Email : wonder dot sk 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
16#include "qgsconfig.h"
17#include "qgseventtracing.h"
18
19#include <future>
20
21#include <QCoreApplication>
22#include <QFile>
23#include <QString>
24#include <QThread>
25
26#ifdef HAVE_TRACY
27#include "qgslogger.h"
28
29#include "tracy/Tracy.hpp"
30#include "tracy/TracyC.h"
31#endif
32
33using namespace Qt::StringLiterals;
34
36
37using namespace Qt::StringLiterals;
38
39struct TraceItem
40{
41 QgsEventTracing::EventType type;
42 uint threadId;
43 qint64 timestamp;
44 QString category;
45 QString name;
46 QString id;
47};
48
50static bool sIsTracing = false;
52Q_GLOBAL_STATIC( QElapsedTimer, sTracingTimer )
54Q_GLOBAL_STATIC( QVector<TraceItem>, sTraceEvents )
56Q_GLOBAL_STATIC( QMutex, sTraceEventsMutex )
57
58
59bool QgsEventTracing::startTracing()
60{
61 if ( sIsTracing )
62 return false;
63
64 sIsTracing = true;
65 sTraceEventsMutex()->lock();
66 sTracingTimer()->start();
67 sTraceEvents()->clear();
68 sTraceEvents()->reserve( 1000 );
69 sTraceEventsMutex()->unlock();
70 return true;
71}
72
73bool QgsEventTracing::stopTracing()
74{
75 if ( !sIsTracing )
76 return false;
77
78 sIsTracing = false;
79 sTracingTimer()->invalidate();
80 return false;
81}
82
83bool QgsEventTracing::isTracingEnabled()
84{
85 return sIsTracing;
86}
87
88static char _eventTypeToChar( QgsEventTracing::EventType type )
89{
90 switch ( type )
91 {
92 case QgsEventTracing::Begin:
93 return 'B';
94 case QgsEventTracing::End:
95 return 'E';
96 case QgsEventTracing::Instant:
97 return 'i';
98 case QgsEventTracing::AsyncBegin:
99 return 'b';
100 case QgsEventTracing::AsyncEnd:
101 return 'e';
102 }
103 return '?';
104}
105
106bool QgsEventTracing::writeTrace( const QString &fileName )
107{
108 if ( sIsTracing )
109 return false;
110
111 QFile f( fileName );
112 if ( !f.open( QIODevice::WriteOnly ) )
113 return false;
114
115 f.write( "{\n\"traceEvents\": [\n" );
116
117 bool first = true;
118 for ( const auto &item : *sTraceEvents() )
119 {
120 if ( !first )
121 f.write( ",\n" );
122 else
123 first = false;
124 const char t = _eventTypeToChar( item.type );
125 QString msg = u" {\"cat\": \"%1\", \"pid\": 1, \"tid\": %2, \"ts\": %3, \"ph\": \"%4\", \"name\": \"%5\""_s.arg( item.category ).arg( item.threadId ).arg( item.timestamp ).arg( t ).arg( item.name );
126
127 // for instant events we always set them as global (currently not supporting instant events at thread scope)
128 if ( item.type == Instant )
129 msg += ", \"s\": \"g\""_L1;
130
131 // async events also need to have ID associated
132 if ( item.type == AsyncBegin || item.type == AsyncEnd )
133 msg += u", \"id\": \"%1\""_s.arg( item.id );
134
135 msg += " }";
136
137 f.write( msg.toUtf8() );
138 }
139
140 f.write( "\n]\n}\n" );
141 f.close();
142 return true;
143}
144
145#ifdef HAVE_TRACY
146// Utility type for storing Tracy zones based on this key
147struct NameAndId
148{
149 QString name;
150 QString id;
151
152 bool operator==( const NameAndId &other ) const { return name == other.name && id == other.id; }
153
154 friend size_t qHash( const NameAndId &key, size_t seed = 0 ) { return qHash( key.name, seed ) ^ qHash( key.id, seed ); }
155};
156
157struct TracyZoneState
158{
159 TracyCZoneCtx zoneCtx;
160 std::optional<qsizetype> asyncDummyId;
161};
162
163struct TracyZoneDummyThread
164{
165 QByteArray nameHandle;
166 bool occupied;
167};
168#endif
169
170void QgsEventTracing::addEvent( QgsEventTracing::EventType type, const QString &category, const QString &name, const QString &id )
171{
172#ifdef HAVE_TRACY
173 static QMutex zonesRegistryLock;
174 static QHash<NameAndId, TracyZoneState> zonesByNameAndId;
175 // Tracy needs zones within a single thread to be properly nested (like XML
176 // tags), which doesn't work for async events that cross threads. To work
177 // around this, we create dummy threads as needed and fill them
178 // top-to-bottom.
179 static QList<TracyZoneDummyThread> asyncDummyThreads;
180 QString zoneNameStr = u"[%1] %2"_s.arg( category, name );
181 QByteArray zoneName = ( zoneNameStr ).toLocal8Bit();
182 NameAndId nameAndId { zoneName, id };
183
184 std::optional<qsizetype> usingDummyThread = std::nullopt;
185 switch ( type )
186 {
187 case AsyncBegin:
188 {
189 QMutexLocker<QMutex> lock( &zonesRegistryLock );
190 for ( qsizetype i = 0; i < asyncDummyThreads.size(); i++ )
191 {
192 TracyZoneDummyThread &dummyThread = asyncDummyThreads[i];
193 if ( !dummyThread.occupied )
194 {
195 // Unoccupied dummy thread, use it
196 TracyCFiberEnter( dummyThread.nameHandle.data() );
197 usingDummyThread = i;
198 dummyThread.occupied = true;
199 break;
200 }
201 }
202 if ( !usingDummyThread )
203 {
204 // No unoccupied thread, make one
205 asyncDummyThreads.append( {
206 u"Async %1"_s.arg( asyncDummyThreads.size(), 3, 10, '0' ).toLocal8Bit(),
207 true,
208 } );
209 TracyCFiberEnter( asyncDummyThreads.last().nameHandle.data() );
210 usingDummyThread = asyncDummyThreads.size() - 1;
211 }
212 [[fallthrough]];
213 }
214 case Begin:
215 {
216 QMutexLocker<QMutex> lock( &zonesRegistryLock );
217 if ( zonesByNameAndId.contains( nameAndId ) )
218 {
219 QgsDebugError( u"Tried to re-begin zone! (name: '%1', id: '%1')"_s.arg( zoneNameStr, id ) );
220 return;
221 }
222 // Use dummy values for source location, since we don't have it
223 uint64_t srcloc = ___tracy_alloc_srcloc_name( 0, "", 0, "", 0, zoneName.constData(), zoneName.size(), 0 );
224 TracyCZoneCtx zone = ___tracy_emit_zone_begin_alloc( srcloc, true );
225 if ( id.size() )
226 {
227 QByteArray extraText = id.toLocal8Bit();
228 TracyCZoneText( zone, extraText.data(), extraText.size() );
229 }
230 zonesByNameAndId[nameAndId] = { zone, usingDummyThread };
231 break;
232 }
233 case AsyncEnd:
234 case End:
235 {
236 QMutexLocker<QMutex> lock( &zonesRegistryLock );
237 auto zoneIt = zonesByNameAndId.constFind( nameAndId );
238 if ( zoneIt == zonesByNameAndId.end() )
239 {
240 QgsDebugError( u"Tried to end unstarted zone! "_s + zoneNameStr );
241 return;
242 }
243
244 if ( zoneIt->asyncDummyId )
245 {
246 TracyZoneDummyThread &dummyThread = asyncDummyThreads[*zoneIt->asyncDummyId];
247 TracyCFiberEnter( dummyThread.nameHandle.data() );
248 dummyThread.occupied = false;
249 usingDummyThread = true;
250 }
251
252 ___tracy_emit_zone_end( zoneIt->zoneCtx );
253 zonesByNameAndId.erase( zoneIt );
254 break;
255 }
256 case Instant:
257 TracyMessageC( zoneName.constData(), zoneName.size(), 0x4444EE );
258 break;
259 }
260 if ( usingDummyThread )
261 {
262 TracyCFiberLeave;
263 }
264#endif
265
266 QgsEventTracing::addEventToQgisTrace( type, category, name, id );
267}
268
269void QgsEventTracing::addEventToQgisTrace( QgsEventTracing::EventType type, const QString &category, const QString &name, const QString &id )
270{
271 if ( !sIsTracing )
272 return;
273
274 sTraceEventsMutex()->lock();
275 TraceItem item;
276 item.type = type;
277 item.timestamp = sTracingTimer()->nsecsElapsed() / 1000;
278 if ( QThread::currentThread() == QCoreApplication::instance()->thread() )
279 item.threadId = 0; // to make it show up first
280 else
281 item.threadId = static_cast<uint>( reinterpret_cast<quint64>( QThread::currentThreadId() ) );
282 item.category = category;
283 item.name = name;
284 item.id = id;
285 sTraceEvents()->append( item );
286 sTraceEventsMutex()->unlock();
287}
288
289void QgsEventTracing::setFloatVariable( const char *name, double value, bool continuous )
290{
291#ifdef HAVE_TRACY
292 if ( !continuous ) // Disable interpolation
293 TracyPlotConfig( name, tracy::PlotFormatType::Number, true, false, 0 );
294 TracyPlot( name, value );
295#else
296 Q_UNUSED( name );
297 Q_UNUSED( value );
298 Q_UNUSED( continuous );
299#endif
300}
301
302void QgsEventTracing::setIntVariable( const char *name, int64_t value, bool continuous )
303{
304#ifdef HAVE_TRACY
305 if ( !continuous ) // Disable interpolation
306 TracyPlotConfig( name, tracy::PlotFormatType::Number, true, false, 0 );
307 TracyPlot( name, value );
308#else
309 Q_UNUSED( name );
310 Q_UNUSED( value );
311 Q_UNUSED( continuous );
312#endif
313}
314
315size_t QgsScopedEvent::sNextId = 0;
316
uint qHash(const QVariant &variant)
Hash for QVariant.
Definition qgis.cpp:611
Q_GLOBAL_STATIC(QReadWriteLock, sDefinitionCacheLock)
bool operator==(const QgsFeatureIterator &fi1, const QgsFeatureIterator &fi2)
#define QgsDebugError(str)
Definition qgslogger.h:59