Revision control
++ // 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 <QtCore/QStandardPaths>
++ #endif
++
++ #include <QtCore/QThreadPool>
++ #include <QtCore/QCoreApplication>
++ #include <QtCore/QFile>
++ #include <QtCore/QDateTime>
++ #include <QtCore/QUrl>
++ #include <QtCore/QDir>
++ #include <QtGui/QDesktopServices>
++
++ #include <Qt3DCore/QAspectEngine>
++ #include <Qt3DCore/QAbstractAspect>
++ #include <Qt3DCore/private/qabstractaspect_p.h>
++ #include <Qt3DCore/private/qaspectengine_p.h>
++ #include <Qt3DCore/private/aspectcommanddebugger_p.h>
++
++ 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<JobRunStats>;
++ 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<JobRunStats>;
++ 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<JobRunStats>;
++
++ // 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<JobRunStats> *storage : std::as_const(m_localStorages))
++ header.jobCount += storage->size();
++
++ m_traceFile->write(reinterpret_cast<char *>(&header), sizeof(FrameHeader));
++
++ for (QList<JobRunStats> *storage : std::as_const(m_localStorages)) {
++ for (const JobRunStats &stat : *storage)
++ m_traceFile->write(reinterpret_cast<const char *>(&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<char *>(&header), sizeof(FrameHeader));
++
++ for (const JobRunStats &stat : *m_submissionStorage)
++ m_traceFile->write(reinterpret_cast<const char *>(&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<quint64>(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<quint64>(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("<unnamed>");
++ }
++
++ 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()
++ {
++ #if QT_CONFIG(desktopservices)
++ QDesktopServices::openUrl(QUrl::fromLocalFile(QDir::currentPath()));
++ #endif
++ }
++
++ 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<QObject *>();
++ if (obj) {
++ auto reply = qobject_cast<Qt3DCore::Debug::AsynchronousCommandReply*>(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"