#include "stdafx.h"
#include "HealthTelemetry.h"

void HealthTelemetry::ReportSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
{
	std::lock_guard<std::mutex> lock(mMutex);
	mSignalStatus.hasSignal = hasSignal;
	mSignalStatus.width = width;
	mSignalStatus.height = height;
	mSignalStatus.modeName = modeName;
}

bool HealthTelemetry::TryReportSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
{
	std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
	if (!lock.owns_lock())
		return false;

	mSignalStatus.hasSignal = hasSignal;
	mSignalStatus.width = width;
	mSignalStatus.height = height;
	mSignalStatus.modeName = modeName;
	return true;
}

void HealthTelemetry::ReportVideoIOStatus(const std::string& backendName, const std::string& modelName,
	bool supportsInternalKeying, bool supportsExternalKeying, bool keyerInterfaceAvailable,
	bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage)
{
	std::lock_guard<std::mutex> lock(mMutex);
	mVideoIOStatus.backendName = backendName;
	mVideoIOStatus.modelName = modelName;
	mVideoIOStatus.supportsInternalKeying = supportsInternalKeying;
	mVideoIOStatus.supportsExternalKeying = supportsExternalKeying;
	mVideoIOStatus.keyerInterfaceAvailable = keyerInterfaceAvailable;
	mVideoIOStatus.externalKeyingRequested = externalKeyingRequested;
	mVideoIOStatus.externalKeyingActive = externalKeyingActive;
	mVideoIOStatus.statusMessage = statusMessage;
}

bool HealthTelemetry::TryReportVideoIOStatus(const std::string& backendName, const std::string& modelName,
	bool supportsInternalKeying, bool supportsExternalKeying, bool keyerInterfaceAvailable,
	bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage)
{
	std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
	if (!lock.owns_lock())
		return false;

	mVideoIOStatus.backendName = backendName;
	mVideoIOStatus.modelName = modelName;
	mVideoIOStatus.supportsInternalKeying = supportsInternalKeying;
	mVideoIOStatus.supportsExternalKeying = supportsExternalKeying;
	mVideoIOStatus.keyerInterfaceAvailable = keyerInterfaceAvailable;
	mVideoIOStatus.externalKeyingRequested = externalKeyingRequested;
	mVideoIOStatus.externalKeyingActive = externalKeyingActive;
	mVideoIOStatus.statusMessage = statusMessage;
	return true;
}

void HealthTelemetry::RecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
{
	std::lock_guard<std::mutex> lock(mMutex);
	mPerformance.frameBudgetMilliseconds = std::max(frameBudgetMilliseconds, 0.0);
	mPerformance.renderMilliseconds = std::max(renderMilliseconds, 0.0);
	if (mPerformance.smoothedRenderMilliseconds <= 0.0)
		mPerformance.smoothedRenderMilliseconds = mPerformance.renderMilliseconds;
	else
		mPerformance.smoothedRenderMilliseconds = mPerformance.smoothedRenderMilliseconds * 0.9 + mPerformance.renderMilliseconds * 0.1;
}

bool HealthTelemetry::TryRecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
{
	std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
	if (!lock.owns_lock())
		return false;

	mPerformance.frameBudgetMilliseconds = std::max(frameBudgetMilliseconds, 0.0);
	mPerformance.renderMilliseconds = std::max(renderMilliseconds, 0.0);
	if (mPerformance.smoothedRenderMilliseconds <= 0.0)
		mPerformance.smoothedRenderMilliseconds = mPerformance.renderMilliseconds;
	else
		mPerformance.smoothedRenderMilliseconds = mPerformance.smoothedRenderMilliseconds * 0.9 + mPerformance.renderMilliseconds * 0.1;
	return true;
}

void HealthTelemetry::RecordFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
	double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
{
	std::lock_guard<std::mutex> lock(mMutex);
	mPerformance.completionIntervalMilliseconds = std::max(completionIntervalMilliseconds, 0.0);
	mPerformance.smoothedCompletionIntervalMilliseconds = std::max(smoothedCompletionIntervalMilliseconds, 0.0);
	mPerformance.maxCompletionIntervalMilliseconds = std::max(maxCompletionIntervalMilliseconds, 0.0);
	mPerformance.lateFrameCount = lateFrameCount;
	mPerformance.droppedFrameCount = droppedFrameCount;
	mPerformance.flushedFrameCount = flushedFrameCount;
}

