123 lines
3.6 KiB
C++
123 lines
3.6 KiB
C++
#pragma once
|
|
|
|
#include "CadenceTelemetry.h"
|
|
#include "../logging/Logger.h"
|
|
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <sstream>
|
|
#include <thread>
|
|
|
|
namespace RenderCadenceCompositor
|
|
{
|
|
struct TelemetryHealthMonitorConfig
|
|
{
|
|
std::chrono::milliseconds interval = std::chrono::seconds(1);
|
|
std::size_t scheduledStarvationThreshold = 0;
|
|
};
|
|
|
|
class TelemetryHealthMonitor
|
|
{
|
|
public:
|
|
explicit TelemetryHealthMonitor(TelemetryHealthMonitorConfig config = TelemetryHealthMonitorConfig()) :
|
|
mConfig(config)
|
|
{
|
|
}
|
|
|
|
TelemetryHealthMonitor(const TelemetryHealthMonitor&) = delete;
|
|
TelemetryHealthMonitor& operator=(const TelemetryHealthMonitor&) = delete;
|
|
|
|
~TelemetryHealthMonitor()
|
|
{
|
|
Stop();
|
|
}
|
|
|
|
template <typename SystemFrameExchange, typename Output, typename OutputThread, typename RenderThread>
|
|
void Start(const SystemFrameExchange& exchange, const Output& output, const OutputThread& outputThread, const RenderThread& renderThread)
|
|
{
|
|
if (mRunning)
|
|
return;
|
|
mStopping = false;
|
|
mThread = std::thread([this, &exchange, &output, &outputThread, &renderThread]() {
|
|
CadenceTelemetry telemetry;
|
|
CadenceTelemetrySnapshot previous;
|
|
bool hasPrevious = false;
|
|
while (!mStopping)
|
|
{
|
|
std::this_thread::sleep_for(mConfig.interval);
|
|
const CadenceTelemetrySnapshot snapshot = telemetry.Sample(exchange, output, outputThread, renderThread);
|
|
ReportHealth(snapshot, hasPrevious ? &previous : nullptr);
|
|
previous = snapshot;
|
|
hasPrevious = true;
|
|
}
|
|
});
|
|
mRunning = true;
|
|
}
|
|
|
|
void Stop()
|
|
{
|
|
mStopping = true;
|
|
if (mThread.joinable())
|
|
mThread.join();
|
|
mRunning = false;
|
|
}
|
|
|
|
private:
|
|
void ReportHealth(const CadenceTelemetrySnapshot& snapshot, const CadenceTelemetrySnapshot* previous) const
|
|
{
|
|
if (!previous)
|
|
return;
|
|
|
|
const uint64_t lateDelta = snapshot.displayedLate - previous->displayedLate;
|
|
const uint64_t droppedDelta = snapshot.dropped - previous->dropped;
|
|
const uint64_t scheduleFailureDelta = snapshot.scheduleFailures - previous->scheduleFailures;
|
|
|
|
if (droppedDelta > 0 || lateDelta > 0)
|
|
{
|
|
std::ostringstream message;
|
|
message << "DeckLink reported frame timing issue: lateDelta=" << lateDelta
|
|
<< " droppedDelta=" << droppedDelta
|
|
<< " totalLate=" << snapshot.displayedLate
|
|
<< " totalDropped=" << snapshot.dropped
|
|
<< " scheduleLead=";
|
|
if (snapshot.deckLinkScheduleLeadAvailable)
|
|
message << snapshot.deckLinkScheduleLeadFrames;
|
|
else
|
|
message << "n/a";
|
|
message << " realignments=" << snapshot.deckLinkScheduleRealignments;
|
|
LogWarning("telemetry", message.str());
|
|
}
|
|
|
|
if (scheduleFailureDelta > 0)
|
|
{
|
|
std::ostringstream message;
|
|
message << "DeckLink schedule failures increased: delta=" << scheduleFailureDelta
|
|
<< " total=" << snapshot.scheduleFailures;
|
|
LogWarning("telemetry", message.str());
|
|
}
|
|
|
|
const bool appScheduledStarved = snapshot.scheduledFrames <= mConfig.scheduledStarvationThreshold
|
|
&& snapshot.scheduledTotal > 0;
|
|
const bool deckLinkStarved = snapshot.deckLinkBufferedAvailable && snapshot.deckLinkBuffered == 0;
|
|
if (appScheduledStarved || deckLinkStarved)
|
|
{
|
|
std::ostringstream message;
|
|
message << "Output buffer starvation detected: scheduled=" << snapshot.scheduledFrames
|
|
<< " decklinkBuffered=";
|
|
if (snapshot.deckLinkBufferedAvailable)
|
|
message << snapshot.deckLinkBuffered;
|
|
else
|
|
message << "n/a";
|
|
message << " renderFps=" << snapshot.renderFps
|
|
<< " scheduleFps=" << snapshot.scheduleFps;
|
|
LogError("telemetry", message.str());
|
|
}
|
|
}
|
|
|
|
TelemetryHealthMonitorConfig mConfig;
|
|
std::thread mThread;
|
|
std::atomic<bool> mStopping{ false };
|
|
std::atomic<bool> mRunning{ false };
|
|
};
|
|
}
|