// Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB). // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qsysteminformationservice_p.h" #include "qsysteminformationservice_p_p.h" #ifdef Q_OS_ANDROID #include #endif #include #include #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE namespace { struct FrameHeader { FrameHeader() : frameId(0) , jobCount(0) , frameType(WorkerJob) { } enum FrameType { WorkerJob = 0, Submission }; quint32 frameId; quint16 jobCount; quint16 frameType; // Submission or worker job }; } namespace Qt3DCore { QSystemInformationServicePrivate::QSystemInformationServicePrivate(QAspectEngine *aspectEngine, const QString &description) : QAbstractServiceProviderPrivate(QServiceLocator::SystemInformation, description) , m_aspectEngine(aspectEngine) , m_submissionStorage(nullptr) , m_frameId(0) , m_commandDebugger(nullptr) { m_traceEnabled = qEnvironmentVariableIsSet("QT3D_TRACE_ENABLED"); m_graphicsTraceEnabled = qEnvironmentVariableIsSet("QT3D_GRAPHICS_TRACE_ENABLED"); if (m_traceEnabled || m_graphicsTraceEnabled) m_jobsStatTimer.start(); const bool commandServerEnabled = qEnvironmentVariableIsSet("QT3D_COMMAND_SERVER_ENABLED"); if (commandServerEnabled) { m_commandDebugger = new Debug::AspectCommandDebugger(q_func()); m_commandDebugger->initialize(); } } QSystemInformationServicePrivate::~QSystemInformationServicePrivate() = default; QSystemInformationServicePrivate *QSystemInformationServicePrivate::get(QSystemInformationService *q) { return q->d_func(); } // Called by the jobs void QSystemInformationServicePrivate::addJobLogStatsEntry(QSystemInformationServicePrivate::JobRunStats &stats) { if (!m_traceEnabled && !m_graphicsTraceEnabled) return; if (!m_jobStatsCached.hasLocalData()) { auto jobVector = new QList; m_jobStatsCached.setLocalData(jobVector); QMutexLocker lock(&m_localStoragesMutex); m_localStorages.push_back(jobVector); } m_jobStatsCached.localData()->push_back(stats); } // Called from Submission thread (which can be main thread in Manual drive mode) void QSystemInformationServicePrivate::addSubmissionLogStatsEntry(QSystemInformationServicePrivate::JobRunStats &stats) { if (!m_traceEnabled && !m_graphicsTraceEnabled) return; QMutexLocker lock(&m_localStoragesMutex); if (!m_jobStatsCached.hasLocalData()) { m_submissionStorage = new QList; m_jobStatsCached.setLocalData(m_submissionStorage); } // Handle the case where submission thread is also the main thread (Scene/Manual drive modes with no RenderThread) if (m_submissionStorage == nullptr && m_jobStatsCached.hasLocalData()) m_submissionStorage = new QList; // When having no submission thread this can be null m_submissionStorage->push_back(stats); } // Called after jobs have been executed (MainThread QAspectJobManager::enqueueJobs) void QSystemInformationServicePrivate::writeFrameJobLogStats() { if (!m_traceEnabled && !m_graphicsTraceEnabled) return; using JobRunStats = QSystemInformationServicePrivate::JobRunStats; if (!m_traceFile) { const QString fileName = QStringLiteral("trace_") + QCoreApplication::applicationName() + QDateTime::currentDateTime().toString(QStringLiteral("_yyMMdd-hhmmss_")) + QSysInfo::productType() + QStringLiteral("_") + QSysInfo::buildAbi() + QStringLiteral(".qt3d"); #ifdef Q_OS_ANDROID m_traceFile.reset(new QFile(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + QStringLiteral("/") + fileName)); #else // TODO fix for iOS m_traceFile.reset(new QFile(fileName)); #endif if (!m_traceFile->open(QFile::WriteOnly|QFile::Truncate)) qCritical("Failed to open trace file"); } // Write Aspect + Job threads { FrameHeader header; header.frameId = m_frameId; header.jobCount = 0; for (const QList *storage : std::as_const(m_localStorages)) header.jobCount += storage->size(); m_traceFile->write(reinterpret_cast(&header), sizeof(FrameHeader)); for (QList *storage : std::as_const(m_localStorages)) { for (const JobRunStats &stat : *storage) m_traceFile->write(reinterpret_cast(&stat), sizeof(JobRunStats)); storage->clear(); } } // Write submission thread { QMutexLocker lock(&m_localStoragesMutex); const qsizetype submissionJobSize = m_submissionStorage != nullptr ? m_submissionStorage->size() : 0; if (submissionJobSize > 0) { FrameHeader header; header.frameId = m_frameId; header.jobCount = submissionJobSize; header.frameType = FrameHeader::Submission; m_traceFile->write(reinterpret_cast(&header), sizeof(FrameHeader)); for (const JobRunStats &stat : *m_submissionStorage) m_traceFile->write(reinterpret_cast(&stat), sizeof(JobRunStats)); m_submissionStorage->clear(); } } m_traceFile->flush(); ++m_frameId; } void QSystemInformationServicePrivate::updateTracing() { if (m_traceEnabled || m_graphicsTraceEnabled) { if (!m_jobsStatTimer.isValid()) m_jobsStatTimer.start(); } else { m_traceFile.reset(); } } QTaskLogger::QTaskLogger(QSystemInformationService *service, const JobId &jobId, Type type) : m_service(service && service->isTraceEnabled() ? service : nullptr) , m_type(type) { m_stats.jobId = jobId; if (m_service) { m_stats.startTime = QSystemInformationServicePrivate::get(m_service)->m_jobsStatTimer.nsecsElapsed(); m_stats.threadId = reinterpret_cast(QThread::currentThreadId()); } } QTaskLogger::QTaskLogger(QSystemInformationService *service, const quint32 jobType, const quint32 instance, QTaskLogger::Type type) : m_service(service && service->isTraceEnabled() ? service : nullptr) , m_type(type) { m_stats.jobId.typeAndInstance[0] = jobType; m_stats.jobId.typeAndInstance[1] = instance; if (m_service) { m_stats.startTime = QSystemInformationServicePrivate::get(m_service)->m_jobsStatTimer.nsecsElapsed(); m_stats.threadId = reinterpret_cast(QThread::currentThreadId()); } } QTaskLogger::~QTaskLogger() { if (m_service) { auto dservice = QSystemInformationServicePrivate::get(m_service); if (m_stats.endTime == 0L) m_stats.endTime = dservice->m_jobsStatTimer.nsecsElapsed(); switch (m_type) { case AspectJob: dservice->addJobLogStatsEntry(m_stats); break; case Submission: dservice->addSubmissionLogStatsEntry(m_stats); break; } } } void QTaskLogger::end(qint64 t) { m_stats.endTime = t > 0 || !m_service ? t : QSystemInformationServicePrivate::get(m_service)->m_jobsStatTimer.nsecsElapsed(); } qint64 QTaskLogger::restart() { if (m_service) m_stats.startTime = QSystemInformationServicePrivate::get(m_service)->m_jobsStatTimer.nsecsElapsed(); return m_stats.startTime; } /* !\internal \class Qt3DCore::QSystemInformationService \inmodule Qt3DCore \brief Interface for a Qt3D system information service This is an interface class that should be subclassesd by providers of the system information service. */ /* Creates an instance of QSystemInformationService, with a \a description for the new service. This constructor is protected so only subclasses can instantiate a QSystemInformationService object. */ QSystemInformationService::QSystemInformationService(QAspectEngine *aspectEngine) : QAbstractServiceProvider(*new QSystemInformationServicePrivate(aspectEngine, QLatin1String("Default System Information Service"))) { } QSystemInformationService::QSystemInformationService(QAspectEngine *aspectEngine, const QString &description) : QAbstractServiceProvider(*new QSystemInformationServicePrivate(aspectEngine, description)) { } /* \internal */ QSystemInformationService::QSystemInformationService(QSystemInformationServicePrivate &dd) : QAbstractServiceProvider(dd) { } bool QSystemInformationService::isTraceEnabled() const { Q_D(const QSystemInformationService); return d->m_traceEnabled; } bool QSystemInformationService::isGraphicsTraceEnabled() const { Q_D(const QSystemInformationService); return d->m_graphicsTraceEnabled; } bool QSystemInformationService::isCommandServerEnabled() const { Q_D(const QSystemInformationService); return d->m_commandDebugger != nullptr; } void QSystemInformationService::setTraceEnabled(bool traceEnabled) { Q_D(QSystemInformationService); if (d->m_traceEnabled != traceEnabled) { d->m_traceEnabled = traceEnabled; emit traceEnabledChanged(d->m_traceEnabled); d->updateTracing(); } } void QSystemInformationService::setGraphicsTraceEnabled(bool graphicsTraceEnabled) { Q_D(QSystemInformationService); if (d->m_graphicsTraceEnabled != graphicsTraceEnabled) { d->m_graphicsTraceEnabled = graphicsTraceEnabled; emit graphicsTraceEnabledChanged(d->m_graphicsTraceEnabled); d->updateTracing(); } } /* \fn QStringList Qt3DCore::QSystemInformationService::aspectNames() const Returns a string list containing the names of all registered aspects. */ QStringList QSystemInformationService::aspectNames() const { Q_D(const QSystemInformationService); if (!d->m_aspectEngine) return {}; QStringList res; const auto aspects = d->m_aspectEngine->aspects(); if (aspects.isEmpty()) return { QLatin1String("No loaded aspects") }; QAspectEnginePrivate *dengine = QAspectEnginePrivate::get(d->m_aspectEngine); for (auto aspect: aspects) { const QString name = dengine->m_factory.aspectName(aspect); if (!name.isEmpty()) res << name; else res << QLatin1String(""); } return res; } /* \fn int Qt3DCore::QSystemInformationService::threadPoolThreadCount() const Returns the maximum number of threads in the Qt3D task manager's threadpool. */ int QSystemInformationService::threadPoolThreadCount() const { return QThreadPool::globalInstance()->maxThreadCount(); } void QSystemInformationService::writePreviousFrameTraces() { Q_D(QSystemInformationService); d->writeFrameJobLogStats(); } void QSystemInformationService::revealLogFolder() { QDesktopServices::openUrl(QUrl::fromLocalFile(QDir::currentPath())); } QVariant QSystemInformationService::executeCommand(const QString &command) { Q_D(QSystemInformationService); if (command == QLatin1String("tracing on")) { setTraceEnabled(true); return {isTraceEnabled()}; } if (command == QLatin1String("tracing off")) { setTraceEnabled(false); return {isTraceEnabled()}; } if (command == QLatin1String("glprofiling on")) { setGraphicsTraceEnabled(true); return {isTraceEnabled()}; } if (command == QLatin1String("glprofiling off")) { setGraphicsTraceEnabled(false); return {isTraceEnabled()}; } return d->m_aspectEngine->executeCommand(command); } void QSystemInformationService::dumpCommand(const QString &command) { QVariant res = executeCommand(command); QObject *obj = res.value(); if (obj) { auto reply = qobject_cast(obj); if (reply) { connect(reply, &Debug::AsynchronousCommandReply::finished, this, [reply]() { qWarning() << qPrintable( QLatin1String(reply->data()) ); }); return; } } qWarning() << qPrintable(res.toString()); } } QT_END_NAMESPACE #include "moc_qsysteminformationservice_p.cpp"