bool HealthTelemetry::TryRecordFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
	double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
{
	std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
	if (!lock.owns_lock())
		return false;

	mPerformance.completionIntervalMilliseconds = std::max(completionIntervalMilliseconds, 0.0);
	mPerformance.smoothedCompletionIntervalMilliseconds = std::max(smoothedCompletionIntervalMilliseconds, 0.0);
	mPerformance.maxCompletionIntervalMilliseconds = std::max(maxCompletionIntervalMilliseconds, 0.0);
	mPerformance.lateFrameCount = lateFrameCount;
	mPerformance.droppedFrameCount = droppedFrameCount;
	mPerformance.flushedFrameCount = flushedFrameCount;
	return true;
}

void HealthTelemetry::RecordRuntimeEventQueueMetrics(const std::string& queueName, std::size_t depth, std::size_t capacity,
	uint64_t droppedCount, double oldestEventAgeMilliseconds)
{
	std::lock_guard<std::mutex> lock(mMutex);
	mRuntimeEvents.queue.queueName = queueName;
	mRuntimeEvents.queue.depth = depth;
	mRuntimeEvents.queue.capacity = capacity;
	mRuntimeEvents.queue.droppedCount = droppedCount;
	mRuntimeEvents.queue.oldestEventAgeMilliseconds = std::max(oldestEventAgeMilliseconds, 0.0);
}

bool HealthTelemetry::TryRecordRuntimeEventQueueMetrics(const std::string& queueName, std::size_t depth, std::size_t capacity,
	uint64_t droppedCount, double oldestEventAgeMilliseconds)
{
	std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
	if (!lock.owns_lock())
		return false;

	mRuntimeEvents.queue.queueName = queueName;
	mRuntimeEvents.queue.depth = depth;
	mRuntimeEvents.queue.capacity = capacity;
	mRuntimeEvents.queue.droppedCount = droppedCount;
	mRuntimeEvents.queue.oldestEventAgeMilliseconds = std::max(oldestEventAgeMilliseconds, 0.0);
	return true;
}

void HealthTelemetry::RecordRuntimeEventDispatchStats(std::size_t dispatchedEvents, std::size_t handlerInvocations,
	std::size_t handlerFailures, double dispatchDurationMilliseconds)
{
	std::lock_guard<std::mutex> lock(mMutex);
	++mRuntimeEvents.dispatch.dispatchCallCount;
	mRuntimeEvents.dispatch.dispatchedEventCount += static_cast<uint64_t>(dispatchedEvents);
	mRuntimeEvents.dispatch.handlerInvocationCount += static_cast<uint64_t>(handlerInvocations);
	mRuntimeEvents.dispatch.handlerFailureCount += static_cast<uint64_t>(handlerFailures);
	mRuntimeEvents.dispatch.lastDispatchDurationMilliseconds = std::max(dispatchDurationMilliseconds, 0.0);
	mRuntimeEvents.dispatch.maxDispatchDurationMilliseconds = std::max(
		mRuntimeEvents.dispatch.maxDispatchDurationMilliseconds,
		mRuntimeEvents.dispatch.lastDispatchDurationMilliseconds);
}

bool HealthTelemetry::TryRecordRuntimeEventDispatchStats(std::size_t dispatchedEvents, std::size_t handlerInvocations,
	std::size_t handlerFailures, double dispatchDurationMilliseconds)
{
	std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
	if (!lock.owns_lock())
		return false;

	++mRuntimeEvents.dispatch.dispatchCallCount;
	mRuntimeEvents.dispatch.dispatchedEventCount += static_cast<uint64_t>(dispatchedEvents);
	mRuntimeEvents.dispatch.handlerInvocationCount += static_cast<uint64_t>(handlerInvocations);
	mRuntimeEvents.dispatch.handlerFailureCount += static_cast<uint64_t>(handlerFailures);
	mRuntimeEvents.dispatch.lastDispatchDurationMilliseconds = std::max(dispatchDurationMilliseconds, 0.0);
	mRuntimeEvents.dispatch.maxDispatchDurationMilliseconds = std::max(
		mRuntimeEvents.dispatch.maxDispatchDurationMilliseconds,
		mRuntimeEvents.dispatch.lastDispatchDurationMilliseconds);
	return true;
}

