#include "CadenceTelemetry.h" #include "CadenceTelemetryJson.h" #include #include #include #include namespace { int gFailures = 0; void Expect(bool condition, const char* message) { if (condition) return; std::cerr << "FAIL: " << message << "\n"; ++gFailures; } struct FakeExchangeMetrics { std::size_t freeCount = 0; std::size_t completedCount = 0; std::size_t scheduledCount = 0; uint64_t completedFrames = 0; uint64_t scheduledFrames = 0; }; struct FakeExchange { FakeExchangeMetrics metrics; FakeExchangeMetrics Metrics() const { return metrics; } }; struct FakeOutputThreadMetrics { uint64_t completedPollMisses = 0; uint64_t scheduleFailures = 0; }; struct FakeOutputThread { FakeOutputThreadMetrics metrics; FakeOutputThreadMetrics Metrics() const { return metrics; } }; struct FakeOutputMetrics { uint64_t completions = 0; uint64_t displayedLate = 0; uint64_t dropped = 0; uint64_t scheduleFailures = 0; bool actualBufferedFramesAvailable = false; uint64_t actualBufferedFrames = 0; double scheduleCallMilliseconds = 0.0; }; struct FakeOutput { FakeOutputMetrics metrics; FakeOutputMetrics Metrics() const { return metrics; } }; struct FakeRenderThreadMetrics { uint64_t shaderBuildsCommitted = 0; uint64_t shaderBuildFailures = 0; uint64_t inputFramesReceived = 0; uint64_t inputFramesDropped = 0; double inputLatestAgeMilliseconds = 0.0; double inputUploadMilliseconds = 0.0; bool inputFormatSupported = true; bool inputSignalPresent = false; }; struct FakeRenderThread { FakeRenderThreadMetrics metrics; FakeRenderThreadMetrics GetMetrics() const { return metrics; } }; void TestTelemetrySamplesCompletedPollMissesAndShaderCounts() { RenderCadenceCompositor::CadenceTelemetry telemetry; FakeExchange exchange; exchange.metrics.freeCount = 7; exchange.metrics.completedCount = 1; exchange.metrics.scheduledCount = 4; exchange.metrics.completedFrames = 100; exchange.metrics.scheduledFrames = 96; FakeOutput output; output.metrics.actualBufferedFramesAvailable = true; output.metrics.actualBufferedFrames = 4; FakeOutputThread outputThread; outputThread.metrics.completedPollMisses = 12; outputThread.metrics.scheduleFailures = 0; FakeRenderThread renderThread; renderThread.metrics.shaderBuildsCommitted = 1; renderThread.metrics.shaderBuildFailures = 0; renderThread.metrics.inputFramesReceived = 9; renderThread.metrics.inputFramesDropped = 2; renderThread.metrics.inputLatestAgeMilliseconds = 4.5; renderThread.metrics.inputUploadMilliseconds = 0.25; renderThread.metrics.inputFormatSupported = true; renderThread.metrics.inputSignalPresent = true; const auto snapshot = telemetry.Sample(exchange, output, outputThread, renderThread); Expect(snapshot.freeFrames == 7, "free frame count is sampled"); Expect(snapshot.completedFrames == 1, "completed frame count is sampled"); Expect(snapshot.scheduledFrames == 4, "scheduled frame count is sampled"); Expect(snapshot.completedPollMisses == 12, "completed poll misses are sampled"); Expect(snapshot.shaderBuildsCommitted == 1, "shader committed count is sampled"); Expect(snapshot.shaderBuildFailures == 0, "shader failure count is sampled"); Expect(snapshot.inputFramesReceived == 9, "input received count is sampled"); Expect(snapshot.inputFramesDropped == 2, "input dropped count is sampled"); Expect(snapshot.inputLatestAgeMilliseconds == 4.5, "input latest age is sampled"); Expect(snapshot.inputUploadMilliseconds == 0.25, "input upload timing is sampled"); Expect(snapshot.inputFormatSupported, "input format support is sampled"); Expect(snapshot.inputSignalPresent, "input signal present is sampled"); Expect(snapshot.deckLinkBufferedAvailable, "buffer telemetry availability is sampled"); Expect(snapshot.deckLinkBuffered == 4, "buffer depth is sampled"); } void TestTelemetryComputesRatesFromDeltas() { RenderCadenceCompositor::CadenceTelemetry telemetry; FakeExchange exchange; FakeOutput output; FakeOutputThread outputThread; FakeRenderThread renderThread; exchange.metrics.completedFrames = 10; exchange.metrics.scheduledFrames = 10; (void)telemetry.Sample(exchange, output, outputThread, renderThread); std::this_thread::sleep_for(std::chrono::milliseconds(5)); exchange.metrics.completedFrames = 20; exchange.metrics.scheduledFrames = 19; const auto snapshot = telemetry.Sample(exchange, output, outputThread, renderThread); Expect(snapshot.sampleSeconds > 0.0, "second telemetry sample has elapsed time"); Expect(snapshot.renderFps > 0.0, "render fps is computed from completed frame delta"); Expect(snapshot.scheduleFps > 0.0, "schedule fps is computed from scheduled frame delta"); } void TestTelemetrySerializesToJson() { RenderCadenceCompositor::CadenceTelemetrySnapshot snapshot; snapshot.sampleSeconds = 1.0; snapshot.renderFps = 59.94; snapshot.scheduleFps = 60.0; snapshot.freeFrames = 7; snapshot.completedFrames = 1; snapshot.scheduledFrames = 4; snapshot.renderedTotal = 120; snapshot.scheduledTotal = 118; snapshot.completedPollMisses = 3; snapshot.scheduleFailures = 0; snapshot.completions = 117; snapshot.displayedLate = 1; snapshot.dropped = 2; snapshot.shaderBuildsCommitted = 1; snapshot.shaderBuildFailures = 0; snapshot.inputFramesReceived = 10; snapshot.inputFramesDropped = 1; snapshot.inputLatestAgeMilliseconds = 3.5; snapshot.inputUploadMilliseconds = 0.75; snapshot.inputFormatSupported = true; snapshot.inputSignalPresent = true; snapshot.inputCaptureFps = 59.94; snapshot.inputConvertMilliseconds = 4.25; snapshot.inputSubmitMilliseconds = 0.35; snapshot.inputNoSignalFrames = 2; snapshot.inputUnsupportedFrames = 3; snapshot.inputSubmitMisses = 4; snapshot.inputCaptureFormat = "UYVY8"; snapshot.deckLinkBufferedAvailable = true; snapshot.deckLinkBuffered = 4; snapshot.deckLinkScheduleCallMilliseconds = 1.25; const std::string json = RenderCadenceCompositor::CadenceTelemetryToJson(snapshot); const std::string expected = "{\"sampleSeconds\":1,\"renderFps\":59.94,\"scheduleFps\":60," "\"free\":7,\"completed\":1,\"scheduled\":4," "\"renderedTotal\":120,\"scheduledTotal\":118," "\"completedPollMisses\":3,\"scheduleFailures\":0," "\"completions\":117,\"late\":1,\"dropped\":2," "\"shaderCommitted\":1,\"shaderFailures\":0," "\"inputFramesReceived\":10,\"inputFramesDropped\":1," "\"inputLatestAgeMs\":3.5,\"inputUploadMs\":0.75," "\"inputFormatSupported\":true,\"inputSignalPresent\":true," "\"inputCaptureFps\":59.94,\"inputConvertMs\":4.25," "\"inputSubmitMs\":0.35,\"inputNoSignalFrames\":2," "\"inputUnsupportedFrames\":3,\"inputSubmitMisses\":4," "\"inputCaptureFormat\":\"UYVY8\"," "\"deckLinkBufferedAvailable\":true,\"deckLinkBuffered\":4," "\"scheduleCallMs\":1.25}"; Expect(json == expected, "telemetry snapshot serializes to stable JSON"); } void TestUnavailableDeckLinkBufferSerializesAsNull() { RenderCadenceCompositor::CadenceTelemetrySnapshot snapshot; snapshot.deckLinkBufferedAvailable = false; const std::string json = RenderCadenceCompositor::CadenceTelemetryToJson(snapshot); Expect( json.find("\"deckLinkBufferedAvailable\":false,\"deckLinkBuffered\":null") != std::string::npos, "unavailable DeckLink buffer depth serializes as null"); } } int main() { TestTelemetrySamplesCompletedPollMissesAndShaderCounts(); TestTelemetryComputesRatesFromDeltas(); TestTelemetrySerializesToJson(); TestUnavailableDeckLinkBufferSerializesAsNull(); if (gFailures != 0) { std::cerr << gFailures << " telemetry test failure(s).\n"; return 1; } std::cout << "RenderCadenceCompositor telemetry tests passed.\n"; return 0; }