/**************************************************************************** ** ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of Qt 3D Studio. ** ** $QT_BEGIN_LICENSE:GPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 or (at your option) any later version ** approved by the KDE Free Qt Foundation. The licenses are as published by ** the Free Software Foundation and appearing in the file LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "q3dsprofiler_p.h" #include "q3dsscenemanager_p.h" #include "q3dsengine_p.h" #include #if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) #include #include #endif #ifdef Q_OS_LINUX #include #include #endif #ifdef Q_OS_MACOS #include #include #include #endif QT_BEGIN_NAMESPACE Q3DSProfiler::Q3DSProfiler() { m_frameData.reserve(64 * 1024); } Q3DSProfiler::~Q3DSProfiler() { for (auto c : m_objectDestroyConnections) QObject::disconnect(c); } void Q3DSProfiler::resetForNewScene(Q3DSSceneManager *sceneManager) { for (auto c : m_objectDestroyConnections) QObject::disconnect(c); m_subMeshData.clear(); m_subPresProfilers.clear(); m_frameData.clear(); m_objectData.clear(); m_objectDestroyConnections.clear(); m_sceneManager = sceneManager; m_presentation = m_sceneManager->m_presentation; Q_ASSERT(m_presentation); m_presentationName = QFileInfo(m_presentation->sourceFile()).fileName(); } void Q3DSProfiler::setEnabled(bool enabled) { if (m_enabled == enabled) return; m_enabled = enabled; } void Q3DSProfiler::reportNewFrame(float deltaMs, const Q3DSRenderLoopStats *rlStats) { if (!m_enabled) return; FrameData d; d.deltaMs = deltaMs; d.quickDeltaMs = rlStats ? rlStats->quickDeltaMs : 0.0f; d.usedDedicatedQuickRenderThread = rlStats ? rlStats->usingSGRenderThread : false; d.usedDefaultQt3DRenderAspectWithOwnRenderThread = rlStats ? rlStats->usingAsyncRenderAspect : false; m_frameData.append(d); } void Q3DSProfiler::updateFrameStats(qint64 globalFrameCounter) { if (!m_enabled) return; Q_ASSERT(!m_frameData.isEmpty()); FrameData &d(m_frameData.last()); d.globalFrameCounter = globalFrameCounter; d.wasDirty = m_sceneManager->m_wasDirty; } void Q3DSProfiler::trackNewObject(QObject *obj, ObjectType type, const QByteArray &id, const char *info, ...) { if (!m_enabled) return; va_list ap; va_start(ap, info); vtrackNewObject(obj, type, id, info, ap); va_end(ap); } void Q3DSProfiler::trackNewObject(QObject *obj, ObjectType type, const QByteArray &id, const QString &info) { if (!m_enabled) return; ObjectData objd(obj, type, id); objd.info = info; m_objectData.insert(type, objd); m_objectDestroyConnections.append(QObject::connect(obj, &QObject::destroyed, [this, obj, type, id]() { m_objectData.remove(type, ObjectData(obj, type, id)); })); } void Q3DSProfiler::vtrackNewObject(QObject *obj, ObjectType type, const QByteArray &id, const char *info, va_list args) { if (!m_enabled) return; ObjectData objd(obj, type, id); objd.info = QString::vasprintf(info, args); m_objectData.insert(type, objd); m_objectDestroyConnections.append(QObject::connect(obj, &QObject::destroyed, [this, obj, type, id]() { m_objectData.remove(type, ObjectData(obj, type, id)); })); } void Q3DSProfiler::updateObjectInfo(QObject *obj, ObjectType type, const QByteArray &id, const char *info, ...) { auto it = m_objectData.find(type); while (it != m_objectData.end() && it.key() == type) { if (it->obj == obj) { va_list ap; va_start(ap, info); it->info = QString::vasprintf(info, ap); it->id = id; va_end(ap); break; } ++it; } } void Q3DSProfiler::reportTotalParseBuildTime(qint64 ms) { m_totalParseBuildTime = ms; } void Q3DSProfiler::reportBehaviorStats(qint64 loadTimeMs, int activeCount) { m_behaviorLoadTime = loadTimeMs; m_behaviorActiveCount = activeCount; } void Q3DSProfiler::reportTimeAfterBuildUntilFirstFrameAction(qint64 ms) { m_firstFrameActionTime = ms; } void Q3DSProfiler::reportSubMeshData(Q3DSMesh *mesh, const SubMeshData &data) { if (!m_enabled) return; m_subMeshData.insert(mesh, data); } void Q3DSProfiler::registerSubPresentationProfiler(Q3DSProfiler *p) { p->m_mainProfiler = this; m_subPresProfilers.append(p); } Q3DSProfiler *Q3DSProfiler::mainPresentationProfiler() { return m_mainProfiler ? m_mainProfiler : this; } void Q3DSProfiler::reportQt3DSceneGraphRoot(Qt3DCore::QEntity *rootEntity) { m_qt3dSceneGraphRoots.append(rootEntity); } void Q3DSProfiler::reportFrameGraphRoot(Qt3DRender::QFrameGraphNode *fgNode) { m_frameGraphRoot = fgNode; } void Q3DSProfiler::reportFrameGraphStopNode(Qt3DRender::QFrameGraphNode *fgNode) { m_fgStopNodes.insert(fgNode); } bool Q3DSProfiler::hasLogChanged() { bool b = m_logChanged; m_logChanged = false; return b; } void Q3DSProfiler::clearLog() { m_log.clear(); Q3DSEngine::clearLog(); m_logChanged = true; } void Q3DSProfiler::addLog(const QString &msg) { m_log.append(msg); m_logChanged = true; } void Q3DSProfiler::sendDataInputValueChange(const QString &dataInputName, const QVariant &value) { m_sceneManager->setDataInputValue(dataInputName, value); } void Q3DSProfiler::setLayerCaching(bool enabled) { m_sceneManager->setLayerCaching(enabled); for (Q3DSProfiler *subPresProfiler : *subPresentationProfilers()) subPresProfiler->m_sceneManager->setLayerCaching(enabled); } float Q3DSProfiler::cpuLoadForCurrentProcess() { if (!m_cpuLoadTimer.isValid()) { m_cpuLoadTimer.start(); } else { // query and calculate every 500 ms only, return the cached value otherwise if (m_cpuLoadTimer.elapsed() < 500) return m_lastCpuLoad; m_cpuLoadTimer.restart(); } #if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) quint64 kernelTimeSys, userTimeSys, kernelTimeProc, userTimeProc; FILETIME creationFTime, exitFTime, kernelFTime, userFTime, idleFTime; if (!GetSystemTimes(&idleFTime, &kernelFTime, &userFTime)) return m_lastCpuLoad; kernelTimeSys = quint64(kernelFTime.dwHighDateTime) << 32 | quint64(kernelFTime.dwLowDateTime); userTimeSys = quint64(userFTime.dwHighDateTime) << 32 | quint64(userFTime.dwLowDateTime); if (!GetProcessTimes(GetCurrentProcess(), &creationFTime, &exitFTime, &kernelFTime, &userFTime)) return m_lastCpuLoad; kernelTimeProc = quint64(kernelFTime.dwHighDateTime) << 32 | quint64(kernelFTime.dwLowDateTime); userTimeProc = quint64(userFTime.dwHighDateTime) << 32 | quint64(userFTime.dwLowDateTime); float d = (kernelTimeSys - m_lastKernelTimeSys) + (userTimeSys - m_lastUserTimeSys); if (d) m_lastCpuLoad = ((kernelTimeProc - m_lastKernelTimeProc) + (userTimeProc - m_lastUserTimeProc)) / d * 100.0f; m_lastKernelTimeSys = kernelTimeSys; m_lastUserTimeSys = userTimeSys; m_lastKernelTimeProc = kernelTimeProc; m_lastUserTimeProc = userTimeProc; #elif defined(Q_OS_LINUX) if (!m_numCpus) { QFile f(QLatin1String("/proc/cpuinfo")); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return m_lastCpuLoad; for ( ; ; ) { QByteArray line = f.readLine(); if (line.isEmpty()) break; if (line.startsWith(QByteArrayLiteral("processor"))) ++m_numCpus; } } if (!m_numCpus) return m_lastCpuLoad; tms processTimes; clock_t timestamp = times(&processTimes); if (timestamp > m_lastTimestamp) { float d = (timestamp - m_lastTimestamp); // behave like adb top, max is 100% so divide by number of cores m_lastCpuLoad = ((processTimes.tms_stime - m_lastKernel) + (processTimes.tms_utime - m_lastUser)) / d / m_numCpus * 100.0f; } m_lastTimestamp = timestamp; m_lastKernel = processTimes.tms_stime; m_lastUser = processTimes.tms_utime; #elif defined(Q_OS_MACOS) // Get thread times mach_port_t task = mach_task_self(); task_thread_times_info threadTimes = {}; mach_msg_type_number_t outCount = TASK_THREAD_TIMES_INFO_COUNT; kern_return_t retVal = task_info(task, TASK_THREAD_TIMES_INFO, task_info_t(&threadTimes), &outCount); if (retVal != KERN_SUCCESS) return m_lastCpuLoad; // Get task info mach_task_basic_info_data_t taskInfo = {}; outCount = MACH_TASK_BASIC_INFO_COUNT; retVal = task_info(task, MACH_TASK_BASIC_INFO, task_info_t(&taskInfo), &outCount); if (retVal != KERN_SUCCESS) return m_lastCpuLoad; struct timeval userTimeVal, systemTimeVal, totalTimeVal; // Thread info contains alive time userTimeVal.tv_sec = threadTimes.user_time.seconds; userTimeVal.tv_usec = threadTimes.user_time.microseconds; systemTimeVal.tv_sec = threadTimes.system_time.seconds; systemTimeVal.tv_usec = threadTimes.system_time.microseconds; // Add user time and system time to total time timeradd(&userTimeVal, &systemTimeVal, &totalTimeVal); // Task info has terminated time userTimeVal.tv_sec = taskInfo.user_time.seconds; userTimeVal.tv_usec = taskInfo.user_time.microseconds; systemTimeVal.tv_sec = taskInfo.system_time.seconds; systemTimeVal.tv_usec = taskInfo.system_time.microseconds; // Add user time and system time to total time timeradd(&userTimeVal, &totalTimeVal, &totalTimeVal); timeradd(&systemTimeVal, &totalTimeVal, &totalTimeVal); // Get current time struct timeval now; int error = gettimeofday(&now, nullptr); if (error) return m_lastCpuLoad; // Convert everything to microseconds qint64 timestamp = timeValToMicroseconds(now); qint64 totalTaskTime = timeValToMicroseconds(totalTimeVal); // On first execution just store the current values if (m_lastTimestamp == 0) { m_lastTimestamp = timestamp; m_lastTaskTime = totalTaskTime; return m_lastCpuLoad; } // Calculate percentage of CPU time this task used over delta time qint64 taskDeltaTime = totalTaskTime - m_lastTaskTime; qint64 totalDeltaTime = timestamp - m_lastTimestamp; if (totalDeltaTime == 0) return m_lastCpuLoad; m_lastTimestamp = timestamp; m_lastTaskTime = totalTaskTime; m_lastCpuLoad = (taskDeltaTime * 100.f) / float(totalDeltaTime) / float(m_macOSProfiler.numActiveProcessors()); #endif return m_lastCpuLoad; } QPair Q3DSProfiler::memUsageForCurrentProcess() { if (!m_memUsageTimer.isValid()) { m_memUsageTimer.start(); } else { // query and calculate every 500 ms only, return the cached value otherwise if (m_memUsageTimer.elapsed() < 500) return m_lastMemUsage; m_memUsageTimer.restart(); } #if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) PROCESS_MEMORY_COUNTERS_EX memInfo; if (!GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast(&memInfo), sizeof(memInfo))) return m_lastMemUsage; qint64 physMappedSize = memInfo.WorkingSetSize; qint64 commitCharge = memInfo.PrivateUsage; m_lastMemUsage = { physMappedSize, commitCharge }; #elif defined(Q_OS_LINUX) QFile f(QLatin1String("/proc/self/status")); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return m_lastMemUsage; qint64 physMappedSize = 0, commitCharge = 0; for ( ; ; ) { QByteArray line = f.readLine(); if (line.isEmpty()) break; if (line.startsWith(QByteArrayLiteral("VmSize:"))) { QByteArrayList c = line.mid(7).trimmed().split(' '); if (!c.isEmpty()) commitCharge = c[0].toLongLong() * 1000; } else if (line.startsWith(QByteArrayLiteral("VmRSS:"))) { QByteArrayList c = line.mid(6).trimmed().split(' '); if (!c.isEmpty()) physMappedSize = c[0].toLongLong() * 1000; } if (physMappedSize && commitCharge) break; } f.close(); m_lastMemUsage = { physMappedSize, commitCharge }; #elif defined(Q_OS_MACOS) mach_task_basic_info_data_t taskinfo = {}; mach_msg_type_number_t outCount = MACH_TASK_BASIC_INFO_COUNT; kern_return_t error = task_info( mach_task_self(), MACH_TASK_BASIC_INFO, task_info_t(&taskinfo), &outCount); if (error == KERN_SUCCESS) { qint64 virtualSize = qint64(taskinfo.virtual_size); qint64 residentSize = qint64(taskinfo.resident_size); m_lastMemUsage = { residentSize, virtualSize }; } #endif return m_lastMemUsage; } #if defined(Q_OS_MACOS) qint64 Q3DSProfiler::timeValToMicroseconds(const struct timeval& timeVal) { static const int microsecondsPerSecond = 1000000; qint64 retVal = timeVal.tv_sec; retVal *= microsecondsPerSecond; retVal += timeVal.tv_usec; return retVal; } #endif QT_END_NAMESPACE