void HealthTelemetry::RecordPersistenceWriteResult(bool succeeded, const std::string& targetKind, const std::string& targetPath,
	const std::string& reason, const std::string& errorMessage, bool newerRequestPending)
{
	std::lock_guard<std::mutex> lock(mMutex);
	if (succeeded)
		++mPersistence.writeSuccessCount;
	else
		++mPersistence.writeFailureCount;
	mPersistence.lastWriteSucceeded = succeeded;
	mPersistence.unsavedChanges = !succeeded || newerRequestPending;
	mPersistence.newerRequestPending = newerRequestPending;
	mPersistence.lastTargetKind = targetKind;
	mPersistence.lastTargetPath = targetPath;
	mPersistence.lastReason = reason;
	mPersistence.lastErrorMessage = errorMessage;
}

bool HealthTelemetry::TryRecordPersistenceWriteResult(bool succeeded, const std::string& targetKind, const std::string& targetPath,
	const std::string& reason, const std::string& errorMessage, bool newerRequestPending)
{
	std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
	if (!lock.owns_lock())
		return false;

	if (succeeded)
		++mPersistence.writeSuccessCount;
	else
		++mPersistence.writeFailureCount;
	mPersistence.lastWriteSucceeded = succeeded;
	mPersistence.unsavedChanges = !succeeded || newerRequestPending;
	mPersistence.newerRequestPending = newerRequestPending;
	mPersistence.lastTargetKind = targetKind;
	mPersistence.lastTargetPath = targetPath;
	mPersistence.lastReason = reason;
	mPersistence.lastErrorMessage = errorMessage;
	return true;
}

void HealthTelemetry::RecordBackendPlayoutHealth(const std::string& lifecycleState, const std::string& completionResult,
	std::size_t readyQueueDepth, std::size_t readyQueueCapacity, uint64_t readyQueuePushedCount,
	std::size_t minReadyQueueDepth, std::size_t maxReadyQueueDepth, uint64_t readyQueueZeroDepthCount,
	uint64_t readyQueuePoppedCount, uint64_t readyQueueDroppedCount, uint64_t readyQueueUnderrunCount,
	double outputRenderMilliseconds, double smoothedOutputRenderMilliseconds, double maxOutputRenderMilliseconds,
	double outputFrameAcquireMilliseconds, double outputFrameRenderRequestMilliseconds, double outputFrameEndAccessMilliseconds,
	uint64_t completedFrameIndex, uint64_t scheduledFrameIndex, uint64_t scheduledLeadFrames,
	uint64_t measuredLagFrames, uint64_t catchUpFrames, uint64_t lateStreak, uint64_t dropStreak,
	uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount,
	bool degraded, const std::string& statusMessage)
{
	std::lock_guard<std::mutex> lock(mMutex);
	mBackendPlayout.lifecycleState = lifecycleState;
	mBackendPlayout.completionResult = completionResult;
	mBackendPlayout.readyQueueDepth = readyQueueDepth;
	mBackendPlayout.readyQueueCapacity = readyQueueCapacity;
	mBackendPlayout.minReadyQueueDepth = minReadyQueueDepth;
	mBackendPlayout.maxReadyQueueDepth = maxReadyQueueDepth;
	mBackendPlayout.readyQueueZeroDepthCount = readyQueueZeroDepthCount;
	mBackendPlayout.readyQueuePushedCount = readyQueuePushedCount;
	mBackendPlayout.readyQueuePoppedCount = readyQueuePoppedCount;
	mBackendPlayout.readyQueueDroppedCount = readyQueueDroppedCount;
	mBackendPlayout.readyQueueUnderrunCount = readyQueueUnderrunCount;
	mBackendPlayout.outputRenderMilliseconds = std::max(outputRenderMilliseconds, 0.0);
	mBackendPlayout.smoothedOutputRenderMilliseconds = std::max(smoothedOutputRenderMilliseconds, 0.0);
	mBackendPlayout.maxOutputRenderMilliseconds = std::max(maxOutputRenderMilliseconds, 0.0);
	mBackendPlayout.outputFrameAcquireMilliseconds = std::max(outputFrameAcquireMilliseconds, 0.0);
	mBackendPlayout.outputFrameRenderRequestMilliseconds = std::max(outputFrameRenderRequestMilliseconds, 0.0);
	mBackendPlayout.outputFrameEndAccessMilliseconds = std::max(outputFrameEndAccessMilliseconds, 0.0);
	mBackendPlayout.completedFrameIndex = completedFrameIndex;
	mBackendPlayout.scheduledFrameIndex = scheduledFrameIndex;
	mBackendPlayout.scheduledLeadFrames = scheduledLeadFrames;
	mBackendPlayout.measuredLagFrames = measuredLagFrames;
	mBackendPlayout.catchUpFrames = catchUpFrames;
	mBackendPlayout.lateStreak = lateStreak;
	mBackendPlayout.dropStreak = dropStreak;
	mBackendPlayout.lateFrameCount = lateFrameCount;
	mBackendPlayout.droppedFrameCount = droppedFrameCount;
	mBackendPlayout.flushedFrameCount = flushedFrameCount;
	mBackendPlayout.degraded = degraded;
	mBackendPlayout.statusMessage = statusMessage;
}

