Generic telemetry
This commit is contained in:
@@ -178,6 +178,8 @@ The control UI is available at:
|
||||
http://127.0.0.1:<serverPort>
|
||||
```
|
||||
|
||||
`/api/state` exposes backend-neutral output telemetry in `videoOutput`. Use `videoOutput.enabled`, `videoOutput.backend`, and `videoOutput.scheduleFailures` for portable status. Backend-specific counters live in `videoOutput.backendMetrics`.
|
||||
|
||||
## Runtime State
|
||||
|
||||
The current layer stack is autosaved to `runtime/runtime_state.json` whenever durable UI/API layer changes are accepted: add/remove, shader assignment, bypass state, ordering, parameter updates, parameter reset, and reload compatibility refreshes. Saves are debounced and written on a background worker, with a final flush during shutdown.
|
||||
|
||||
@@ -139,6 +139,8 @@ The input edge writes CPU frames into `InputFrameMailbox`. The current DeckLink
|
||||
|
||||
The output edge consumes completed system-memory frames from `SystemFrameExchange`. The current DeckLink backend schedules those frames to DeckLink. If video output is unavailable, the app continues running render cadence, control, preview, telemetry, and logging.
|
||||
|
||||
Runtime state exposes backend-neutral output telemetry through `videoOutput`. Portable fields such as `enabled`, `backend`, and `scheduleFailures` stay at that level; backend-specific counters live under `videoOutput.backendMetrics`.
|
||||
|
||||
`PreviewWindowThread` is optional and uses a non-consuming system-memory tap. It paints with Win32/GDI on its own thread and skips preview ticks instead of blocking the frame exchange.
|
||||
|
||||
Screenshot routes are present in the UI/OpenAPI surface but are not implemented in the current native command path yet.
|
||||
|
||||
@@ -520,6 +520,8 @@ components:
|
||||
$ref: "#/components/schemas/RuntimeStatus"
|
||||
video:
|
||||
$ref: "#/components/schemas/VideoStatus"
|
||||
videoOutput:
|
||||
$ref: "#/components/schemas/VideoOutputStatus"
|
||||
decklink:
|
||||
$ref: "#/components/schemas/DeckLinkStatus"
|
||||
videoIO:
|
||||
@@ -561,6 +563,12 @@ components:
|
||||
type: number
|
||||
enableExternalKeying:
|
||||
type: boolean
|
||||
videoInputBackend:
|
||||
type: string
|
||||
enum: [decklink, none]
|
||||
videoOutputBackend:
|
||||
type: string
|
||||
enum: [decklink, none]
|
||||
inputVideoFormat:
|
||||
type: string
|
||||
inputFrameRate:
|
||||
@@ -589,6 +597,52 @@ components:
|
||||
type: number
|
||||
modeName:
|
||||
type: string
|
||||
VideoOutputStatus:
|
||||
type: object
|
||||
description: Backend-neutral output telemetry. Backend-specific counters live under `backendMetrics`.
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
backend:
|
||||
type: string
|
||||
example: decklink
|
||||
statusMessage:
|
||||
type: string
|
||||
scheduleFailures:
|
||||
type: number
|
||||
completions:
|
||||
type: number
|
||||
late:
|
||||
type: number
|
||||
dropped:
|
||||
type: number
|
||||
backendMetrics:
|
||||
$ref: "#/components/schemas/VideoOutputBackendMetrics"
|
||||
VideoOutputBackendMetrics:
|
||||
type: object
|
||||
description: Backend-specific output metrics. For `decklink`, this contains DeckLink schedule and buffer telemetry.
|
||||
additionalProperties: true
|
||||
properties:
|
||||
bufferedAvailable:
|
||||
type: boolean
|
||||
buffered:
|
||||
type: number
|
||||
nullable: true
|
||||
scheduleCallMs:
|
||||
type: number
|
||||
scheduleLeadAvailable:
|
||||
type: boolean
|
||||
scheduleLeadFrames:
|
||||
type: number
|
||||
nullable: true
|
||||
playbackFrameIndex:
|
||||
type: number
|
||||
nextScheduleFrameIndex:
|
||||
type: number
|
||||
playbackStreamTime:
|
||||
type: number
|
||||
scheduleRealignments:
|
||||
type: number
|
||||
DeckLinkStatus:
|
||||
type: object
|
||||
deprecated: true
|
||||
|
||||
@@ -262,7 +262,7 @@ Startup order is:
|
||||
|
||||
If DeckLink discovery or output setup fails, the app logs a warning and continues running without starting the output scheduler or scheduled playback. This keeps render cadence, runtime shader testing, HTTP state, and logging available on machines without DeckLink hardware or drivers.
|
||||
|
||||
`/api/state` reports the output status in `videoIO.statusMessage`.
|
||||
`/api/state` reports backend-neutral output status in `videoOutput`. Portable fields live at `videoOutput.enabled`, `videoOutput.backend`, and `videoOutput.scheduleFailures`; backend-specific counters such as DeckLink buffered depth and schedule lead live under `videoOutput.backendMetrics`. The older `videoIO` and `decklink` status objects are retained for compatibility while clients migrate.
|
||||
|
||||
## Optional DeckLink Input
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ struct RuntimeStateJsonInput
|
||||
inline void WriteVideoIoStatusJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
|
||||
{
|
||||
writer.BeginObject();
|
||||
writer.KeyString("backend", "decklink");
|
||||
writer.KeyString("backend", input.config.videoOutputBackend);
|
||||
writer.KeyNull("modelName");
|
||||
writer.KeyBool("supportsInternalKeying", false);
|
||||
writer.KeyBool("supportsExternalKeying", false);
|
||||
@@ -38,6 +38,47 @@ inline void WriteVideoIoStatusJson(JsonWriter& writer, const RuntimeStateJsonInp
|
||||
writer.EndObject();
|
||||
}
|
||||
|
||||
inline void WriteVideoOutputBackendMetricsJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
|
||||
{
|
||||
writer.BeginObject();
|
||||
if (input.config.videoOutputBackend == "decklink")
|
||||
{
|
||||
writer.KeyBool("bufferedAvailable", input.telemetry.deckLinkBufferedAvailable);
|
||||
writer.Key("buffered");
|
||||
if (input.telemetry.deckLinkBufferedAvailable)
|
||||
writer.UInt(input.telemetry.deckLinkBuffered);
|
||||
else
|
||||
writer.Null();
|
||||
writer.KeyDouble("scheduleCallMs", input.telemetry.deckLinkScheduleCallMilliseconds);
|
||||
writer.KeyBool("scheduleLeadAvailable", input.telemetry.deckLinkScheduleLeadAvailable);
|
||||
writer.Key("scheduleLeadFrames");
|
||||
if (input.telemetry.deckLinkScheduleLeadAvailable)
|
||||
writer.Int(input.telemetry.deckLinkScheduleLeadFrames);
|
||||
else
|
||||
writer.Null();
|
||||
writer.KeyUInt("playbackFrameIndex", input.telemetry.deckLinkPlaybackFrameIndex);
|
||||
writer.KeyUInt("nextScheduleFrameIndex", input.telemetry.deckLinkNextScheduleFrameIndex);
|
||||
writer.KeyInt("playbackStreamTime", input.telemetry.deckLinkPlaybackStreamTime);
|
||||
writer.KeyUInt("scheduleRealignments", input.telemetry.deckLinkScheduleRealignments);
|
||||
}
|
||||
writer.EndObject();
|
||||
}
|
||||
|
||||
inline void WriteVideoOutputTelemetryJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
|
||||
{
|
||||
writer.BeginObject();
|
||||
writer.KeyBool("enabled", input.videoOutputEnabled);
|
||||
writer.KeyString("backend", input.config.videoOutputBackend);
|
||||
writer.KeyString("statusMessage", input.videoOutputStatus);
|
||||
writer.KeyUInt("scheduleFailures", input.telemetry.scheduleFailures);
|
||||
writer.KeyUInt("completions", input.telemetry.completions);
|
||||
writer.KeyUInt("late", input.telemetry.displayedLate);
|
||||
writer.KeyUInt("dropped", input.telemetry.dropped);
|
||||
writer.Key("backendMetrics");
|
||||
WriteVideoOutputBackendMetricsJson(writer, input);
|
||||
writer.EndObject();
|
||||
}
|
||||
|
||||
inline void OutputDimensions(const RuntimeStateJsonInput& input, unsigned& width, unsigned& height)
|
||||
{
|
||||
VideoFormatDimensions(input.config.outputVideoFormat, width, height);
|
||||
@@ -277,6 +318,8 @@ inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
|
||||
writer.KeyString("modeName", input.config.outputVideoFormat + " output-only");
|
||||
writer.EndObject();
|
||||
|
||||
writer.Key("videoOutput");
|
||||
WriteVideoOutputTelemetryJson(writer, input);
|
||||
writer.Key("decklink");
|
||||
WriteVideoIoStatusJson(writer, input);
|
||||
writer.Key("videoIO");
|
||||
|
||||
@@ -50,7 +50,19 @@ int main()
|
||||
telemetry.completedReadbackCopyMilliseconds = 1.2;
|
||||
telemetry.completedDrops = 3;
|
||||
telemetry.acquireMisses = 4;
|
||||
telemetry.scheduleFailures = 2;
|
||||
telemetry.completions = 12;
|
||||
telemetry.displayedLate = 1;
|
||||
telemetry.shaderBuildsCommitted = 1;
|
||||
telemetry.deckLinkBufferedAvailable = true;
|
||||
telemetry.deckLinkBuffered = 4;
|
||||
telemetry.deckLinkScheduleCallMilliseconds = 1.25;
|
||||
telemetry.deckLinkScheduleLeadAvailable = true;
|
||||
telemetry.deckLinkScheduleLeadFrames = 4;
|
||||
telemetry.deckLinkPlaybackFrameIndex = 10;
|
||||
telemetry.deckLinkNextScheduleFrameIndex = 14;
|
||||
telemetry.deckLinkPlaybackStreamTime = 10010;
|
||||
telemetry.deckLinkScheduleRealignments = 1;
|
||||
|
||||
const std::filesystem::path root = MakeTestRoot();
|
||||
WriteFile(root / "solid-color" / "shader.slang", "float4 shadeVideo(float2 uv) { return float4(uv, 0.0, 1.0); }\n");
|
||||
@@ -107,6 +119,10 @@ int main()
|
||||
ExpectContains(json, "\"height\":1080", "state JSON should expose output height");
|
||||
ExpectContains(json, "\"videoInputBackend\":\"decklink\"", "state JSON should expose input backend");
|
||||
ExpectContains(json, "\"videoOutputBackend\":\"decklink\"", "state JSON should expose output backend");
|
||||
ExpectContains(json, "\"videoOutput\":{\"enabled\":true,\"backend\":\"decklink\"", "state JSON should expose neutral video output status");
|
||||
ExpectContains(json, "\"scheduleFailures\":2", "state JSON should expose neutral video output schedule failures");
|
||||
ExpectContains(json, "\"backendMetrics\":{\"bufferedAvailable\":true,\"buffered\":4", "state JSON should expose backend-specific video output metrics");
|
||||
ExpectContains(json, "\"scheduleLeadFrames\":4", "state JSON should expose backend-specific schedule lead");
|
||||
ExpectContains(json, "\"renderMs\":2.5", "state JSON should expose top-level render timing");
|
||||
ExpectContains(json, "\"budgetUsedPercent\":15", "state JSON should expose top-level render budget percentage");
|
||||
ExpectContains(json, "\"renderFrameMs\":2.5", "state JSON should expose cadence render timing");
|
||||
|
||||
Reference in New Issue
Block a user