#import #import #include #include #include "ds4_gpu.h" /* Shared optional UI/headless probe. Process placement matters: a separate * queue in the model process is not a separate-process scheduling test. */ static id g_canary_device; static id g_canary_queue; static id g_canary_buffer; static double ds4_monotonic_seconds(void) { struct timespec time; if (clock_gettime(CLOCK_MONOTONIC, &time) != 0) return 0.0; return (double)time.tv_sec + (double)time.tv_nsec / 1000000000.0; } int ds4_gpu_canary_probe(ds4_gpu_canary_sample *sample) { if (!sample) return 0; sample->scheduled_seconds = 0.0; sample->completed_seconds = 0.0; sample->gpu_wait_seconds = -1.0; sample->gpu_interval_seconds = -1.0; sample->host_return_seconds = -1.0; @autoreleasepool { if (!g_canary_device) g_canary_device = MTLCreateSystemDefaultDevice(); if (!g_canary_queue && g_canary_device) { g_canary_queue = [g_canary_device newCommandQueue]; } if (!g_canary_buffer && g_canary_device) { g_canary_buffer = [g_canary_device newBufferWithLength:4096 options:MTLResourceStorageModeShared]; } if (!g_canary_queue || !g_canary_buffer) return 0; id cb = [g_canary_queue commandBuffer]; id blit = [cb blitCommandEncoder]; if (!cb || !blit) return 0; [blit fillBuffer:g_canary_buffer range:NSMakeRange(0, 4096) value:0]; [blit endEncoding]; mach_timebase_info_data_t timebase; if (mach_timebase_info(&timebase) != KERN_SUCCESS || !timebase.denom) return 0; const double scale = (double)timebase.numer / timebase.denom / 1e9; const double started = ds4_monotonic_seconds(); const double mach_started = (double)mach_absolute_time() * scale; [cb commit]; [cb waitUntilScheduled]; sample->scheduled_seconds = ds4_monotonic_seconds() - started; [cb waitUntilCompleted]; const double mach_returned = (double)mach_absolute_time() * scale; sample->completed_seconds = ds4_monotonic_seconds() - started; // Metal GPU times use system mach time, unlike CLOCK_MONOTONIC on // macOS. Read only after completion; unavailable timestamps stay -1. const double gpu_start = cb.GPUStartTime; const double gpu_end = cb.GPUEndTime; if (gpu_start > 0.0 && gpu_start >= mach_started && gpu_end >= gpu_start && mach_returned >= gpu_end) { sample->gpu_wait_seconds = gpu_start - mach_started; // Includes GPU scheduling/preemption, not exclusive busy time. sample->gpu_interval_seconds = gpu_end - gpu_start; sample->host_return_seconds = mach_returned - gpu_end; } return cb.status == MTLCommandBufferStatusCompleted; } }