bool HealthTelemetry::TryRecordBackendPlayoutHealth(const std::string& lifecycleState, const std::string& completionResult,
	std::size_t readyQueueDepth, std::size_t readyQueueCapacity, uint64_t readyQueuePushedCount,
	std::size_t minReadyQueueDepth, std::size_t maxReadyQueueDepth, uint64_t readyQueueZeroDepthCount,
	uint64_t readyQueuePoppedCount, uint64_t readyQueueDroppedCount, uint64_t readyQueueUnderrunCount,
	double outputRenderMilliseconds, double smoothedOutputRenderMilliseconds, double maxOutputRenderMilliseconds,
	double outputFrameAcquireMilliseconds, double outputFrameRenderRequestMilliseconds, double outputFrameEndAccessMilliseconds,
	uint64_t completedFrameIndex, uint64_t scheduledFrameIndex, uint64_t scheduledLeadFrames,
	uint64_t measuredLagFrames, uint64_t catchUpFrames, uint64_t lateStreak, uint64_t dropStreak,
	uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount,
	bool degraded, const std::string& statusMessage)
{
	std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
	if (!lock.owns_lock())
		return false;

	mBackendPlayout.lifecycleState = lifecycleState;
	mBackendPlayout.completionResult = completionResult;
	mBackendPlayout.readyQueueDepth = readyQueueDepth;
	mBackendPlayout.readyQueueCapacity = readyQueueCapacity;
	mBackendPlayout.minReadyQueueDepth = minReadyQueueDepth;
	mBackendPlayout.maxReadyQueueDepth = maxReadyQueueDepth;
	mBackendPlayout.readyQueueZeroDepthCount = readyQueueZeroDepthCount;
	mBackendPlayout.readyQueuePushedCount = readyQueuePushedCount;
	mBackendPlayout.readyQueuePoppedCount = readyQueuePoppedCount;
	mBackendPlayout.readyQueueDroppedCount = readyQueueDroppedCount;
	mBackendPlayout.readyQueueUnderrunCount = readyQueueUnderrunCount;
	mBackendPlayout.outputRenderMilliseconds = std::max(outputRenderMilliseconds, 0.0);
	mBackendPlayout.smoothedOutputRenderMilliseconds = std::max(smoothedOutputRenderMilliseconds, 0.0);
	mBackendPlayout.maxOutputRenderMilliseconds = std::max(maxOutputRenderMilliseconds, 0.0);
	mBackendPlayout.outputFrameAcquireMilliseconds = std::max(outputFrameAcquireMilliseconds, 0.0);
	mBackendPlayout.outputFrameRenderRequestMilliseconds = std::max(outputFrameRenderRequestMilliseconds, 0.0);
	mBackendPlayout.outputFrameEndAccessMilliseconds = std::max(outputFrameEndAccessMilliseconds, 0.0);
	mBackendPlayout.completedFrameIndex = completedFrameIndex;
	mBackendPlayout.scheduledFrameIndex = scheduledFrameIndex;
	mBackendPlayout.scheduledLeadFrames = scheduledLeadFrames;
	mBackendPlayout.measuredLagFrames = measuredLagFrames;
	mBackendPlayout.catchUpFrames = catchUpFrames;
	mBackendPlayout.lateStreak = lateStreak;
	mBackendPlayout.dropStreak = dropStreak;
	mBackendPlayout.lateFrameCount = lateFrameCount;
	mBackendPlayout.droppedFrameCount = droppedFrameCount;
	mBackendPlayout.flushedFrameCount = flushedFrameCount;
	mBackendPlayout.degraded = degraded;
	mBackendPlayout.statusMessage = statusMessage;
	return true;
}

