Show detailed CPU load information in a tool-tip (#5970)

* Profiler rework

* Workaround for GCC bug

* Rollback QFile removal

* Use enum instead of a plain index to describe detailed stats

* Use the GCC workaround code for all compilers to avoid redundancy

* Update and fix comments

* Implement suggestions from review

* Split AudioEngine::renderNextBuffer() into separate functions, fix old formatting

* Remove QFile include

* Revert formatting changes

* Apply suggestion from review (remove unnecessary template parameter)

* Revert more formatting changes

* Convert DetailType to enum class

* DetailType enum class improvements suggested in review

* Use std::atomic for m_detailLoad

* RAII-style profiler probes

* Apply suggestions from code review

Co-authored-by: Dominic Clark <mrdomclark@gmail.com>

* Fix namespace comment

* Improve CPU load widget precision and use floats for load computations

* Atomic m_cpuLoad

* Add custom step size support for CPULoadWidget

* Apply suggestions from review (convert the profiler probe into a nested class, other small changes)

* Do not limit stored load averages to 100%

---------

Co-authored-by: sakertooth <sakertooth@gmail.com>
Co-authored-by: Dominic Clark <mrdomclark@gmail.com>
This commit is contained in:
Martin Pavelek
2023-09-05 01:33:42 +02:00
committed by GitHub
parent fa05ce20b8
commit de062d6c54
8 changed files with 140 additions and 23 deletions

View File

@@ -209,6 +209,7 @@ lmms--gui--Oscilloscope {
lmms--gui--CPULoadWidget {
border: none;
background: url(resources:cpuload_bg.png);
qproperty-stepSize: 4;
}
/* scrollbar: trough */

View File

@@ -241,6 +241,7 @@ lmms--gui--Oscilloscope {
lmms--gui--CPULoadWidget {
border: none;
background: url(resources:cpuload_bg.png);
qproperty-stepSize: 1;
}
/* scrollbar: trough */

View File

@@ -275,6 +275,11 @@ public:
return m_profiler.cpuLoad();
}
int detailLoad(const AudioEngineProfiler::DetailType type) const
{
return m_profiler.detailLoad(type);
}
const qualitySettings & currentQualitySettings() const
{
return m_qualitySettings;
@@ -401,6 +406,10 @@ private:
AudioDevice * tryAudioDevices();
MidiClient * tryMidiClients();
void renderStageNoteSetup();
void renderStageInstruments();
void renderStageEffects();
void renderStageMix();
const surroundSampleFrame * renderNextBuffer();

View File

@@ -25,6 +25,8 @@
#ifndef LMMS_AUDIO_ENGINE_PROFILER_H
#define LMMS_AUDIO_ENGINE_PROFILER_H
#include <array>
#include <atomic>
#include <QFile>
#include "lmms_basics.h"
@@ -53,11 +55,55 @@ public:
void setOutputFile( const QString& outputFile );
enum class DetailType {
NoteSetup,
Instruments,
Effects,
Mixing,
Count
};
constexpr static auto DetailCount = static_cast<std::size_t>(DetailType::Count);
int detailLoad(const DetailType type) const
{
return m_detailLoad[static_cast<std::size_t>(type)].load(std::memory_order_relaxed);
}
class Probe
{
public:
Probe(AudioEngineProfiler& profiler, AudioEngineProfiler::DetailType type)
: m_profiler(profiler)
, m_type(type)
{
profiler.startDetail(type);
}
~Probe() { m_profiler.finishDetail(m_type); }
Probe& operator=(const Probe&) = delete;
Probe(const Probe&) = delete;
Probe(Probe&&) = delete;
private:
AudioEngineProfiler &m_profiler;
const AudioEngineProfiler::DetailType m_type;
};
private:
void startDetail(const DetailType type) { m_detailTimer[static_cast<std::size_t>(type)].reset(); }
void finishDetail(const DetailType type)
{
m_detailTime[static_cast<std::size_t>(type)] = m_detailTimer[static_cast<std::size_t>(type)].elapsed();
}
MicroTimer m_periodTimer;
int m_cpuLoad;
std::atomic<float> m_cpuLoad;
QFile m_outputFile;
// Use arrays to avoid dynamic allocations in realtime code
std::array<MicroTimer, DetailCount> m_detailTimer;
std::array<int, DetailCount> m_detailTime{0};
std::array<std::atomic<float>, DetailCount> m_detailLoad{0};
};
} // namespace lmms

View File

@@ -26,6 +26,7 @@
#ifndef LMMS_GUI_CPU_LOAD_WIDGET_H
#define LMMS_GUI_CPU_LOAD_WIDGET_H
#include <algorithm>
#include <QTimer>
#include <QPixmap>
#include <QWidget>
@@ -40,6 +41,7 @@ namespace lmms::gui
class CPULoadWidget : public QWidget
{
Q_OBJECT
Q_PROPERTY(int stepSize MEMBER m_stepSize)
public:
CPULoadWidget( QWidget * _parent );
~CPULoadWidget() override = default;
@@ -54,6 +56,8 @@ protected slots:
private:
int stepSize() const { return std::max(1, m_stepSize); }
int m_currentLoad;
QPixmap m_temp;
@@ -64,6 +68,8 @@ private:
QTimer m_updateTimer;
int m_stepSize;
} ;

View File

@@ -333,12 +333,9 @@ void AudioEngine::pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames )
const surroundSampleFrame * AudioEngine::renderNextBuffer()
void AudioEngine::renderStageNoteSetup()
{
m_profiler.startPeriod();
s_renderingThread = true;
AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::NoteSetup);
if( m_clearSignal )
{
@@ -387,9 +384,15 @@ const surroundSampleFrame * AudioEngine::renderNextBuffer()
m_newPlayHandles.free( e );
e = next;
}
}
// STAGE 1: run and render all play handles
AudioEngineWorkerThread::fillJobQueue<PlayHandleList>( m_playHandles );
void AudioEngine::renderStageInstruments()
{
AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Instruments);
AudioEngineWorkerThread::fillJobQueue(m_playHandles);
AudioEngineWorkerThread::startAndWaitForJobs();
// removed all play handles which are done
@@ -417,16 +420,28 @@ const surroundSampleFrame * AudioEngine::renderNextBuffer()
++it;
}
}
}
void AudioEngine::renderStageEffects()
{
AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Effects);
// STAGE 2: process effects of all instrument- and sampletracks
AudioEngineWorkerThread::fillJobQueue(m_audioPorts);
AudioEngineWorkerThread::startAndWaitForJobs();
}
// STAGE 3: do master mix in mixer
void AudioEngine::renderStageMix()
{
AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Mixing);
Mixer *mixer = Engine::mixer();
mixer->masterMix(m_outputBufferWrite);
emit nextAudioBuffer(m_outputBufferRead);
runChangesInModel();
@@ -435,10 +450,22 @@ const surroundSampleFrame * AudioEngine::renderNextBuffer()
EnvelopeAndLfoParameters::instances()->trigger();
Controller::triggerFrameCounter();
AutomatableModel::incrementPeriodCounter();
}
const surroundSampleFrame *AudioEngine::renderNextBuffer()
{
m_profiler.startPeriod();
s_renderingThread = true;
renderStageNoteSetup(); // STAGE 0: clear old play handles and buffers, setup new play handles
renderStageInstruments(); // STAGE 1: run and render all play handles
renderStageEffects(); // STAGE 2: process effects of all instrument- and sampletracks
renderStageMix(); // STAGE 3: do master mix in mixer
s_renderingThread = false;
m_profiler.finishPeriod( processingSampleRate(), m_framesPerPeriod );
m_profiler.finishPeriod(processingSampleRate(), m_framesPerPeriod);
return m_outputBufferRead;
}