void HealthTelemetry::RecordOutputRenderQueueWait(double queueWaitMilliseconds)
{
	std::lock_guard<std::mutex> lock(mMutex);
	mBackendPlayout.outputRenderQueueWaitMilliseconds = std::max(queueWaitMilliseconds, 0.0);
}

bool HealthTelemetry::TryRecordOutputRenderQueueWait(double queueWaitMilliseconds)
{
	std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
	if (!lock.owns_lock())
		return false;

	mBackendPlayout.outputRenderQueueWaitMilliseconds = std::max(queueWaitMilliseconds, 0.0);
	return true;
}

void HealthTelemetry::RecordSystemMemoryPlayoutStats(std::size_t freeFrameCount, std::size_t readyFrameCount,
	std::size_t scheduledFrameCount, uint64_t underrunCount, uint64_t repeatCount, uint64_t dropCount,
	double frameAgeAtScheduleMilliseconds, double frameAgeAtCompletionMilliseconds)
{
	std::lock_guard<std::mutex> lock(mMutex);
	mBackendPlayout.systemFramePoolFree = freeFrameCount;
	mBackendPlayout.systemFramePoolReady = readyFrameCount;
	mBackendPlayout.systemFramePoolScheduled = scheduledFrameCount;
	mBackendPlayout.systemFrameUnderrunCount = underrunCount;
	mBackendPlayout.systemFrameRepeatCount = repeatCount;
	mBackendPlayout.systemFrameDropCount = dropCount;
	mBackendPlayout.systemFrameAgeAtScheduleMilliseconds = std::max(frameAgeAtScheduleMilliseconds, 0.0);
	mBackendPlayout.systemFrameAgeAtCompletionMilliseconds = std::max(frameAgeAtCompletionMilliseconds, 0.0);
}

bool HealthTelemetry::TryRecordSystemMemoryPlayoutStats(std::size_t freeFrameCount, std::size_t readyFrameCount,
	std::size_t scheduledFrameCount, uint64_t underrunCount, uint64_t repeatCount, uint64_t dropCount,
	double frameAgeAtScheduleMilliseconds, double frameAgeAtCompletionMilliseconds)
{
	std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
	if (!lock.owns_lock())
		return false;

	mBackendPlayout.systemFramePoolFree = freeFrameCount;
	mBackendPlayout.systemFramePoolReady = readyFrameCount;
	mBackendPlayout.systemFramePoolScheduled = scheduledFrameCount;
	mBackendPlayout.systemFrameUnderrunCount = underrunCount;
	mBackendPlayout.systemFrameRepeatCount = repeatCount;
	mBackendPlayout.systemFrameDropCount = dropCount;
	mBackendPlayout.systemFrameAgeAtScheduleMilliseconds = std::max(frameAgeAtScheduleMilliseconds, 0.0);
	mBackendPlayout.systemFrameAgeAtCompletionMilliseconds = std::max(frameAgeAtCompletionMilliseconds, 0.0);
	return true;
}

void HealthTelemetry::RecordDeckLinkBufferTelemetry(bool actualBufferedFramesAvailable, uint64_t actualBufferedFrames,
	std::size_t targetBufferedFrames, double scheduleCallMilliseconds, uint64_t scheduleFailureCount)
{
	std::lock_guard<std::mutex> lock(mMutex);
	mBackendPlayout.actualDeckLinkBufferedFramesAvailable = actualBufferedFramesAvailable;
	mBackendPlayout.actualDeckLinkBufferedFrames = actualBufferedFramesAvailable ? actualBufferedFrames : 0;
	mBackendPlayout.targetDeckLinkBufferedFrames = targetBufferedFrames;
	mBackendPlayout.deckLinkScheduleCallMilliseconds = std::max(scheduleCallMilliseconds, 0.0);
	mBackendPlayout.deckLinkScheduleFailureCount = scheduleFailureCount;
}

bool HealthTelemetry::TryRecordDeckLinkBufferTelemetry(bool actualBufferedFramesAvailable, uint64_t actualBufferedFrames,
	std::size_t targetBufferedFrames, double scheduleCallMilliseconds, uint64_t scheduleFailureCount)
{
	std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
	if (!lock.owns_lock())
		return false;

	mBackendPlayout.actualDeckLinkBufferedFramesAvailable = actualBufferedFramesAvailable;
	mBackendPlayout.actualDeckLinkBufferedFrames = actualBufferedFramesAvailable ? actualBufferedFrames : 0;
	mBackendPlayout.targetDeckLinkBufferedFrames = targetBufferedFrames;
	mBackendPlayout.deckLinkScheduleCallMilliseconds = std::max(scheduleCallMilliseconds, 0.0);
	mBackendPlayout.deckLinkScheduleFailureCount = scheduleFailureCount;
	return true;
}

void HealthTelemetry::RecordOutputRenderPipelineTiming(
	double drawMilliseconds,
	double fenceWaitMilliseconds,
	double mapMilliseconds,
	double readbackCopyMilliseconds,
	double cachedCopyMilliseconds,
	double asyncQueueMilliseconds,
	double asyncQueueBufferMilliseconds,
	double asyncQueueSetupMilliseconds,
	double asyncQueueReadPixelsMilliseconds,
	double asyncQueueFenceMilliseconds,
	double syncReadMilliseconds,
	bool asyncReadbackMissed,
	bool cachedFallbackUsed,
	bool syncFallbackUsed)
{
	std::lock_guard<std::mutex> lock(mMutex);
	mBackendPlayout.outputRenderDrawMilliseconds = std::max(drawMilliseconds, 0.0);
	mBackendPlayout.outputReadbackFenceWaitMilliseconds = std::max(fenceWaitMilliseconds, 0.0);
	mBackendPlayout.outputReadbackMapMilliseconds = std::max(mapMilliseconds, 0.0);
	mBackendPlayout.outputReadbackCopyMilliseconds = std::max(readbackCopyMilliseconds, 0.0);
	mBackendPlayout.outputCachedCopyMilliseconds = std::max(cachedCopyMilliseconds, 0.0);
	mBackendPlayout.outputAsyncQueueMilliseconds = std::max(asyncQueueMilliseconds, 0.0);
	mBackendPlayout.outputAsyncQueueBufferMilliseconds = std::max(asyncQueueBufferMilliseconds, 0.0);
	mBackendPlayout.outputAsyncQueueSetupMilliseconds = std::max(asyncQueueSetupMilliseconds, 0.0);
	mBackendPlayout.outputAsyncQueueReadPixelsMilliseconds = std::max(asyncQueueReadPixelsMilliseconds, 0.0);
	mBackendPlayout.outputAsyncQueueFenceMilliseconds = std::max(asyncQueueFenceMilliseconds, 0.0);
	mBackendPlayout.outputSyncReadMilliseconds = std::max(syncReadMilliseconds, 0.0);
	if (asyncReadbackMissed)
		++mBackendPlayout.outputAsyncReadbackMissCount;
	if (cachedFallbackUsed)
		++mBackendPlayout.outputCachedFallbackCount;
	if (syncFallbackUsed)
		++mBackendPlayout.outputSyncFallbackCount;
}