View File

@@ -24,6 +24,8 @@
#include "AudioEngineProfiler.h"
#include <cstdint>
namespace lmms
{
@@ -38,10 +40,24 @@ AudioEngineProfiler::AudioEngineProfiler() :
void AudioEngineProfiler::finishPeriod( sample_rate_t sampleRate, fpp_t framesPerPeriod )
{
int periodElapsed = m_periodTimer.elapsed();
// Time taken to process all data and fill the audio buffer.
const unsigned int periodElapsed = m_periodTimer.elapsed();
// Maximum time the processing can take before causing buffer underflow. Convert to us.
const uint64_t timeLimit = static_cast<uint64_t>(1000000) * framesPerPeriod / sampleRate;
const float newCpuLoad = periodElapsed / 10000.0f * sampleRate / framesPerPeriod;
m_cpuLoad = std::clamp<int>((newCpuLoad * 0.1f + m_cpuLoad * 0.9f), 0, 100);
// Compute new overall CPU load and apply exponential averaging.
// The result is used for overload detection in AudioEngine::criticalXRuns()
// → the weight of a new sample must be high enough to allow relatively fast changes!
const auto newCpuLoad = 100.f * periodElapsed / timeLimit;
m_cpuLoad = newCpuLoad * 0.1f + m_cpuLoad * 0.9f;
// Compute detailed load analysis. Can use stronger averaging to get more stable readout.
for (std::size_t i = 0; i < DetailCount; i++)
{
const auto newLoad = 100.f * m_detailTime[i] / timeLimit;
const auto oldLoad = m_detailLoad[i].load(std::memory_order_relaxed);
m_detailLoad[i].store(newLoad * 0.05f + oldLoad * 0.95f, std::memory_order_relaxed);
}
if( m_outputFile.isOpen() )
{

View File

@@ -24,6 +24,7 @@
*/
#include <algorithm>
#include <QPainter>
#include "AudioEngine.h"
@@ -72,10 +73,9 @@ void CPULoadWidget::paintEvent( QPaintEvent * )
QPainter p( &m_temp );
p.drawPixmap( 0, 0, m_background );
// as load-indicator consists of small 2-pixel wide leds with
// 1 pixel spacing, we have to make sure, only whole leds are
// shown which we achieve by the following formula
int w = ( m_leds.width() * m_currentLoad / 300 ) * 3;
// Normally the CPU load indicator moves smoothly, with 1 pixel resolution. However, some themes may want to
// draw discrete elements (like LEDs), so the stepSize property can be used to specify a larger step size.
int w = (m_leds.width() * std::min(m_currentLoad, 100) / (stepSize() * 100)) * stepSize();
if( w > 0 )
{
p.drawPixmap( 23, 3, m_leds, 0, 0, w,
@@ -91,10 +91,21 @@ void CPULoadWidget::paintEvent( QPaintEvent * )
void CPULoadWidget::updateCpuLoad()
{
// smooth load-values a bit
int new_load = ( m_currentLoad + Engine::audioEngine()->cpuLoad() ) / 2;
if( new_load != m_currentLoad )
// Additional display smoothing for the main load-value. Stronger averaging
// cannot be used directly in the profiler: cpuLoad() must react fast enough
// to be useful as overload indicator in AudioEngine::criticalXRuns().
const int new_load = (m_currentLoad + Engine::audioEngine()->cpuLoad()) / 2;
if (new_load != m_currentLoad)
{
auto engine = Engine::audioEngine();
setToolTip(
tr("DSP total: %1%").arg(new_load) + "\n"
+ tr(" - Notes and setup: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::NoteSetup)) + "\n"
+ tr(" - Instruments: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::Instruments)) + "\n"
+ tr(" - Effects: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::Effects)) + "\n"
+ tr(" - Mixing: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::Mixing))
);
m_currentLoad = new_load;
m_changed = true;
update();
@@ -102,4 +113,4 @@ void CPULoadWidget::updateCpuLoad()
}
} // namespace lmms::gui
} // namespace lmms::gui