bool HealthTelemetry::TryRecordOutputRenderPipelineTiming(
	double drawMilliseconds,
	double fenceWaitMilliseconds,
	double mapMilliseconds,
	double readbackCopyMilliseconds,
	double cachedCopyMilliseconds,
	double asyncQueueMilliseconds,
	double asyncQueueBufferMilliseconds,
	double asyncQueueSetupMilliseconds,
	double asyncQueueReadPixelsMilliseconds,
	double asyncQueueFenceMilliseconds,
	double syncReadMilliseconds,
	bool asyncReadbackMissed,
	bool cachedFallbackUsed,
	bool syncFallbackUsed)
{
	std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
	if (!lock.owns_lock())
		return false;

	mBackendPlayout.outputRenderDrawMilliseconds = std::max(drawMilliseconds, 0.0);
	mBackendPlayout.outputReadbackFenceWaitMilliseconds = std::max(fenceWaitMilliseconds, 0.0);
	mBackendPlayout.outputReadbackMapMilliseconds = std::max(mapMilliseconds, 0.0);
	mBackendPlayout.outputReadbackCopyMilliseconds = std::max(readbackCopyMilliseconds, 0.0);
	mBackendPlayout.outputCachedCopyMilliseconds = std::max(cachedCopyMilliseconds, 0.0);
	mBackendPlayout.outputAsyncQueueMilliseconds = std::max(asyncQueueMilliseconds, 0.0);
	mBackendPlayout.outputAsyncQueueBufferMilliseconds = std::max(asyncQueueBufferMilliseconds, 0.0);
	mBackendPlayout.outputAsyncQueueSetupMilliseconds = std::max(asyncQueueSetupMilliseconds, 0.0);
	mBackendPlayout.outputAsyncQueueReadPixelsMilliseconds = std::max(asyncQueueReadPixelsMilliseconds, 0.0);
	mBackendPlayout.outputAsyncQueueFenceMilliseconds = std::max(asyncQueueFenceMilliseconds, 0.0);
	mBackendPlayout.outputSyncReadMilliseconds = std::max(syncReadMilliseconds, 0.0);
	if (asyncReadbackMissed)
		++mBackendPlayout.outputAsyncReadbackMissCount;
	if (cachedFallbackUsed)
		++mBackendPlayout.outputCachedFallbackCount;
	if (syncFallbackUsed)
		++mBackendPlayout.outputSyncFallbackCount;
	return true;
}

HealthTelemetry::SignalStatusSnapshot HealthTelemetry::GetSignalStatusSnapshot() const
{
	std::lock_guard<std::mutex> lock(mMutex);
	return mSignalStatus;
}

HealthTelemetry::VideoIOStatusSnapshot HealthTelemetry::GetVideoIOStatusSnapshot() const
{
	std::lock_guard<std::mutex> lock(mMutex);
	return mVideoIOStatus;
}

HealthTelemetry::PerformanceSnapshot HealthTelemetry::GetPerformanceSnapshot() const
{
	std::lock_guard<std::mutex> lock(mMutex);
	return mPerformance;
}

HealthTelemetry::RuntimeEventMetricsSnapshot HealthTelemetry::GetRuntimeEventMetricsSnapshot() const
{
	std::lock_guard<std::mutex> lock(mMutex);
	return mRuntimeEvents;
}

HealthTelemetry::PersistenceSnapshot HealthTelemetry::GetPersistenceSnapshot() const
{
	std::lock_guard<std::mutex> lock(mMutex);
	return mPersistence;
}

HealthTelemetry::BackendPlayoutSnapshot HealthTelemetry::GetBackendPlayoutSnapshot() const
{
	std::lock_guard<std::mutex> lock(mMutex);
	return mBackendPlayout;
}

HealthTelemetry::Snapshot HealthTelemetry::GetSnapshot() const
{
	std::lock_guard<std::mutex> lock(mMutex);

	Snapshot snapshot;
	snapshot.signal = mSignalStatus;
	snapshot.videoIO = mVideoIOStatus;
	snapshot.performance = mPerformance;
	snapshot.runtimeEvents = mRuntimeEvents;
	snapshot.persistence = mPersistence;
	snapshot.backendPlayout = mBackendPlayout;
	return snapshot;
}
