From 094b0d0ad502a0e6a35ae35620ea7eb6954c91ae Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Thu, 13 Nov 2025 06:51:30 -0800 Subject: [PATCH 001/205] WIP changes --- .../CommonUtilities/CommonUtilities.vcxproj | 5 + .../CommonUtilities.vcxproj.filters | 15 +++ .../CommonUtilities/mc/MetricsAdapters.cpp | 3 + .../CommonUtilities/mc/MetricsAdapters.h | 3 + .../CommonUtilities/mc/MetricsCalculator.cpp | 3 + .../CommonUtilities/mc/MetricsCalculator.h | 3 + .../CommonUtilities/mc/MetricsPolicies.cpp | 3 + .../CommonUtilities/mc/MetricsPolicies.h | 3 + .../CommonUtilities/mc/MetricsTypes.cpp | 31 +++++ .../CommonUtilities/mc/MetricsTypes.h | 117 ++++++++++++++++++ 10 files changed, 186 insertions(+) create mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsAdapters.cpp create mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsAdapters.h create mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp create mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h create mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsPolicies.cpp create mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsPolicies.h create mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp create mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsTypes.h diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj index 2ab93cde..fd4f0f76 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj @@ -68,6 +68,10 @@ + + + + @@ -150,6 +154,7 @@ + diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters index 5abadad4..b99e80b3 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters @@ -294,6 +294,18 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + @@ -470,6 +482,9 @@ Source Files + + Source Files + diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsAdapters.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsAdapters.cpp new file mode 100644 index 00000000..b284ec1c --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/MetricsAdapters.cpp @@ -0,0 +1,3 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#pragma once diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsAdapters.h b/IntelPresentMon/CommonUtilities/mc/MetricsAdapters.h new file mode 100644 index 00000000..b284ec1c --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/MetricsAdapters.h @@ -0,0 +1,3 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#pragma once diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp new file mode 100644 index 00000000..b284ec1c --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -0,0 +1,3 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#pragma once diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h new file mode 100644 index 00000000..b284ec1c --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h @@ -0,0 +1,3 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#pragma once diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.cpp new file mode 100644 index 00000000..b284ec1c --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.cpp @@ -0,0 +1,3 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#pragma once diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.h b/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.h new file mode 100644 index 00000000..b284ec1c --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.h @@ -0,0 +1,3 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#pragma once diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp new file mode 100644 index 00000000..7dced920 --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp @@ -0,0 +1,31 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#include "MetricsTypes.h" + +#include "../PresentData/PresentMonTraceConsumer.hpp" +#include "../IntelPresentMon/PresentMonUtils/StreamFormat.h" + +namespace pmon::util::metrics { + + PresentSnapshot PresentSnapshot::FromCircularBuffer(const PmNsmPresentEvent& p) { + PresentSnapshot snap{}; + + snap.presentStartTime = p.PresentStartTime; + snap.processId = p.ProcessId; + snap.t + snap.timeInPresent = p.TimeInPresent; + snap.readyTime = p.ReadyTime; + // ... etc ... + + // Normalize parallel arrays to vector + snap.displayed.reserve(p.DisplayedCount); + for (size_t i = 0; i < p.DisplayedCount; ++i) { + snap.displayed.push_back({ + p.Displayed_FrameType[i], + p.Displayed_ScreenTime[i] + }); + } + + return snap; + } +} \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h new file mode 100644 index 00000000..d83b3335 --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h @@ -0,0 +1,117 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#pragma once +#include +#include +#include + +// Forward declarations for external types +enum class FrameType; // From PresentData +enum class PresentResult; // From PresentData +struct PmNsmPresentEvent; // From PresentMonUtils + +namespace pmon::util::metrics { + + // Immutable snapshot - safe for both ownership models + struct PresentSnapshot { + // Timing Data + uint64_t presentStartTime; + uint64_t readyTime; + uint64_t timeInPresent; + uint64_t gpuStartTime; + uint64_t gpuDuration; + uint64_t gpuVideoDuration; + + // Propagated Timing (for frame generation) + uint64_t appPropagatedPresentStartTime; + uint64_t appPropagatedTimeInPresent; + uint64_t appPropagatedGPUStartTime; + uint64_t appPropagatedReadyTime; + uint64_t appPropagatedGPUDuration; + uint64_t appPropagatedGPUVideoDuration; + + // Instrumented Timestamps + uint64_t appSimStartTime; + uint64_t appSleepStartTime; + uint64_t appSleepEndTime; + uint64_t appRenderSubmitStartTime; + std::pair appInputSample; // time, frame_id + + // PC Latency Timestamps + uint64_t pclSimStartTime; + uint64_t pclInputPingTime; + + // Input Device Timestamps + uint64_t inputTime; // All input devices + uint64_t mouseClickTime; // Mouse click specific + + // Display Data (normalized from both formats) + struct DisplayEntry { + FrameType frameType; + uint64_t screenTime; + }; + std::vector displayed; + + // Vendor-Specific + uint64_t flipDelay; // NVIDIA + + // Metadata + PresentResult finalState; + uint32_t processId; + uint64_t swapChainAddress; + + // Factory Methods + // Console uses ConsoleAdapter directly, so no conversion needed + static PresentSnapshot FromCircularBuffer(const PmNsmPresentEvent& p); + }; + + struct FrameMetrics { + // Core Timing (always computed) + uint64_t timeInSeconds; // QPC timestamp + double msBetweenPresents; + double msInPresentApi; + double msUntilRenderComplete; + + // CPU Metrics (app frames only) + uint64_t cpuStartQpc; + double msCPUBusy; + double msCPUWait; + + // GPU Metrics (app frames only) + double msGPULatency; + double msGPUBusy; + double msVideoBusy; + double msGPUWait; + + // Display Metrics (displayed frames only) + double msDisplayLatency; + double msDisplayedTime; + double msUntilDisplayed; + double msBetweenDisplayChange; + uint64_t screenTimeQpc; + + // Input Latency (optional, app+displayed only) + std::optional msClickToPhotonLatency; + std::optional msAllInputPhotonLatency; + std::optional msInstrumentedInputTime; + std::optional msPcLatency; + + // Animation (optional, app+displayed only) + std::optional msAnimationError; + std::optional msAnimationTime; + + // Instrumented Metrics (optional) + std::optional msInstrumentedLatency; + std::optional msInstrumentedRenderLatency; + std::optional msInstrumentedSleep; + std::optional msInstrumentedGpuLatency; + std::optional msReadyTimeToDisplayLatency; + std::optional msBetweenSimStarts; + + // Vendor-specific (optional) + std::optional msFlipDelay; // NVIDIA + + // Frame Classification + FrameType frameType; + }; +} \ No newline at end of file From a35dbc44142694f15c417a9018672a702da0b236 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Thu, 13 Nov 2025 06:51:30 -0800 Subject: [PATCH 002/205] WIP changes --- .../CommonUtilities/CommonUtilities.vcxproj | 5 + .../CommonUtilities.vcxproj.filters | 15 ++ .../CommonUtilities/mc/MetricsCalculator.cpp | 3 + .../CommonUtilities/mc/MetricsCalculator.h | 85 ++++++++++++ .../CommonUtilities/mc/MetricsPolicies.cpp | 3 + .../CommonUtilities/mc/MetricsPolicies.h | 76 ++++++++++ .../CommonUtilities/mc/MetricsTypes.cpp | 64 +++++++++ .../CommonUtilities/mc/MetricsTypes.h | 131 ++++++++++++++++++ .../CommonUtilities/mc/QpcCalculator.h | 37 +++++ PresentData/ConsoleAdapter.h | 69 +++++++++ PresentData/PresentData.vcxproj | 1 + PresentData/PresentData.vcxproj.filters | 1 + 12 files changed, 490 insertions(+) create mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp create mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h create mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsPolicies.cpp create mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsPolicies.h create mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp create mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsTypes.h create mode 100644 IntelPresentMon/CommonUtilities/mc/QpcCalculator.h create mode 100644 PresentData/ConsoleAdapter.h diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj index 2ab93cde..b5ee2f5a 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj @@ -68,6 +68,10 @@ + + + + @@ -150,6 +154,7 @@ + diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters index 5abadad4..6f106a90 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters @@ -294,6 +294,18 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + @@ -470,6 +482,9 @@ Source Files + + Source Files + diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp new file mode 100644 index 00000000..b284ec1c --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -0,0 +1,3 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#pragma once diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h new file mode 100644 index 00000000..ec364f0b --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h @@ -0,0 +1,85 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#pragma once +#include +#include +#include "QpcCalculator.h" +#include "MetricsTypes.h" + +namespace pmon::util::metrics +{ + // Result of metric calculation for one display index + struct ComputedMetrics { + FrameMetrics metrics; + + // State changes to apply to SwapChain + struct StateDeltas { + std::optional newEmaInput2FrameStart; + std::optional newAccumulatedInput2FrameStart; + std::optional newLastReceivedPclSimStart; + std::optional newLastReceivedPclInputTime; + std::optional lastReceivedNotDisplayedAllInputTime; + std::optional lastReceivedNotDisplayedMouseClickTime; + std::optional lastReceivedNotDisplayedAppProviderInputTime; + bool shouldResetInputTimes = false; + } stateDeltas; + }; + + // Context for single display index computation + struct FrameComputationContext { + size_t displayIndex; + size_t appIndex; // Index of application frame in display array + bool displayed; + uint64_t screenTime; + uint64_t nextScreenTime; + uint64_t cpuStart; + uint64_t simStartTime; + }; + + // Index calculation helper + struct DisplayIndexing { + size_t startIndex; // First display index to process + size_t endIndex; // One past last index + size_t appIndex; // Index of app frame (or SIZE_MAX if none) + bool hasNextDisplayed; + + // Template version works with both ConsoleAdapter and PresentSnapshot + template + static DisplayIndexing Calculate( + const PresentT& present, + const PresentT* nextDisplayed); + }; + + // === Pure Calculation Functions === + + // Main computation kernel (pure function) + // Template version - works with different input types + // PresentT: ConsoleAdapter (Strategy A) or PresentSnapshot (Strategy B for Console, always for GUI) + // ChainT: SwapChainData (Console) or fpsSwapChainData (GUI) - both stable references + template + ComputedMetrics ComputeFrameMetrics( + const QpcCalculator& qpc, + const PresentT& present, + const PresentT* nextDisplayed, + const ChainT& chain, // Direct reference to stable map entry + const FrameComputationContext& context); + + // Helper: Calculate CPU start time + template + uint64_t CalculateCPUStart( + const ChainT& chainState, + const PresentT& present); + + // Helper: Calculate simulation start time (for animation error) + template + uint64_t CalculateSimStartTime( + const ChainT& chainState, + const PresentT& present, + AnimationErrorSource source); + + // Helper: Calculate animation time + double CalculateAnimationTime( + const QpcCalculator& qpc, + uint64_t firstAppSimStartTime, + uint64_t currentSimTime); +} \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.cpp new file mode 100644 index 00000000..b284ec1c --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.cpp @@ -0,0 +1,3 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#pragma once diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.h b/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.h new file mode 100644 index 00000000..09baca9d --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.h @@ -0,0 +1,76 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#pragma once +#include "MetricsTypes.h" +#include "MetricsCalculator.h" + +struct SwapChainData; // Defined in PresentData/PresentMonTraceConsumer.hpp +struct fpsSwapChainData; // Defined in IntelPresentMon/Interprocess/source/SyncData.h +struct ProcessInfo; // Defined in PresentData/PresentMonTraceConsumer.hpp +class InputToFsManager; // Defined in IntelPresentMon/Interprocess/ + +namespace pmon::util::metrics { + // Policy interface - works with forward-declared types + template + class MetricsOutputPolicy { + public: + virtual ~MetricsOutputPolicy() = default; + + virtual void OnMetricsComputed( + const FrameMetrics& metrics, + const ComputedMetrics::StateDeltas& deltas, + SwapChainT& chain, // ← Forward-declared type is fine here + size_t displayIndex, + bool isAppIndex) = 0; + + virtual void OnFrameComplete( + const PresentSnapshot& present, + SwapChainT& chain) = 0; // ← Forward-declared type is fine here + }; + + // Console policy - forward-declared types in declaration + class ConsoleMetricsPolicy : public MetricsOutputPolicy { + ProcessInfo* processInfo_; // ← Pointer to forward-declared type is fine + bool isRecording_; + bool computeAvg_; + + public: + ConsoleMetricsPolicy( + ProcessInfo* processInfo, + bool isRecording, + bool computeAvg); + + void OnMetricsComputed( + const FrameMetrics& metrics, + const ComputedMetrics::StateDeltas& deltas, + SwapChainData& chain, + size_t displayIndex, + bool isAppIndex) override; + + void OnFrameComplete( + const PresentSnapshot& present, + SwapChainData& chain) override; + }; + + // Middleware policy - forward-declared types in declaration + class MiddlewareMetricsPolicy : public MetricsOutputPolicy { + InputToFsManager& pclManager_; // ← Reference to forward-declared type is fine + uint32_t processId_; + + public: + MiddlewareMetricsPolicy( + InputToFsManager& pclManager, + uint32_t processId); + + void OnMetricsComputed( + const FrameMetrics& metrics, + const ComputedMetrics::StateDeltas& deltas, + fpsSwapChainData& chain, + size_t displayIndex, + bool isAppIndex) override; + + void OnFrameComplete( + const PresentSnapshot& present, + fpsSwapChainData& chain) override; + }; +} \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp new file mode 100644 index 00000000..1dee3ebd --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp @@ -0,0 +1,64 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#include "MetricsTypes.h" + +#include "../PresentData/PresentMonTraceConsumer.hpp" +#include "../IntelPresentMon/PresentMonUtils/StreamFormat.h" + +namespace pmon::util::metrics { + + PresentSnapshot PresentSnapshot::FromCircularBuffer(const PmNsmPresentEvent& p) { + PresentSnapshot snap{}; + + snap.presentStartTime = p.PresentStartTime; + snap.readyTime = p.ReadyTime; + snap.timeInPresent = p.TimeInPresent; + snap.gpuStartTime = p.GPUStartTime; + snap.gpuDuration = p.GPUDuration; + snap.gpuVideoDuration = p.GPUVideoDuration; + + snap.appPropagatedPresentStartTime = p.AppPropagatedPresentStartTime; + snap.appPropagatedTimeInPresent = p.AppPropagatedTimeInPresent; + snap.appPropagatedGPUStartTime = p.AppPropagatedGPUStartTime; + snap.appPropagatedReadyTime = p.AppPropagatedReadyTime; + snap.appPropagatedGPUDuration = p.AppPropagatedGPUDuration; + snap.appPropagatedGPUVideoDuration = p.AppPropagatedGPUVideoDuration; + + snap.appSleepStartTime = p.AppSleepStartTime; + snap.appSleepEndTime = p.AppSleepEndTime; + snap.appSimStartTime = p.AppSimStartTime; + snap.appSleepEndTime = p.AppSleepEndTime; + snap.appRenderSubmitStartTime = p.AppRenderSubmitStartTime; + snap.appRenderSubmitEndTime = p.AppRenderSubmitEndTime; + snap.appPresentStartTime = p.AppPresentStartTime; + snap.appPresentEndTime = p.AppPresentEndTime; + snap.appInputSample = { p.AppInputTime, p.AppInputType }; + + snap.inputTime = p.InputTime; + snap.mouseClickTime = p.MouseClickTime; + + snap.pclSimStartTime = p.PclSimStartTime; + snap.pclInputPingTime = p.PclInputPingTime; + snap.flipDelay = p.FlipDelay; + snap.FlipToken = p.FlipToken; + + // Normalize parallel arrays to vector + snap.displayed.reserve(p.DisplayedCount); + for (size_t i = 0; i < p.DisplayedCount; ++i) { + snap.displayed.push_back({ + p.Displayed_FrameType[i], + p.Displayed_ScreenTime[i] + }); + } + + snap.finalState = p.FinalState; + snap.swapChainAddress = p.SwapChainAddress; + snap.frameId = p.FrameId; + snap.processId = p.ProcessId; + snap.threadId = p.ThreadId; + // Application provided frame ID + // snap.appFrameId = p.AppFrameId; + + return snap; + } +} \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h new file mode 100644 index 00000000..9c02b2d5 --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h @@ -0,0 +1,131 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#pragma once +#include +#include +#include +#include + +// Forward declarations for external types +enum class FrameType; // From PresentData +enum class PresentResult; // From PresentData +enum class InputDeviceType; // From PresentData +struct PmNsmPresentEvent; // From PresentMonUtils + +namespace pmon::util::metrics { + + // What the animation error calculation is based on + enum class AnimationErrorSource { + CpuStart, + AppProvider, + PCLatency, + }; + + // Immutable snapshot - safe for both ownership models + struct PresentSnapshot { + // Timing Data + uint64_t presentStartTime; + uint64_t readyTime; + uint64_t timeInPresent; + uint64_t gpuStartTime; + uint64_t gpuDuration; + uint64_t gpuVideoDuration; + + // Used to track the application work when Intel XeSS-FG is enabled + uint64_t appPropagatedPresentStartTime; + uint64_t appPropagatedTimeInPresent; + uint64_t appPropagatedGPUStartTime; + uint64_t appPropagatedReadyTime; + uint64_t appPropagatedGPUDuration; + uint64_t appPropagatedGPUVideoDuration; + + // Instrumented Timestamps + uint64_t appSimStartTime; + uint64_t appSleepStartTime; + uint64_t appSleepEndTime; + uint64_t appRenderSubmitStartTime; + uint64_t appRenderSubmitEndTime; + uint64_t appPresentStartTime; + uint64_t appPresentEndTime; + std::pair appInputSample; // time, input type + + // Input Device Timestamps + uint64_t inputTime; // All input devices + uint64_t mouseClickTime; // Mouse click specific + + // Display Data (normalized from both formats) + struct DisplayEntry { + FrameType frameType; + uint64_t screenTime; + }; + std::vector displayed; + + // PC Latency data + uint64_t pclSimStartTime; + uint64_t pclInputPingTime; + uint64_t flipDelay; + uint32_t FlipToken; + + // Metadata + PresentResult finalState; + uint32_t processId; + uint32_t threadId; + uint64_t swapChainAddress; + uint32_t frameId; + uint32_t appFrameId; + + // Factory Methods + // Console uses ConsoleAdapter directly, so no conversion needed + static PresentSnapshot FromCircularBuffer(const PmNsmPresentEvent& p); + }; + + struct FrameMetrics { + // Core Timing (always computed) + uint64_t timeInSeconds; // QPC timestamp + double msBetweenPresents; + double msInPresentApi; + double msUntilRenderComplete; + + // CPU Metrics (app frames only) + uint64_t cpuStartQpc; + double msCPUBusy; + double msCPUWait; + + // GPU Metrics (app frames only) + double msGPULatency; + double msGPUBusy; + double msVideoBusy; + double msGPUWait; + + // Display Metrics (displayed frames only) + double msDisplayLatency; + double msDisplayedTime; + double msUntilDisplayed; + double msBetweenDisplayChange; + uint64_t screenTimeQpc; + + // Input Latency (optional, app+displayed only) + std::optional msClickToPhotonLatency; + std::optional msAllInputPhotonLatency; + std::optional msInstrumentedInputTime; + std::optional msPcLatency; + + // Animation (optional, app+displayed only) + std::optional msAnimationError; + std::optional msAnimationTime; + + // Instrumented Metrics (optional) + std::optional msInstrumentedLatency; + std::optional msInstrumentedRenderLatency; + std::optional msInstrumentedSleep; + std::optional msInstrumentedGpuLatency; + std::optional msReadyTimeToDisplayLatency; + std::optional msBetweenSimStarts; + + // PCLatency (optional) + std::optional msFlipDelay; // NVIDIA + + // Frame Classification + FrameType frameType; + }; +} \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/mc/QpcCalculator.h b/IntelPresentMon/CommonUtilities/mc/QpcCalculator.h new file mode 100644 index 00000000..4647d759 --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/QpcCalculator.h @@ -0,0 +1,37 @@ +// Copyright (C) 2022 Intel Corporation +// SPDX-License-Identifier: MIT +#pragma once +#include + +namespace pmon::util::metrics +{ + // Simple value-based QPC math utility + // No polymorphism, no environment dependencies - just QPC calculations + class QpcCalculator { + uint64_t qpcFrequency_; + uint64_t sessionStartTimestamp_; + + public: + QpcCalculator(uint64_t qpcFrequency, uint64_t sessionStartTimestamp) + : qpcFrequency_(qpcFrequency) + , sessionStartTimestamp_(sessionStartTimestamp) + { + } + + // Convert QPC duration to milliseconds + double TimestampDeltaToMilliSeconds(uint64_t duration) const { + return duration * 1000.0 / qpcFrequency_; + } + + // Convert time between two QPC timestamps to milliseconds + double TimestampDeltaToUnsignedMilliSeconds(uint64_t start, uint64_t end) const { + return (end - start) * 1000.0 / qpcFrequency_; + } + + // Get trace session start timestamp (for animation time calculations) + uint64_t GetStartTimestamp() const { + return sessionStartTimestamp_; + } + }; + +} \ No newline at end of file diff --git a/PresentData/ConsoleAdapter.h b/PresentData/ConsoleAdapter.h new file mode 100644 index 00000000..a833d7e6 --- /dev/null +++ b/PresentData/ConsoleAdapter.h @@ -0,0 +1,69 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#pragma once +#include +#include +#include +#include "PresentMonTraceConsumer.hpp" + +namespace pmon::util::metrics { + // Zero-cost wrapper around Console's shared_ptr + // Provides same interface as PresentSnapshot via inline getters + class ConsoleAdapter { + const PresentEvent* ptr_; + + public: + // Constructors + explicit ConsoleAdapter(const std::shared_ptr& p) : ptr_(p.get()) {} + explicit ConsoleAdapter(const PresentEvent* p) : ptr_(p) {} + + // Inline getters - compile to direct field access (zero overhead) + uint64_t getPresentStartTime() const { return ptr_->PresentStartTime; } + uint64_t getReadyTime() const { return ptr_->ReadyTime; } + uint64_t getTimeInPresent() const { return ptr_->TimeInPresent; } + uint64_t getGPUStartTime() const { return ptr_->GPUStartTime; } + uint64_t getGPUDuration() const { return ptr_->GPUDuration; } + uint64_t getGPUVideoDuration() const { return ptr_->GPUVideoDuration; } + + // Propagated data + uint64_t getAppPropagatedPresentStartTime() const { return ptr_->AppPropagatedPresentStartTime; } + uint64_t getAppPropagatedTimeInPresent() const { return ptr_->AppPropagatedTimeInPresent; } + uint64_t getAppPropagatedGPUStartTime() const { return ptr_->AppPropagatedGPUStartTime; } + uint64_t getAppPropagatedReadyTime() const { return ptr_->AppPropagatedReadyTime; } + uint64_t getAppPropagatedGPUDuration() const { return ptr_->AppPropagatedGPUDuration; } + uint64_t getAppPropagatedGPUVideoDuration() const { return ptr_->AppPropagatedGPUVideoDuration; } + + // Instrumented data + uint64_t getAppSimStartTime() const { return ptr_->AppSimStartTime; } + uint64_t getAppSleepStartTime() const { return ptr_->AppSleepStartTime; } + uint64_t getAppSleepEndTime() const { return ptr_->AppSleepEndTime; } + uint64_t getAppRenderSubmitStartTime() const { return ptr_->AppRenderSubmitStartTime; } + std::pair getAppInputSample() const { return ptr_->AppInputSample; } + + // PC Latency + uint64_t getPclSimStartTime() const { return ptr_->PclSimStartTime; } + uint64_t getPclInputPingTime() const { return ptr_->PclInputPingTime; } + + // Input tracking + uint64_t getInputTime() const { return ptr_->InputTime; } + uint64_t getMouseClickTime() const { return ptr_->MouseClickTime; } + + // Display data - normalized access + size_t getDisplayedCount() const { return ptr_->Displayed.size(); } + FrameType getDisplayedFrameType(size_t idx) const { return ptr_->Displayed[idx].first; } + uint64_t getDisplayedScreenTime(size_t idx) const { return ptr_->Displayed[idx].second; } + + // Vendor-specific + uint64_t getFlipDelay() const { return ptr_->FlipDelay; } + + // Metadata + PresentResult getFinalState() const { return ptr_->FinalState; } + uint32_t getProcessId() const { return ptr_->ProcessId; } + uint64_t getSwapChainAddress() const { return ptr_->SwapChainAddress; } + + // Predicates + bool hasAppPropagatedData() const { return ptr_->AppPropagatedPresentStartTime != 0; } + bool hasPclSimStartTime() const { return ptr_->PclSimStartTime != 0; } + bool hasPclInputPingTime() const { return ptr_->PclInputPingTime != 0; } + }; +} \ No newline at end of file diff --git a/PresentData/PresentData.vcxproj b/PresentData/PresentData.vcxproj index 639290ba..3557af13 100644 --- a/PresentData/PresentData.vcxproj +++ b/PresentData/PresentData.vcxproj @@ -341,6 +341,7 @@ + diff --git a/PresentData/PresentData.vcxproj.filters b/PresentData/PresentData.vcxproj.filters index 262423f1..19e57419 100644 --- a/PresentData/PresentData.vcxproj.filters +++ b/PresentData/PresentData.vcxproj.filters @@ -48,6 +48,7 @@ + From 5c84a373c7d22c0c47cb0299e768f2a7678c4b60 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Fri, 14 Nov 2025 07:51:27 -0800 Subject: [PATCH 003/205] Added in swapchain tests --- .../CommonUtilities/CommonUtilities.vcxproj | 1 + .../CommonUtilities.vcxproj.filters | 3 + .../CommonUtilities/mc/MetricsPolicies.cpp | 3 + .../CommonUtilities/mc/SwapChainCoreState.h | 73 ++++ .../PresentMonAPI2Tests.vcxproj | 1 + .../PresentMonAPI2Tests.vcxproj.filters | 3 + .../PresentMonAPI2Tests/SwapChainTests.cpp | 384 ++++++++++++++++++ 7 files changed, 468 insertions(+) create mode 100644 IntelPresentMon/CommonUtilities/mc/SwapChainCoreState.h create mode 100644 IntelPresentMon/PresentMonAPI2Tests/SwapChainTests.cpp diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj index 59570217..291349c3 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj @@ -72,6 +72,7 @@ + diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters index 6f106a90..7e22cc8f 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters @@ -306,6 +306,9 @@ Header Files + + Header Files + diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.cpp index b284ec1c..0d0ff6b5 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.cpp @@ -1,3 +1,6 @@ // Copyright (C) 2025 Intel Corporation // SPDX-License-Identifier: MIT #pragma once +#include "MetricsPolicies.h" + +#include "../../../PresentData/PresentMonTraceConsumer.hpp" // SwapChainData, ProcessInfo \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/mc/SwapChainCoreState.h b/IntelPresentMon/CommonUtilities/mc/SwapChainCoreState.h new file mode 100644 index 00000000..880402c5 --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/SwapChainCoreState.h @@ -0,0 +1,73 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#pragma once + +#include +#include +#include "MetricsTypes.h" + +namespace pmon::util::metrics { + +template +struct SwapChainCoreState { + + // Pending and Historical Presents + + // Pending presents waiting for the next displayed present. + std::vector pendingPresents; + + // The most recent present that has been processed (e.g., output into CSV and/or used for frame + // statistics). + std::optional lastPresent; + + // The most recent app present that has been processed (e.g., output into CSV and/or used for frame + // statistics). + std::optional lastAppPresent; + + // Timing State + + // QPC of the last simulation start time regardless of whether it was displayed or not + uint64_t lastSimStartTime = 0; + + // The simulation start time of the most recent displayed frame + uint64_t lastDisplayedSimStartTime = 0; + + // The screen time of the most recent displayed frame (any type) + uint64_t lastDisplayedScreenTime = 0; + + // The screen time of the most recent displayed application frame + uint64_t lastDisplayedAppScreenTime = 0; + + // QPC of the first received simulation start time from the application provider + uint64_t firstAppSimStartTime = 0; + + // Dropped Frame Input Tracking + + // QPC of last received all-input that did not make it to the screen (dropped Present) + uint64_t lastReceivedNotDisplayedAllInputTime = 0; + + // QPC of last received mouse-click input that did not make it to the screen (dropped Present) + uint64_t lastReceivedNotDisplayedMouseClickTime = 0; + + // QPC of the last received app provider input that did not make it to the screen (dropped Present) + uint64_t lastReceivedNotDisplayedAppProviderInputTime = 0; + + // QPC of last received PC Latency simulation start that did not make it to the screen (dropped Present) + uint64_t lastReceivedNotDisplayedPclSimStart = 0; + + // QPC of last received PC Latency input time that did not make it to the screen (dropped Present) + uint64_t lastReceivedNotDisplayedPclInputTime = 0; + + // Animation Error Configuration + AnimationErrorSource animationErrorSource = AnimationErrorSource::CpuStart; + + // PC Latency Accumulation + + // Accumulated PC latency input to frame start time due to dropped Present() calls + double accumulatedInput2FrameStartTime = 0.0; + + // NVIDIA Specific Tracking + uint64_t lastDisplayedFlipDelay = 0; +}; + +} \ No newline at end of file diff --git a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj index 0f33d4de..1b7649c1 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj +++ b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj @@ -100,6 +100,7 @@ + diff --git a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj.filters b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj.filters index beac5d79..6bcf81a2 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj.filters +++ b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj.filters @@ -39,6 +39,9 @@ Source Files + + Source Files + diff --git a/IntelPresentMon/PresentMonAPI2Tests/SwapChainTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/SwapChainTests.cpp new file mode 100644 index 00000000..4340d796 --- /dev/null +++ b/IntelPresentMon/PresentMonAPI2Tests/SwapChainTests.cpp @@ -0,0 +1,384 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#include "CppUnitTest.h" +#include "../CommonUtilities/mc/SwapChainCoreState.h" +#include "../CommonUtilities/mc/MetricsTypes.h" +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace SwapChainTests +{ + // Mock types for testing template instantiation + struct MockPresentEvent { + uint64_t timestamp = 0; + int data = 0; + }; + + TEST_CLASS(SwapChainCoreStateTests) + { + public: + + TEST_METHOD(DefaultConstruction_AllFieldsInitialized) + { + // Test with a simple type to verify default initialization + pmon::util::metrics::SwapChainCoreState core; + + // Verify timing state defaults to 0 + Assert::AreEqual(uint64_t(0), core.lastSimStartTime); + Assert::AreEqual(uint64_t(0), core.lastDisplayedSimStartTime); + Assert::AreEqual(uint64_t(0), core.lastDisplayedScreenTime); + Assert::AreEqual(uint64_t(0), core.lastDisplayedAppScreenTime); + Assert::AreEqual(uint64_t(0), core.firstAppSimStartTime); + + // Verify dropped frame tracking defaults to 0 + Assert::AreEqual(uint64_t(0), core.lastReceivedNotDisplayedAllInputTime); + Assert::AreEqual(uint64_t(0), core.lastReceivedNotDisplayedMouseClickTime); + Assert::AreEqual(uint64_t(0), core.lastReceivedNotDisplayedAppProviderInputTime); + Assert::AreEqual(uint64_t(0), core.lastReceivedNotDisplayedPclSimStart); + Assert::AreEqual(uint64_t(0), core.lastReceivedNotDisplayedPclInputTime); + + // Verify PC Latency accumulation defaults to 0.0 + Assert::AreEqual(0.0, core.accumulatedInput2FrameStartTime); + + // Verify NVIDIA-specific defaults to 0 + Assert::AreEqual(uint64_t(0), core.lastDisplayedFlipDelay); + + // Verify animation error source defaults to CpuStart + Assert::IsTrue(core.animationErrorSource == pmon::util::metrics::AnimationErrorSource::CpuStart); + + // Verify optional presents are empty + Assert::IsFalse(core.lastPresent.has_value()); + Assert::IsFalse(core.lastAppPresent.has_value()); + + // Verify pending presents vector is empty + Assert::AreEqual(size_t(0), core.pendingPresents.size()); + } + + TEST_METHOD(SharedPtrInstantiation_ConsolePattern) + { + // Simulates Console usage with shared_ptr + using ConsoleCore = pmon::util::metrics::SwapChainCoreState>; + ConsoleCore core; + + // Create a mock present event + auto present = std::make_shared(); + present->timestamp = 12345; + present->data = 99; + + // Add to pending presents + core.pendingPresents.push_back(present); + Assert::AreEqual(size_t(1), core.pendingPresents.size()); + + // Set as last present + core.lastPresent = present; + Assert::IsTrue(core.lastPresent.has_value()); + Assert::AreEqual(uint64_t(12345), (*core.lastPresent)->timestamp); + + // Verify reference counting works (same pointer) + Assert::IsTrue(core.pendingPresents[0].get() == core.lastPresent->get()); + } + + TEST_METHOD(ValueTypeInstantiation_MiddlewarePattern) + { + // Simulates Middleware usage with value copies + using MiddlewareCore = pmon::util::metrics::SwapChainCoreState; + MiddlewareCore core; + + // Create a mock present event + MockPresentEvent present{}; + present.timestamp = 54321; + present.data = 42; + + // Add to pending presents (value copy) + core.pendingPresents.push_back(present); + Assert::AreEqual(size_t(1), core.pendingPresents.size()); + + // Set as last present (value copy) + core.lastPresent = present; + Assert::IsTrue(core.lastPresent.has_value()); + Assert::AreEqual(uint64_t(54321), core.lastPresent->timestamp); + + // Modify original - copies should be independent + present.timestamp = 99999; + Assert::AreEqual(uint64_t(54321), core.pendingPresents[0].timestamp); + Assert::AreEqual(uint64_t(54321), core.lastPresent->timestamp); + } + + TEST_METHOD(PendingPresents_VectorOperations) + { + using TestCore = pmon::util::metrics::SwapChainCoreState; + TestCore core; + + // Add multiple items + core.pendingPresents.push_back(1); + core.pendingPresents.push_back(2); + core.pendingPresents.push_back(3); + + Assert::AreEqual(size_t(3), core.pendingPresents.size()); + Assert::AreEqual(1, core.pendingPresents[0]); + Assert::AreEqual(2, core.pendingPresents[1]); + Assert::AreEqual(3, core.pendingPresents[2]); + + // Clear pending presents + core.pendingPresents.clear(); + Assert::AreEqual(size_t(0), core.pendingPresents.size()); + } + + TEST_METHOD(OptionalPresents_HasValue) + { + using TestCore = pmon::util::metrics::SwapChainCoreState; + TestCore core; + + // Initially empty + Assert::IsFalse(core.lastPresent.has_value()); + Assert::IsFalse(core.lastAppPresent.has_value()); + + // Set lastPresent + core.lastPresent = 42; + Assert::IsTrue(core.lastPresent.has_value()); + Assert::AreEqual(42, *core.lastPresent); + Assert::IsFalse(core.lastAppPresent.has_value()); + + // Set lastAppPresent + core.lastAppPresent = 99; + Assert::IsTrue(core.lastPresent.has_value()); + Assert::IsTrue(core.lastAppPresent.has_value()); + Assert::AreEqual(42, *core.lastPresent); + Assert::AreEqual(99, *core.lastAppPresent); + + // Reset lastPresent + core.lastPresent.reset(); + Assert::IsFalse(core.lastPresent.has_value()); + Assert::IsTrue(core.lastAppPresent.has_value()); + } + + TEST_METHOD(TimingState_AssignmentAndRetrieval) + { + using TestCore = pmon::util::metrics::SwapChainCoreState; + TestCore core; + + // Set timing values + core.lastSimStartTime = 1000; + core.lastDisplayedSimStartTime = 2000; + core.lastDisplayedScreenTime = 3000; + core.lastDisplayedAppScreenTime = 4000; + core.firstAppSimStartTime = 5000; + + // Verify retrieval + Assert::AreEqual(uint64_t(1000), core.lastSimStartTime); + Assert::AreEqual(uint64_t(2000), core.lastDisplayedSimStartTime); + Assert::AreEqual(uint64_t(3000), core.lastDisplayedScreenTime); + Assert::AreEqual(uint64_t(4000), core.lastDisplayedAppScreenTime); + Assert::AreEqual(uint64_t(5000), core.firstAppSimStartTime); + } + + TEST_METHOD(DroppedFrameTracking_AssignmentAndRetrieval) + { + using TestCore = pmon::util::metrics::SwapChainCoreState; + TestCore core; + + // Set dropped frame tracking values + core.lastReceivedNotDisplayedAllInputTime = 1111; + core.lastReceivedNotDisplayedMouseClickTime = 2222; + core.lastReceivedNotDisplayedAppProviderInputTime = 3333; + core.lastReceivedNotDisplayedPclSimStart = 4444; + core.lastReceivedNotDisplayedPclInputTime = 5555; + + // Verify retrieval + Assert::AreEqual(uint64_t(1111), core.lastReceivedNotDisplayedAllInputTime); + Assert::AreEqual(uint64_t(2222), core.lastReceivedNotDisplayedMouseClickTime); + Assert::AreEqual(uint64_t(3333), core.lastReceivedNotDisplayedAppProviderInputTime); + Assert::AreEqual(uint64_t(4444), core.lastReceivedNotDisplayedPclSimStart); + Assert::AreEqual(uint64_t(5555), core.lastReceivedNotDisplayedPclInputTime); + } + + TEST_METHOD(PCLatencyAccumulation_DoubleType) + { + using TestCore = pmon::util::metrics::SwapChainCoreState; + TestCore core; + + // Initially 0.0 + Assert::AreEqual(0.0, core.accumulatedInput2FrameStartTime); + + // Set value + core.accumulatedInput2FrameStartTime = 16.7; + Assert::AreEqual(16.7, core.accumulatedInput2FrameStartTime, 0.001); + + // Accumulate more + core.accumulatedInput2FrameStartTime += 8.3; + Assert::AreEqual(25.0, core.accumulatedInput2FrameStartTime, 0.001); + + // Reset + core.accumulatedInput2FrameStartTime = 0.0; + Assert::AreEqual(0.0, core.accumulatedInput2FrameStartTime); + } + + TEST_METHOD(AnimationErrorSource_EnumAssignment) + { + using TestCore = pmon::util::metrics::SwapChainCoreState; + TestCore core; + + // Default is CpuStart + Assert::IsTrue(core.animationErrorSource == pmon::util::metrics::AnimationErrorSource::CpuStart); + + // Change to AppProvider + core.animationErrorSource = pmon::util::metrics::AnimationErrorSource::AppProvider; + Assert::IsTrue(core.animationErrorSource == pmon::util::metrics::AnimationErrorSource::AppProvider); + Assert::IsFalse(core.animationErrorSource == pmon::util::metrics::AnimationErrorSource::CpuStart); + + // Change to PCLatency + core.animationErrorSource = pmon::util::metrics::AnimationErrorSource::PCLatency; + Assert::IsTrue(core.animationErrorSource == pmon::util::metrics::AnimationErrorSource::PCLatency); + Assert::IsFalse(core.animationErrorSource == pmon::util::metrics::AnimationErrorSource::AppProvider); + } + + TEST_METHOD(NvidiaFlipDelay_AssignmentAndRetrieval) + { + using TestCore = pmon::util::metrics::SwapChainCoreState; + TestCore core; + + // Default is 0 + Assert::AreEqual(uint64_t(0), core.lastDisplayedFlipDelay); + + // Set value + core.lastDisplayedFlipDelay = 8888; + Assert::AreEqual(uint64_t(8888), core.lastDisplayedFlipDelay); + } + + TEST_METHOD(ComplexType_SharedPtrWithRealData) + { + // More realistic scenario with complex type + struct ComplexEvent { + uint64_t qpcTime; + std::vector displayData; + double metric; + }; + + using ComplexCore = pmon::util::metrics::SwapChainCoreState>; + ComplexCore core; + + // Create event with data + auto event = std::make_shared(); + event->qpcTime = 123456789; + event->displayData = {1, 2, 3, 4, 5}; + event->metric = 16.7; + + // Store in core state + core.pendingPresents.push_back(event); + core.lastPresent = event; + core.lastSimStartTime = event->qpcTime; + + // Verify access + Assert::IsTrue(core.lastPresent.has_value()); + Assert::AreEqual(uint64_t(123456789), (*core.lastPresent)->qpcTime); + Assert::AreEqual(size_t(5), (*core.lastPresent)->displayData.size()); + Assert::AreEqual(16.7, (*core.lastPresent)->metric, 0.001); + Assert::AreEqual(uint64_t(123456789), core.lastSimStartTime); + } + + TEST_METHOD(StateTransitions_SimulateFrameProcessing) + { + // Simulate a realistic frame processing scenario + using TestCore = pmon::util::metrics::SwapChainCoreState; + TestCore core; + + // Frame 1: First frame received + int frame1 = 1000; + core.pendingPresents.push_back(frame1); + core.lastPresent = frame1; + core.lastSimStartTime = 1000; + + Assert::AreEqual(size_t(1), core.pendingPresents.size()); + Assert::AreEqual(1000, *core.lastPresent); + + // Frame 2: App frame received + int frame2 = 2000; + core.pendingPresents.push_back(frame2); + core.lastPresent = frame2; + core.lastAppPresent = frame2; + core.lastSimStartTime = 2000; + core.firstAppSimStartTime = 2000; + + Assert::AreEqual(size_t(2), core.pendingPresents.size()); + Assert::AreEqual(2000, *core.lastAppPresent); + + // Frame 2 displayed: Update display state + core.lastDisplayedSimStartTime = 2000; + core.lastDisplayedScreenTime = 2016; // +16ms latency + core.lastDisplayedAppScreenTime = 2016; + + // Clear pending presents (they've been processed) + core.pendingPresents.clear(); + + Assert::AreEqual(size_t(0), core.pendingPresents.size()); + Assert::AreEqual(uint64_t(2016), core.lastDisplayedScreenTime); + + // Frame 3: Dropped frame (not displayed) + int frame3 = 3000; + core.pendingPresents.push_back(frame3); + core.lastPresent = frame3; + core.lastReceivedNotDisplayedAllInputTime = 2990; // Had input + + Assert::AreEqual(size_t(1), core.pendingPresents.size()); + Assert::AreEqual(uint64_t(2990), core.lastReceivedNotDisplayedAllInputTime); + } + + TEST_METHOD(CopySemantics_Independent) + { + // Verify that copies are independent (important for value types) + using TestCore = pmon::util::metrics::SwapChainCoreState; + TestCore core1; + + // Set state in core1 + core1.lastSimStartTime = 1234; + core1.pendingPresents.push_back(1); + core1.pendingPresents.push_back(2); + core1.lastPresent = 42; + core1.accumulatedInput2FrameStartTime = 16.7; + + // Copy to core2 + TestCore core2 = core1; + + // Verify core2 has same values + Assert::AreEqual(uint64_t(1234), core2.lastSimStartTime); + Assert::AreEqual(size_t(2), core2.pendingPresents.size()); + Assert::AreEqual(42, *core2.lastPresent); + Assert::AreEqual(16.7, core2.accumulatedInput2FrameStartTime, 0.001); + + // Modify core2 + core2.lastSimStartTime = 5678; + core2.pendingPresents.push_back(3); + core2.lastPresent = 99; + + // Verify core1 is unchanged + Assert::AreEqual(uint64_t(1234), core1.lastSimStartTime); + Assert::AreEqual(size_t(2), core1.pendingPresents.size()); + Assert::AreEqual(42, *core1.lastPresent); + } + + TEST_METHOD(MoveSemantics_Efficient) + { + // Verify move semantics work correctly (important for efficiency) + using TestCore = pmon::util::metrics::SwapChainCoreState; + TestCore core1; + + // Set state with large vector + for (int i = 0; i < 100; ++i) { + core1.pendingPresents.push_back(i); + } + core1.lastSimStartTime = 9999; + core1.lastPresent = 42; + + // Move to core2 + TestCore core2 = std::move(core1); + + // Verify core2 has the data + Assert::AreEqual(size_t(100), core2.pendingPresents.size()); + Assert::AreEqual(uint64_t(9999), core2.lastSimStartTime); + Assert::AreEqual(42, *core2.lastPresent); + + // Note: core1 is in moved-from state, don't test its values + } + }; +} From 70905650c8e80795d982c9a75e535332579628d5 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Mon, 17 Nov 2025 09:27:37 -0800 Subject: [PATCH 004/205] Clean up of interfaces for unified metrics --- .../CommonUtilities/CommonUtilities.vcxproj | 1 - .../CommonUtilities.vcxproj.filters | 3 - .../CommonUtilities/mc/MetricsPolicies.cpp | 120 ++++- .../CommonUtilities/mc/MetricsPolicies.h | 4 +- .../CommonUtilities/mc/MetricsTypes.cpp | 3 +- .../PresentMonMiddleware/ConcreteMiddleware.h | 4 + .../PresentMonUtils/StreamFormat.h | 1 + IntelPresentMon/UnitTests/MetricsCore.cpp | 483 ++++++++++++++++++ IntelPresentMon/UnitTests/UnitTests.vcxproj | 1 + .../UnitTests/UnitTests.vcxproj.filters | 1 + PresentMon/PresentMon.hpp | 4 + 11 files changed, 615 insertions(+), 10 deletions(-) create mode 100644 IntelPresentMon/UnitTests/MetricsCore.cpp diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj index 291349c3..dec9e613 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj @@ -69,7 +69,6 @@ - diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters index 7e22cc8f..2d325532 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters @@ -297,9 +297,6 @@ Header Files - - Header Files - Header Files diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.cpp index 0d0ff6b5..2438dc2e 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.cpp @@ -1,6 +1,122 @@ // Copyright (C) 2025 Intel Corporation // SPDX-License-Identifier: MIT -#pragma once +#include "MetricsTypes.h" #include "MetricsPolicies.h" -#include "../../../PresentData/PresentMonTraceConsumer.hpp" // SwapChainData, ProcessInfo \ No newline at end of file +// Include full definitions +#include "../../../PresentMon/PresentMon.hpp" +#include "../..//PresentMonMiddleware/ConcreteMiddleware.h" + +namespace pmon::util::metrics { + +void ConsoleMetricsPolicy::OnMetricsComputed( + const FrameMetrics& metrics, + const ComputedMetrics::StateDeltas& deltas, + SwapChainData& chain, + size_t displayIndex, + bool isAppIndex) +{ + if (deltas.newAccumulatedInput2FrameStart) { + chain.core.accumulatedInput2FrameStartTime = *deltas.newAccumulatedInput2FrameStart; + } + + if (deltas.newLastReceivedPclSimStart) { + chain.core.lastReceivedNotDisplayedPclSimStart = *deltas.newLastReceivedPclSimStart; + } + + if (deltas.lastReceivedNotDisplayedAllInputTime) { + chain.core.lastReceivedNotDisplayedAllInputTime = *deltas.lastReceivedNotDisplayedAllInputTime; + } + + if (deltas.lastReceivedNotDisplayedMouseClickTime) { + chain.core.lastReceivedNotDisplayedMouseClickTime = *deltas.lastReceivedNotDisplayedMouseClickTime; + } + + // Console-specific: Update averages (UNCHANGED - not in core) + if (isRecording_) { + chain.mAvgCPUDuration = UpdateExponentialMovingAverage( + chain.mAvgCPUDuration, metrics.msCPUBusy); + chain.mAvgGPUDuration = UpdateExponentialMovingAverage( + chain.mAvgGPUDuration, metrics.msGPUBusy); + // ... etc + } + + // Write to CSV (unchanged logic) + if (isRecording_) { + WriteCsvRow(processInfo_, metrics); + } +} + +void ConsoleMetricsPolicy::OnFrameComplete( + const PresentSnapshot& present, + SwapChainData& chain) +{ + // BEFORE: Update chain state directly + // chain.mLastPresent = present.toSharedPtr(); + // chain.mLastSimStartTime = present.simStartTime; + + // AFTER: Update through .core member + // Note: Might need to reconstruct shared_ptr from snapshot, or keep original + // Alternatively, policy might track the original shared_ptr separately + chain.core.lastSimStartTime = GetSimStartTime(present); + + // Update last displayed times if this frame was displayed + if (IsDisplayed(present)) { + chain.core.lastDisplayedSimStartTime = GetSimStartTime(present); + chain.core.lastDisplayedScreenTime = present.screenTime; + } +} + +void MiddlewareMetricsPolicy::OnMetricsComputed( + const FrameMetrics& metrics, + const ComputedMetrics::StateDeltas& deltas, + pmon::mid::fpsSwapChainData& chain, + size_t displayIndex, + bool isAppIndex) +{ + // BEFORE: Apply deltas to chain fields directly + // if (deltas.newAccumulatedInput2FrameStart) { + // chain.mAccumulatedInput2FrameStartTime = *deltas.newAccumulatedInput2FrameStart; + // } + + // AFTER: Apply deltas through .core member (IDENTICAL to Console!) + if (deltas.newAccumulatedInput2FrameStart) { + chain.core.accumulatedInput2FrameStartTime = *deltas.newAccumulatedInput2FrameStart; + } + + if (deltas.newLastReceivedPclSimStart) { + chain.core.lastReceivedNotDisplayedPclSimStart = *deltas.newLastReceivedPclSimStart; + } + + if (deltas.lastReceivedNotDisplayedAllInputTime) { + chain.core.lastReceivedNotDisplayedAllInputTime = *deltas.lastReceivedNotDisplayedAllInputTime; + } + + // Middleware-specific: Push to telemetry vectors (UNCHANGED - not in core) + if (isAppIndex) { + chain.mCPUBusy.push_back(metrics.msCPUBusy); + chain.mGPULatency.push_back(metrics.msGPULatency); + chain.mDisplayLatency.push_back(metrics.msDisplayLatency); + } + + // Update InputToFsManager (unchanged logic) + pclManager_.UpdateMetrics(processId_, metrics); +} + +void MiddlewareMetricsPolicy::OnFrameComplete( + const PresentSnapshot& present, + pmon::mid::fpsSwapChainData& chain) +{ + // AFTER: Update through .core member (same pattern as Console) + chain.core.lastSimStartTime = present.simStartTime; + + if (IsDisplayed(present)) { + chain.core.lastDisplayedSimStartTime = present.simStartTime; + chain.core.lastDisplayedScreenTime = present.screenTime; + } + + // Middleware-specific: Update optimization counters (UNCHANGED) + chain.display_count++; +} + +} // namespace pmon::metrics \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.h b/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.h index 6393474e..03f59852 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.h @@ -5,9 +5,9 @@ #include "MetricsCalculator.h" struct SwapChainData; // Defined in PresentData/PresentMonTraceConsumer.hpp -struct fpsSwapChainData; // Defined in IntelPresentMon/Interprocess/source/SyncData.h +struct fpsSwapChainData; // Defined in IntelPresentMon/ struct ProcessInfo; // Defined in PresentData/PresentMonTraceConsumer.hpp -class InputToFsManager; // Defined in IntelPresentMon/Interprocess/ +class InputToFsManager; // Defined in namespace pmon::util::metrics { // Policy interface - works with forward-declared types diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp index 33cf43ef..199a5f03 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp @@ -56,8 +56,7 @@ namespace pmon::util::metrics { snap.frameId = p.FrameId; snap.processId = p.ProcessId; snap.threadId = p.ThreadId; - // Application provided frame ID - // snap.appFrameId = p.AppFrameId; + snap.appFrameId = p.AppFrameId; return snap; } diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h index 1e24419a..aa5c13c5 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h @@ -9,6 +9,7 @@ #include "../CommonUtilities/Hash.h" #include "../CommonUtilities/Math.h" #include "FrameTimingData.h" +#include "../IntelPresentMon/CommonUtilities/mc/SwapChainCoreState.h" namespace pmapi::intro { @@ -77,6 +78,9 @@ namespace pmon::mid // - pending presents whose metrics cannot be computed until future presents are received, // - exponential averages of key metrics displayed in console output. struct fpsSwapChainData { + // Shared swap chain core state + pmon::util::metrics::SwapChainCoreState> core; + // Pending presents waiting for the next displayed present. std::vector mPendingPresents; diff --git a/IntelPresentMon/PresentMonUtils/StreamFormat.h b/IntelPresentMon/PresentMonUtils/StreamFormat.h index 408ddf9f..26a31919 100644 --- a/IntelPresentMon/PresentMonUtils/StreamFormat.h +++ b/IntelPresentMon/PresentMonUtils/StreamFormat.h @@ -129,6 +129,7 @@ struct PmNsmPresentEvent uint32_t DriverThreadId; uint32_t FrameId; // ID for the logical frame that this Present is associated with. + uint32_t AppFrameId; // Application provided frame ID Runtime Runtime; PresentMode PresentMode; diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp new file mode 100644 index 00000000..c3a1a659 --- /dev/null +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -0,0 +1,483 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#include +#include +#include +#include +#include +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace pmon::util::metrics; + +namespace MetricsCoreTests +{ + // ============================================================================ + // SECTION 1: Core Types & Foundation + // ============================================================================ + + TEST_CLASS(QpcCalculatorTests) + { + public: + TEST_METHOD(TimestampDeltaToMilliSeconds_BasicConversion) + { + // 10MHz QPC frequency (10,000,000 ticks per second) + QpcCalculator qpc(10'000'000, 0); + + // 10,000 ticks = 1 millisecond at 10MHz + double result = qpc.TimestampDeltaToMilliSeconds(10'000); + Assert::AreEqual(1.0, result, 0.0001); + } + + TEST_METHOD(TimestampDeltaToMilliSeconds_ZeroDuration) + { + QpcCalculator qpc(10'000'000, 0); + double result = qpc.TimestampDeltaToMilliSeconds(0); + Assert::AreEqual(0.0, result); + } + + TEST_METHOD(TimestampDeltaToMilliSeconds_LargeDuration) + { + QpcCalculator qpc(10'000'000, 0); + + // 100,000,000 ticks = 10,000 milliseconds at 10MHz + double result = qpc.TimestampDeltaToMilliSeconds(100'000'000); + Assert::AreEqual(10'000.0, result, 0.01); + } + + TEST_METHOD(TimestampDeltaToUnsignedMilliSeconds_ForwardTime) + { + QpcCalculator qpc(10'000'000, 0); + + // Start at 1000, end at 11000 (10,000 ticks = 1ms) + double result = qpc.TimestampDeltaToUnsignedMilliSeconds(1000, 11'000); + Assert::AreEqual(1.0, result, 0.0001); + } + + TEST_METHOD(TimestampDeltaToUnsignedMilliSeconds_ZeroDelta) + { + QpcCalculator qpc(10'000'000, 0); + double result = qpc.TimestampDeltaToUnsignedMilliSeconds(5000, 5000); + Assert::AreEqual(0.0, result); + } + + TEST_METHOD(TimestampDeltaToUnsignedMilliSeconds_TypicalFrameTime) + { + // Typical QPC frequency: ~10MHz + QpcCalculator qpc(10'000'000, 0); + + // 16.666ms frame time at 60fps + uint64_t frameTimeTicks = 166'660; + double result = qpc.TimestampDeltaToUnsignedMilliSeconds(0, frameTimeTicks); + Assert::AreEqual(16.666, result, 0.001); + } + + TEST_METHOD(GetStartTimestamp_ReturnsCorrectValue) + { + uint64_t startTime = 123'456'789; + QpcCalculator qpc(10'000'000, startTime); + + Assert::AreEqual(startTime, qpc.GetStartTimestamp()); + } + }; + + TEST_CLASS(PresentSnapshotTests) + { + public: + TEST_METHOD(FromCircularBuffer_CopiesBasicTimingFields) + { + // Create a mock NSM present event + PmNsmPresentEvent nsmEvent{}; + nsmEvent.PresentStartTime = 1000; + nsmEvent.ReadyTime = 2000; + nsmEvent.TimeInPresent = 500; + nsmEvent.GPUStartTime = 1200; + nsmEvent.GPUDuration = 800; + nsmEvent.GPUVideoDuration = 300; + + auto snap = PresentSnapshot::FromCircularBuffer(nsmEvent); + + Assert::AreEqual(1000ull, snap.presentStartTime); + Assert::AreEqual(2000ull, snap.readyTime); + Assert::AreEqual(500ull, snap.timeInPresent); + Assert::AreEqual(1200ull, snap.gpuStartTime); + Assert::AreEqual(800ull, snap.gpuDuration); + Assert::AreEqual(300ull, snap.gpuVideoDuration); + } + + TEST_METHOD(FromCircularBuffer_CopiesAppPropagatedData) + { + PmNsmPresentEvent nsmEvent{}; + nsmEvent.AppPropagatedPresentStartTime = 5000; + nsmEvent.AppPropagatedTimeInPresent = 600; + nsmEvent.AppPropagatedGPUStartTime = 5200; + nsmEvent.AppPropagatedReadyTime = 6000; + nsmEvent.AppPropagatedGPUDuration = 800; + nsmEvent.AppPropagatedGPUVideoDuration = 200; + + auto snap = PresentSnapshot::FromCircularBuffer(nsmEvent); + + Assert::AreEqual(5000ull, snap.appPropagatedPresentStartTime); + Assert::AreEqual(600ull, snap.appPropagatedTimeInPresent); + Assert::AreEqual(5200ull, snap.appPropagatedGPUStartTime); + Assert::AreEqual(6000ull, snap.appPropagatedReadyTime); + Assert::AreEqual(800ull, snap.appPropagatedGPUDuration); + Assert::AreEqual(200ull, snap.appPropagatedGPUVideoDuration); + } + + TEST_METHOD(FromCircularBuffer_CopiesInstrumentedTimestamps) + { + PmNsmPresentEvent nsmEvent{}; + nsmEvent.AppSimStartTime = 100; + nsmEvent.AppSleepStartTime = 200; + nsmEvent.AppSleepEndTime = 250; + nsmEvent.AppRenderSubmitStartTime = 300; + + auto snap = PresentSnapshot::FromCircularBuffer(nsmEvent); + + Assert::AreEqual(100ull, snap.appSimStartTime); + Assert::AreEqual(200ull, snap.appSleepStartTime); + Assert::AreEqual(250ull, snap.appSleepEndTime); + Assert::AreEqual(300ull, snap.appRenderSubmitStartTime); + } + + TEST_METHOD(FromCircularBuffer_CopiesPcLatencyData) + { + PmNsmPresentEvent nsmEvent{}; + nsmEvent.PclSimStartTime = 7000; + nsmEvent.PclInputPingTime = 6500; + + auto snap = PresentSnapshot::FromCircularBuffer(nsmEvent); + + Assert::AreEqual(7000ull, snap.pclSimStartTime); + Assert::AreEqual(6500ull, snap.pclInputPingTime); + } + + TEST_METHOD(FromCircularBuffer_CopiesInputTimes) + { + PmNsmPresentEvent nsmEvent{}; + nsmEvent.InputTime = 8000; + nsmEvent.MouseClickTime = 8050; + + auto snap = PresentSnapshot::FromCircularBuffer(nsmEvent); + + Assert::AreEqual(8000ull, snap.inputTime); + Assert::AreEqual(8050ull, snap.mouseClickTime); + } + + TEST_METHOD(FromCircularBuffer_NormalizesDisplayArrays) + { + PmNsmPresentEvent nsmEvent{}; + nsmEvent.DisplayedCount = 2; + nsmEvent.Displayed_FrameType[0] = FrameType::Application; + nsmEvent.Displayed_ScreenTime[0] = 9000; + nsmEvent.Displayed_FrameType[1] = FrameType::Repeated; + nsmEvent.Displayed_ScreenTime[1] = 9500; + + auto snap = PresentSnapshot::FromCircularBuffer(nsmEvent); + + Assert::AreEqual(size_t(2), snap.displayed.size()); + Assert::IsTrue(snap.displayed[0].frameType == FrameType::Application); + Assert::AreEqual(9000ull, snap.displayed[0].screenTime); + Assert::IsTrue(snap.displayed[1].frameType == FrameType::Repeated); + Assert::AreEqual(9500ull, snap.displayed[1].screenTime); + } + + TEST_METHOD(FromCircularBuffer_HandlesEmptyDisplayArray) + { + PmNsmPresentEvent nsmEvent{}; + nsmEvent.DisplayedCount = 0; + + auto snap = PresentSnapshot::FromCircularBuffer(nsmEvent); + + Assert::AreEqual(size_t(0), snap.displayed.size()); + } + + TEST_METHOD(FromCircularBuffer_CopiesMetadata) + { + PmNsmPresentEvent nsmEvent{}; + nsmEvent.ProcessId = 1234; + nsmEvent.ThreadId = 5678; + nsmEvent.SwapChainAddress = 0xDEADBEEF; + nsmEvent.FrameId = 42; + + auto snap = PresentSnapshot::FromCircularBuffer(nsmEvent); + + Assert::AreEqual(uint32_t(1234), snap.processId); + Assert::AreEqual(uint32_t(5678), snap.threadId); + Assert::AreEqual(uint64_t(0xDEADBEEF), snap.swapChainAddress); + Assert::AreEqual(uint32_t(42), snap.frameId); + } + }; + + // ConsoleAdapter tests are skipped in unit tests because they require PresentData + // which has ETW dependencies. These will be tested during Console integration. + /* + TEST_CLASS(ConsoleAdapterTests) + { + public: + TEST_METHOD(Constructor_AcceptsSharedPtr) + { + auto event = std::make_shared(); + event->PresentStartTime = 1234; + + ConsoleAdapter adapter(event); + + Assert::AreEqual(1234ull, adapter.getPresentStartTime()); + } + + TEST_METHOD(Constructor_AcceptsRawPointer) + { + PresentEvent event{}; + event.ReadyTime = 5678; + + ConsoleAdapter adapter(&event); + + Assert::AreEqual(5678ull, adapter.getReadyTime()); + } + + TEST_METHOD(GettersProvideAccessToAllTimingFields) + { + auto event = std::make_shared(); + event->PresentStartTime = 100; + event->ReadyTime = 200; + event->TimeInPresent = 50; + event->GPUStartTime = 150; + event->GPUDuration = 75; + event->GPUVideoDuration = 25; + + ConsoleAdapter adapter(event); + + Assert::AreEqual(100ull, adapter.getPresentStartTime()); + Assert::AreEqual(200ull, adapter.getReadyTime()); + Assert::AreEqual(50ull, adapter.getTimeInPresent()); + Assert::AreEqual(150ull, adapter.getGPUStartTime()); + Assert::AreEqual(75ull, adapter.getGPUDuration()); + Assert::AreEqual(25ull, adapter.getGPUVideoDuration()); + } + + TEST_METHOD(GettersProvideAccessToAppPropagatedData) + { + auto event = std::make_shared(); + event->AppPropagatedPresentStartTime = 300; + event->AppPropagatedGPUDuration = 100; + + ConsoleAdapter adapter(event); + + Assert::AreEqual(300ull, adapter.getAppPropagatedPresentStartTime()); + Assert::AreEqual(100ull, adapter.getAppPropagatedGPUDuration()); + } + + TEST_METHOD(GettersProvideAccessToPcLatencyData) + { + auto event = std::make_shared(); + event->PclSimStartTime = 400; + event->PclInputPingTime = 350; + + ConsoleAdapter adapter(event); + + Assert::AreEqual(400ull, adapter.getPclSimStartTime()); + Assert::AreEqual(350ull, adapter.getPclInputPingTime()); + } + + TEST_METHOD(GetDisplayedCount_ReturnsVectorSize) + { + auto event = std::make_shared(); + event->Displayed.push_back({FrameType::Application, 100}); + event->Displayed.push_back({FrameType::Repeated, 200}); + + ConsoleAdapter adapter(event); + + Assert::AreEqual(size_t(2), adapter.getDisplayedCount()); + } + + TEST_METHOD(GetDisplayed_ProvidesIndexedAccess) + { + auto event = std::make_shared(); + event->Displayed.push_back({FrameType::Application, 1000}); + event->Displayed.push_back({FrameType::Repeated, 2000}); + + ConsoleAdapter adapter(event); + + Assert::IsTrue(adapter.getDisplayedFrameType(0) == FrameType::Application); + Assert::AreEqual(1000ull, adapter.getDisplayedScreenTime(0)); + Assert::IsTrue(adapter.getDisplayedFrameType(1) == FrameType::Repeated); + Assert::AreEqual(2000ull, adapter.getDisplayedScreenTime(1)); + } + + TEST_METHOD(HasAppPropagatedData_ReturnsTrueWhenPresent) + { + auto event = std::make_shared(); + event->AppPropagatedPresentStartTime = 123; + + ConsoleAdapter adapter(event); + + Assert::IsTrue(adapter.hasAppPropagatedData()); + } + + TEST_METHOD(HasAppPropagatedData_ReturnsFalseWhenZero) + { + auto event = std::make_shared(); + event->AppPropagatedPresentStartTime = 0; + + ConsoleAdapter adapter(event); + + Assert::IsFalse(adapter.hasAppPropagatedData()); + } + + TEST_METHOD(HasPclSimStartTime_ReturnsTrueWhenPresent) + { + auto event = std::make_shared(); + event->PclSimStartTime = 456; + + ConsoleAdapter adapter(event); + + Assert::IsTrue(adapter.hasPclSimStartTime()); + } + + TEST_METHOD(HasPclInputPingTime_ReturnsTrueWhenPresent) + { + auto event = std::make_shared(); + event->PclInputPingTime = 789; + + ConsoleAdapter adapter(event); + + Assert::IsTrue(adapter.hasPclInputPingTime()); + } + }; + */ + + // ============================================================================ + // SECTION 2: SwapChainCoreState + // ============================================================================ + + TEST_CLASS(SwapChainCoreStateTests) + { + public: + // Simple mock type for testing - just needs to be storable + struct MockPresent { + uint64_t presentStartTime = 0; + }; + + TEST_METHOD(DefaultConstruction_InitializesTimestampsToZero) + { + SwapChainCoreState> state; + + Assert::AreEqual(0ull, state.lastSimStartTime); + Assert::AreEqual(0ull, state.lastDisplayedSimStartTime); + Assert::AreEqual(0ull, state.lastDisplayedScreenTime); + Assert::AreEqual(0ull, state.firstAppSimStartTime); + } + + TEST_METHOD(DefaultConstruction_InitializesOptionalPresentToEmpty) + { + SwapChainCoreState> state; + + Assert::IsFalse(state.lastPresent.has_value()); + Assert::IsFalse(state.lastAppPresent.has_value()); + } + + TEST_METHOD(PendingPresents_CanStoreMultipleSharedPtrs) + { + SwapChainCoreState> state; + + auto p1 = std::make_shared(); + auto p2 = std::make_shared(); + auto p3 = std::make_shared(); + + state.pendingPresents.push_back(p1); + state.pendingPresents.push_back(p2); + state.pendingPresents.push_back(p3); + + Assert::AreEqual(size_t(3), state.pendingPresents.size()); + } + + TEST_METHOD(LastPresent_CanBeAssigned) + { + SwapChainCoreState> state; + auto event = std::make_shared(); + event->presentStartTime = 12345; + + state.lastPresent = event; + + Assert::IsTrue(state.lastPresent.has_value()); + Assert::AreEqual(12345ull, (*state.lastPresent)->presentStartTime); + } + + TEST_METHOD(DroppedInputTracking_InitializesToZero) + { + SwapChainCoreState> state; + + Assert::AreEqual(0ull, state.lastReceivedNotDisplayedAllInputTime); + Assert::AreEqual(0ull, state.lastReceivedNotDisplayedMouseClickTime); + Assert::AreEqual(0ull, state.lastReceivedNotDisplayedAppProviderInputTime); + } + + TEST_METHOD(DroppedInputTracking_CanBeUpdated) + { + SwapChainCoreState> state; + + state.lastReceivedNotDisplayedAllInputTime = 1000; + state.lastReceivedNotDisplayedMouseClickTime = 2000; + state.lastReceivedNotDisplayedAppProviderInputTime = 3000; + + Assert::AreEqual(1000ull, state.lastReceivedNotDisplayedAllInputTime); + Assert::AreEqual(2000ull, state.lastReceivedNotDisplayedMouseClickTime); + Assert::AreEqual(3000ull, state.lastReceivedNotDisplayedAppProviderInputTime); + } + + TEST_METHOD(PcLatencyAccumulation_InitializesToZero) + { + SwapChainCoreState> state; + + Assert::AreEqual(0.0, state.accumulatedInput2FrameStartTime); + } + + TEST_METHOD(PcLatencyAccumulation_CanAccumulateDroppedFrames) + { + SwapChainCoreState> state; + + // Simulate accumulating 3 dropped frames at 16.666ms each + state.accumulatedInput2FrameStartTime += 16.666; + state.accumulatedInput2FrameStartTime += 16.666; + state.accumulatedInput2FrameStartTime += 16.666; + + Assert::AreEqual(49.998, state.accumulatedInput2FrameStartTime, 0.001); + } + + TEST_METHOD(AnimationErrorSource_DefaultsToCpuStart) + { + SwapChainCoreState> state; + + Assert::IsTrue(state.animationErrorSource == AnimationErrorSource::CpuStart); + } + + TEST_METHOD(AnimationErrorSource_CanBeChanged) + { + SwapChainCoreState> state; + + state.animationErrorSource = AnimationErrorSource::PCLatency; + Assert::IsTrue(state.animationErrorSource == AnimationErrorSource::PCLatency); + + state.animationErrorSource = AnimationErrorSource::AppProvider; + Assert::IsTrue(state.animationErrorSource == AnimationErrorSource::AppProvider); + } + + TEST_METHOD(WorksWithPresentSnapshotType) + { + // Verify template instantiation works with PresentSnapshot too + SwapChainCoreState state; + + PresentSnapshot snap{}; + snap.presentStartTime = 5000; + + state.pendingPresents.push_back(snap); + state.lastPresent = snap; + + Assert::AreEqual(size_t(1), state.pendingPresents.size()); + Assert::IsTrue(state.lastPresent.has_value()); + Assert::AreEqual(5000ull, state.lastPresent->presentStartTime); + } + }; +} diff --git a/IntelPresentMon/UnitTests/UnitTests.vcxproj b/IntelPresentMon/UnitTests/UnitTests.vcxproj index e7578f2e..c77292a0 100644 --- a/IntelPresentMon/UnitTests/UnitTests.vcxproj +++ b/IntelPresentMon/UnitTests/UnitTests.vcxproj @@ -101,6 +101,7 @@ + diff --git a/IntelPresentMon/UnitTests/UnitTests.vcxproj.filters b/IntelPresentMon/UnitTests/UnitTests.vcxproj.filters index e9ae2968..ec12170e 100644 --- a/IntelPresentMon/UnitTests/UnitTests.vcxproj.filters +++ b/IntelPresentMon/UnitTests/UnitTests.vcxproj.filters @@ -3,6 +3,7 @@ + \ No newline at end of file diff --git a/PresentMon/PresentMon.hpp b/PresentMon/PresentMon.hpp index 389855c2..5a02e8bf 100644 --- a/PresentMon/PresentMon.hpp +++ b/PresentMon/PresentMon.hpp @@ -29,6 +29,7 @@ which is controlled from MainThread based on user input or timer. #include "../PresentData/PresentMonTraceConsumer.hpp" #include "../PresentData/PresentMonTraceSession.hpp" +#include "../IntelPresentMon/CommonUtilities/mc/SwapChainCoreState.h" #include #include @@ -183,6 +184,9 @@ struct FrameMetrics2 { // - pending presents whose metrics cannot be computed until future presents are received, // - exponential averages of key metrics displayed in console output. struct SwapChainData { + // Shared swap chain core state + pmon::util::metrics::SwapChainCoreState> core; + // Pending presents waiting for the next displayed present. std::vector> mPendingPresents; From bb8ed4763d6be6fea948ec06f307f0886f159098 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Mon, 24 Nov 2025 08:50:43 -0800 Subject: [PATCH 005/205] Updated WIP implementation Removed initial template idea of Present type. Added additional tests for helpers --- .../CommonUtilities/CommonUtilities.vcxproj | 1 + .../CommonUtilities.vcxproj.filters | 3 + .../CommonUtilities/mc/MetricsCalculator.cpp | 141 +++- .../CommonUtilities/mc/MetricsCalculator.h | 40 +- .../CommonUtilities/mc/MetricsTypes.cpp | 133 ++-- .../CommonUtilities/mc/MetricsTypes.h | 68 +- .../CommonUtilities/mc/QpcCalculator.h | 8 + .../CommonUtilities/mc/SwapChainCoreState.h | 7 +- .../PresentMonAPI2Tests/SwapChainTests.cpp | 420 +++++------- .../PresentMonMiddleware/ConcreteMiddleware.h | 3 - IntelPresentMon/UnitTests/MetricsCore.cpp | 649 ++++++++++++++---- PresentMon/PresentMon.hpp | 3 - 12 files changed, 1026 insertions(+), 450 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj index dec9e613..409e5603 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj @@ -154,6 +154,7 @@ + diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters index 2d325532..f065ddcd 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters @@ -485,6 +485,9 @@ Source Files + + Source Files + diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index b284ec1c..90b349da 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -1,3 +1,140 @@ -// Copyright (C) 2025 Intel Corporation +// Copyright (C) 2025 Intel Corporation // SPDX-License-Identifier: MIT -#pragma once +#include "MetricsCalculator.h" + +#include "../PresentData/PresentMonTraceConsumer.hpp" +#include "../IntelPresentMon/PresentMonUtils/StreamFormat.h" + +namespace pmon::util::metrics +{ + DisplayIndexing DisplayIndexing::Calculate( + const FrameData& present, + const FrameData* nextDisplayed) + { + DisplayIndexing result{}; + + // Get display count + auto displayCount = present.getDisplayedCount(); // ConsoleAdapter/PresentSnapshot method + + // Check if displayed + bool displayed = present.getFinalState() == PresentResult::Presented && displayCount > 0; + + // hasNextDisplayed + result.hasNextDisplayed = (nextDisplayed != nullptr); + + // Figure out range to process based on three cases: + // Case 1: Not displayed → empty range [0, 0) + // Case 2: Displayed, no next → process [0..N-2], postpone N-1 → range [0, N-1) + // Case 3: Displayed, with next → process postponed [N-1] → range [N-1, N) + + if (!displayed || displayCount == 0) { + // Case 1: Not displayed + result.startIndex = 0; + result.endIndex = 0; // Empty range + } + else if (nextDisplayed == nullptr) { + // Case 2: Postpone last display + result.startIndex = 0; + result.endIndex = displayCount - 1; // One past [N-2] = [N-1] (excludes last!) + } + else { + // Case 3: Process postponed last display + result.startIndex = displayCount - 1; + result.endIndex = displayCount; // One past [N-1] = [N] + } + + // appIndex - find first NotSet or Application frame + // Search from startIndex through ALL displays (not just the processing range) + result.appIndex = std::numeric_limits::max(); + if (displayCount > 0) { + for (size_t i = result.startIndex; i < displayCount; ++i) { + auto frameType = present.getDisplayedFrameType(i); + if (frameType == FrameType::NotSet || frameType == FrameType::Application) { + result.appIndex = i; + break; + } + } + } + else { + result.appIndex = 0; + } + return result; + } + + ComputedMetrics ComputeFrameMetrics( + const QpcCalculator& qpc, + const FrameData& present, + const FrameData* nextDisplayed, + const SwapChainCoreState& chain) + { + + ComputedMetrics result{}; + + return result; + } + + // Helper: Calculate CPU start time + uint64_t CalculateCPUStart( + const SwapChainCoreState& chainState, + const FrameData& present) + { + uint64_t cpuStart = 0; + if (chainState.lastAppPresent.has_value()) { + const auto& lastAppPresent = chainState.lastAppPresent.value(); + if (lastAppPresent.getAppPropagatedPresentStartTime() != 0) { + cpuStart = lastAppPresent.getAppPropagatedPresentStartTime() + + lastAppPresent.getAppPropagatedTimeInPresent(); + } + else { + cpuStart = lastAppPresent.getPresentStartTime() + + lastAppPresent.getTimeInPresent(); + } + } + else { + cpuStart = chainState.lastPresent.has_value() ? + chainState.lastPresent->getPresentStartTime() + chainState.lastPresent->getTimeInPresent() : 0; + } + return cpuStart; + } + + // Helper: Calculate simulation start time (for animation error) + uint64_t CalculateSimStartTime( + const SwapChainCoreState& chainState, + const FrameData& present, + AnimationErrorSource source) + { + uint64_t simStartTime = 0; + if (source == AnimationErrorSource::CpuStart) { + simStartTime = CalculateCPUStart(chainState, present); + } + else if (source == AnimationErrorSource::AppProvider) { + simStartTime = present.getAppSimStartTime(); + if (simStartTime == 0) { + // Fallback to CPU start + simStartTime = CalculateCPUStart(chainState, present); + } + } + else if (source == AnimationErrorSource::PCLatency) { + simStartTime = present.getPclSimStartTime(); + if (simStartTime == 0) { + // Fallback to CPU start + simStartTime = CalculateCPUStart(chainState, present); + } + } + return simStartTime; + } + + // Helper: Calculate animation time + double CalculateAnimationTime( + const QpcCalculator& qpc, + uint64_t firstAppSimStartTime, + uint64_t currentSimTime) + { + double animationTime = 0.0; + uint64_t firstSimStartTime = firstAppSimStartTime != 0 ? firstAppSimStartTime : qpc.GetStartTimestamp(); + if (currentSimTime > firstSimStartTime) { + animationTime = qpc.TimestampDeltaToMilliSeconds(firstSimStartTime, currentSimTime); + } + return animationTime; + } +} \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h index ec364f0b..d74a5c8b 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h @@ -5,6 +5,7 @@ #include #include "QpcCalculator.h" #include "MetricsTypes.h" +#include "SwapChainCoreState.h" namespace pmon::util::metrics { @@ -25,17 +26,6 @@ namespace pmon::util::metrics } stateDeltas; }; - // Context for single display index computation - struct FrameComputationContext { - size_t displayIndex; - size_t appIndex; // Index of application frame in display array - bool displayed; - uint64_t screenTime; - uint64_t nextScreenTime; - uint64_t cpuStart; - uint64_t simStartTime; - }; - // Index calculation helper struct DisplayIndexing { size_t startIndex; // First display index to process @@ -43,38 +33,28 @@ namespace pmon::util::metrics size_t appIndex; // Index of app frame (or SIZE_MAX if none) bool hasNextDisplayed; - // Template version works with both ConsoleAdapter and PresentSnapshot - template static DisplayIndexing Calculate( - const PresentT& present, - const PresentT* nextDisplayed); + const FrameData& present, + const FrameData* nextDisplayed); }; // === Pure Calculation Functions === - // Main computation kernel (pure function) - // Template version - works with different input types - // PresentT: ConsoleAdapter (Strategy A) or PresentSnapshot (Strategy B for Console, always for GUI) - // ChainT: SwapChainData (Console) or fpsSwapChainData (GUI) - both stable references - template ComputedMetrics ComputeFrameMetrics( const QpcCalculator& qpc, - const PresentT& present, - const PresentT* nextDisplayed, - const ChainT& chain, // Direct reference to stable map entry - const FrameComputationContext& context); + const FrameData& present, + const FrameData* nextDisplayed, + const SwapChainCoreState& chain); // Helper: Calculate CPU start time - template uint64_t CalculateCPUStart( - const ChainT& chainState, - const PresentT& present); + const SwapChainCoreState& chainState, + const FrameData& present); // Helper: Calculate simulation start time (for animation error) - template uint64_t CalculateSimStartTime( - const ChainT& chainState, - const PresentT& present, + const SwapChainCoreState& chainState, + const FrameData& present, AnimationErrorSource source); // Helper: Calculate animation time diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp index 199a5f03..c326b91a 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp @@ -7,57 +7,104 @@ namespace pmon::util::metrics { - PresentSnapshot PresentSnapshot::FromCircularBuffer(const PmNsmPresentEvent& p) { - PresentSnapshot snap{}; - - snap.presentStartTime = p.PresentStartTime; - snap.readyTime = p.ReadyTime; - snap.timeInPresent = p.TimeInPresent; - snap.gpuStartTime = p.GPUStartTime; - snap.gpuDuration = p.GPUDuration; - snap.gpuVideoDuration = p.GPUVideoDuration; - - snap.appPropagatedPresentStartTime = p.AppPropagatedPresentStartTime; - snap.appPropagatedTimeInPresent = p.AppPropagatedTimeInPresent; - snap.appPropagatedGPUStartTime = p.AppPropagatedGPUStartTime; - snap.appPropagatedReadyTime = p.AppPropagatedReadyTime; - snap.appPropagatedGPUDuration = p.AppPropagatedGPUDuration; - snap.appPropagatedGPUVideoDuration = p.AppPropagatedGPUVideoDuration; - - snap.appSleepStartTime = p.AppSleepStartTime; - snap.appSleepEndTime = p.AppSleepEndTime; - snap.appSimStartTime = p.AppSimStartTime; - snap.appSleepEndTime = p.AppSleepEndTime; - snap.appRenderSubmitStartTime = p.AppRenderSubmitStartTime; - snap.appRenderSubmitEndTime = p.AppRenderSubmitEndTime; - snap.appPresentStartTime = p.AppPresentStartTime; - snap.appPresentEndTime = p.AppPresentEndTime; - snap.appInputSample = { p.AppInputTime, p.AppInputType }; - - snap.inputTime = p.InputTime; - snap.mouseClickTime = p.MouseClickTime; - - snap.pclSimStartTime = p.PclSimStartTime; - snap.pclInputPingTime = p.PclInputPingTime; - snap.flipDelay = p.FlipDelay; - snap.FlipToken = p.FlipToken; + FrameData FrameData::CopyFrameData(const PmNsmPresentEvent& p) { + FrameData frame{}; + + frame.presentStartTime = p.PresentStartTime; + frame.readyTime = p.ReadyTime; + frame.timeInPresent = p.TimeInPresent; + frame.gpuStartTime = p.GPUStartTime; + frame.gpuDuration = p.GPUDuration; + frame.gpuVideoDuration = p.GPUVideoDuration; + + frame.appPropagatedPresentStartTime = p.AppPropagatedPresentStartTime; + frame.appPropagatedTimeInPresent = p.AppPropagatedTimeInPresent; + frame.appPropagatedGPUStartTime = p.AppPropagatedGPUStartTime; + frame.appPropagatedReadyTime = p.AppPropagatedReadyTime; + frame.appPropagatedGPUDuration = p.AppPropagatedGPUDuration; + frame.appPropagatedGPUVideoDuration = p.AppPropagatedGPUVideoDuration; + + frame.appSleepStartTime = p.AppSleepStartTime; + frame.appSleepEndTime = p.AppSleepEndTime; + frame.appSimStartTime = p.AppSimStartTime; + frame.appSleepEndTime = p.AppSleepEndTime; + frame.appRenderSubmitStartTime = p.AppRenderSubmitStartTime; + frame.appRenderSubmitEndTime = p.AppRenderSubmitEndTime; + frame.appPresentStartTime = p.AppPresentStartTime; + frame.appPresentEndTime = p.AppPresentEndTime; + frame.appInputSample = { p.AppInputTime, p.AppInputType }; + + frame.inputTime = p.InputTime; + frame.mouseClickTime = p.MouseClickTime; + + frame.pclSimStartTime = p.PclSimStartTime; + frame.pclInputPingTime = p.PclInputPingTime; + frame.flipDelay = p.FlipDelay; + frame.FlipToken = p.FlipToken; // Normalize parallel arrays to vector - snap.displayed.reserve(p.DisplayedCount); + frame.displayed.reserve(p.DisplayedCount); for (size_t i = 0; i < p.DisplayedCount; ++i) { - snap.displayed.push_back({ + frame.displayed.push_back({ p.Displayed_FrameType[i], p.Displayed_ScreenTime[i] }); } - snap.finalState = p.FinalState; - snap.swapChainAddress = p.SwapChainAddress; - snap.frameId = p.FrameId; - snap.processId = p.ProcessId; - snap.threadId = p.ThreadId; - snap.appFrameId = p.AppFrameId; + frame.finalState = p.FinalState; + frame.swapChainAddress = p.SwapChainAddress; + frame.frameId = p.FrameId; + frame.processId = p.ProcessId; + frame.threadId = p.ThreadId; + frame.appFrameId = p.AppFrameId; + + return frame; + } + + FrameData FrameData::CopyFrameData(const std::shared_ptr& p) { + FrameData frame{}; + + frame.presentStartTime = p->PresentStartTime; + frame.readyTime = p->ReadyTime; + frame.timeInPresent = p->TimeInPresent; + frame.gpuStartTime = p->GPUStartTime; + frame.gpuDuration = p->GPUDuration; + frame.gpuVideoDuration = p->GPUVideoDuration; + + frame.appPropagatedPresentStartTime = p->AppPropagatedPresentStartTime; + frame.appPropagatedTimeInPresent = p->AppPropagatedTimeInPresent; + frame.appPropagatedGPUStartTime = p->AppPropagatedGPUStartTime; + frame.appPropagatedReadyTime = p->AppPropagatedReadyTime; + frame.appPropagatedGPUDuration = p->AppPropagatedGPUDuration; + frame.appPropagatedGPUVideoDuration = p->AppPropagatedGPUVideoDuration; + + frame.appSleepStartTime = p->AppSleepStartTime; + frame.appSleepEndTime = p->AppSleepEndTime; + frame.appSimStartTime = p->AppSimStartTime; + frame.appSleepEndTime = p->AppSleepEndTime; + frame.appRenderSubmitStartTime = p->AppRenderSubmitStartTime; + frame.appRenderSubmitEndTime = p->AppRenderSubmitEndTime; + frame.appPresentStartTime = p->AppPresentStartTime; + frame.appPresentEndTime = p->AppPresentEndTime; + frame.appInputSample = p->AppInputSample; + + frame.inputTime = p->InputTime; + frame.mouseClickTime = p->MouseClickTime; + + frame.pclSimStartTime = p->PclSimStartTime; + frame.pclInputPingTime = p->PclInputPingTime; + frame.flipDelay = p->FlipDelay; + frame.FlipToken = p->FlipToken; + + frame.displayed = p->Displayed; + + frame.finalState = p->FinalState; + frame.swapChainAddress = p->SwapChainAddress; + frame.frameId = p->FrameId; + frame.processId = p->ProcessId; + frame.threadId = p->ThreadId; + frame.appFrameId = p->AppFrameId; - return snap; + return frame; } } \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h index 0f811a54..358ee05a 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h @@ -11,6 +11,7 @@ enum class FrameType; // From PresentData enum class PresentResult; // From PresentData enum class InputDeviceType; // From PresentData struct PmNsmPresentEvent; // From PresentMonUtils +struct PresentEvent; // From PresentMonTraceConsumer namespace pmon::util::metrics { @@ -22,7 +23,7 @@ namespace pmon::util::metrics { }; // Immutable snapshot - safe for both ownership models - struct PresentSnapshot { + struct FrameData { // Timing Data uint64_t presentStartTime; uint64_t readyTime; @@ -53,12 +54,7 @@ namespace pmon::util::metrics { uint64_t inputTime; // All input devices uint64_t mouseClickTime; // Mouse click specific - // Display Data (normalized from both formats) - struct DisplayEntry { - FrameType frameType; - uint64_t screenTime; - }; - std::vector displayed; + std::vector> displayed; // PC Latency data uint64_t pclSimStartTime; @@ -74,9 +70,63 @@ namespace pmon::util::metrics { uint32_t frameId; uint32_t appFrameId; + // Setters for test setup + void setFinalState(PresentResult state) { finalState = state; } + + // Inline getters - same interface as ConsoleAdapter (zero cost) + uint64_t getPresentStartTime() const { return presentStartTime; } + uint64_t getReadyTime() const { return readyTime; } + uint64_t getTimeInPresent() const { return timeInPresent; } + uint64_t getGPUStartTime() const { return gpuStartTime; } + uint64_t getGPUDuration() const { return gpuDuration; } + uint64_t getGPUVideoDuration() const { return gpuVideoDuration; } + + // Propagated data + uint64_t getAppPropagatedPresentStartTime() const { return appPropagatedPresentStartTime; } + uint64_t getAppPropagatedTimeInPresent() const { return appPropagatedTimeInPresent; } + uint64_t getAppPropagatedGPUStartTime() const { return appPropagatedGPUStartTime; } + uint64_t getAppPropagatedReadyTime() const { return appPropagatedReadyTime; } + uint64_t getAppPropagatedGPUDuration() const { return appPropagatedGPUDuration; } + uint64_t getAppPropagatedGPUVideoDuration() const { return appPropagatedGPUVideoDuration; } + + // Instrumented data + uint64_t getAppSimStartTime() const { return appSimStartTime; } + uint64_t getAppSleepStartTime() const { return appSleepStartTime; } + uint64_t getAppSleepEndTime() const { return appSleepEndTime; } + uint64_t getAppRenderSubmitStartTime() const { return appRenderSubmitStartTime; } + uint64_t getAppRenderSubmitEndTime() const { return appRenderSubmitEndTime; } + uint64_t getAppPresentStartTime() const { return appPresentStartTime; } + uint64_t getAppPresentEndTime() const { return appPresentEndTime; } + std::pair getAppInputSample() const { return appInputSample; } + + // PC Latency + uint64_t getPclSimStartTime() const { return pclSimStartTime; } + uint64_t getPclInputPingTime() const { return pclInputPingTime; } + + // Input tracking + uint64_t getInputTime() const { return inputTime; } + uint64_t getMouseClickTime() const { return mouseClickTime; } + + // Display data - normalized access + size_t getDisplayedCount() const { return displayed.size(); } + FrameType getDisplayedFrameType(size_t idx) const { return displayed[idx].first; } + uint64_t getDisplayedScreenTime(size_t idx) const { return displayed[idx].second; } + + // Vendor-specific + uint64_t getFlipDelay() const { return flipDelay; } + uint32_t getFlipToken() const { return FlipToken; } + + // Metadata + PresentResult getFinalState() const { return finalState; } + uint32_t getProcessId() const { return processId; } + uint32_t getThreadId() const { return threadId; } + uint64_t getSwapChainAddress() const { return swapChainAddress; } + uint32_t getFrameId() const { return frameId; } + uint32_t getAppFrameId() const { return appFrameId; } + // Factory Methods - // Console uses ConsoleAdapter directly, so no conversion needed - static PresentSnapshot FromCircularBuffer(const PmNsmPresentEvent& p); + static FrameData CopyFrameData(const PmNsmPresentEvent& p); + static FrameData CopyFrameData(const std::shared_ptr& p); }; struct FrameMetrics { diff --git a/IntelPresentMon/CommonUtilities/mc/QpcCalculator.h b/IntelPresentMon/CommonUtilities/mc/QpcCalculator.h index 4647d759..66345a03 100644 --- a/IntelPresentMon/CommonUtilities/mc/QpcCalculator.h +++ b/IntelPresentMon/CommonUtilities/mc/QpcCalculator.h @@ -23,6 +23,14 @@ namespace pmon::util::metrics return duration * 1000.0 / qpcFrequency_; } + // Convert time between two QPC timestamps to milliseconds (signed) + double TimestampDeltaToMilliSeconds(uint64_t start, uint64_t end) const + { + return start == 0 || end == 0 || start == end ? 0.0 : + end > start ? TimestampDeltaToMilliSeconds(end - start) + : -TimestampDeltaToMilliSeconds(start - end); + } + // Convert time between two QPC timestamps to milliseconds double TimestampDeltaToUnsignedMilliSeconds(uint64_t start, uint64_t end) const { return (end - start) * 1000.0 / qpcFrequency_; diff --git a/IntelPresentMon/CommonUtilities/mc/SwapChainCoreState.h b/IntelPresentMon/CommonUtilities/mc/SwapChainCoreState.h index 880402c5..1cf2554d 100644 --- a/IntelPresentMon/CommonUtilities/mc/SwapChainCoreState.h +++ b/IntelPresentMon/CommonUtilities/mc/SwapChainCoreState.h @@ -8,21 +8,20 @@ namespace pmon::util::metrics { -template struct SwapChainCoreState { // Pending and Historical Presents // Pending presents waiting for the next displayed present. - std::vector pendingPresents; + std::vector pendingPresents; // The most recent present that has been processed (e.g., output into CSV and/or used for frame // statistics). - std::optional lastPresent; + std::optional lastPresent; // The most recent app present that has been processed (e.g., output into CSV and/or used for frame // statistics). - std::optional lastAppPresent; + std::optional lastAppPresent; // Timing State diff --git a/IntelPresentMon/PresentMonAPI2Tests/SwapChainTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/SwapChainTests.cpp index 4340d796..f58b8749 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/SwapChainTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/SwapChainTests.cpp @@ -9,374 +9,332 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace SwapChainTests { - // Mock types for testing template instantiation - struct MockPresentEvent { - uint64_t timestamp = 0; - int data = 0; - }; - - TEST_CLASS(SwapChainCoreStateTests) + TEST_CLASS(SwapChainStateTests) { public: TEST_METHOD(DefaultConstruction_AllFieldsInitialized) { // Test with a simple type to verify default initialization - pmon::util::metrics::SwapChainCoreState core; + pmon::util::metrics::SwapChainCoreState swapChain; // Verify timing state defaults to 0 - Assert::AreEqual(uint64_t(0), core.lastSimStartTime); - Assert::AreEqual(uint64_t(0), core.lastDisplayedSimStartTime); - Assert::AreEqual(uint64_t(0), core.lastDisplayedScreenTime); - Assert::AreEqual(uint64_t(0), core.lastDisplayedAppScreenTime); - Assert::AreEqual(uint64_t(0), core.firstAppSimStartTime); + Assert::AreEqual(uint64_t(0), swapChain.lastSimStartTime); + Assert::AreEqual(uint64_t(0), swapChain.lastDisplayedSimStartTime); + Assert::AreEqual(uint64_t(0), swapChain.lastDisplayedScreenTime); + Assert::AreEqual(uint64_t(0), swapChain.lastDisplayedAppScreenTime); + Assert::AreEqual(uint64_t(0), swapChain.firstAppSimStartTime); // Verify dropped frame tracking defaults to 0 - Assert::AreEqual(uint64_t(0), core.lastReceivedNotDisplayedAllInputTime); - Assert::AreEqual(uint64_t(0), core.lastReceivedNotDisplayedMouseClickTime); - Assert::AreEqual(uint64_t(0), core.lastReceivedNotDisplayedAppProviderInputTime); - Assert::AreEqual(uint64_t(0), core.lastReceivedNotDisplayedPclSimStart); - Assert::AreEqual(uint64_t(0), core.lastReceivedNotDisplayedPclInputTime); + Assert::AreEqual(uint64_t(0), swapChain.lastReceivedNotDisplayedAllInputTime); + Assert::AreEqual(uint64_t(0), swapChain.lastReceivedNotDisplayedMouseClickTime); + Assert::AreEqual(uint64_t(0), swapChain.lastReceivedNotDisplayedAppProviderInputTime); + Assert::AreEqual(uint64_t(0), swapChain.lastReceivedNotDisplayedPclSimStart); + Assert::AreEqual(uint64_t(0), swapChain.lastReceivedNotDisplayedPclInputTime); // Verify PC Latency accumulation defaults to 0.0 - Assert::AreEqual(0.0, core.accumulatedInput2FrameStartTime); + Assert::AreEqual(0.0, swapChain.accumulatedInput2FrameStartTime); // Verify NVIDIA-specific defaults to 0 - Assert::AreEqual(uint64_t(0), core.lastDisplayedFlipDelay); + Assert::AreEqual(uint64_t(0), swapChain.lastDisplayedFlipDelay); // Verify animation error source defaults to CpuStart - Assert::IsTrue(core.animationErrorSource == pmon::util::metrics::AnimationErrorSource::CpuStart); + Assert::IsTrue(swapChain.animationErrorSource == pmon::util::metrics::AnimationErrorSource::CpuStart); // Verify optional presents are empty - Assert::IsFalse(core.lastPresent.has_value()); - Assert::IsFalse(core.lastAppPresent.has_value()); + Assert::IsFalse(swapChain.lastPresent.has_value()); + Assert::IsFalse(swapChain.lastAppPresent.has_value()); // Verify pending presents vector is empty - Assert::AreEqual(size_t(0), core.pendingPresents.size()); + Assert::AreEqual(size_t(0), swapChain.pendingPresents.size()); } - TEST_METHOD(SharedPtrInstantiation_ConsolePattern) + TEST_METHOD(SwapChainInstantiation_ConsolePattern) { - // Simulates Console usage with shared_ptr - using ConsoleCore = pmon::util::metrics::SwapChainCoreState>; - ConsoleCore core; - + // Test with a simple type to verify default initialization + pmon::util::metrics::SwapChainCoreState swapChain{}; + // Create a mock present event - auto present = std::make_shared(); - present->timestamp = 12345; - present->data = 99; + pmon::util::metrics::FrameData present{}; + present.presentStartTime = 1122; + present.appFrameId = 3344; // Add to pending presents - core.pendingPresents.push_back(present); - Assert::AreEqual(size_t(1), core.pendingPresents.size()); + swapChain.pendingPresents.push_back(present); + Assert::AreEqual(size_t(1), swapChain.pendingPresents.size()); // Set as last present - core.lastPresent = present; - Assert::IsTrue(core.lastPresent.has_value()); - Assert::AreEqual(uint64_t(12345), (*core.lastPresent)->timestamp); - - // Verify reference counting works (same pointer) - Assert::IsTrue(core.pendingPresents[0].get() == core.lastPresent->get()); - } - - TEST_METHOD(ValueTypeInstantiation_MiddlewarePattern) - { - // Simulates Middleware usage with value copies - using MiddlewareCore = pmon::util::metrics::SwapChainCoreState; - MiddlewareCore core; - - // Create a mock present event - MockPresentEvent present{}; - present.timestamp = 54321; - present.data = 42; - - // Add to pending presents (value copy) - core.pendingPresents.push_back(present); - Assert::AreEqual(size_t(1), core.pendingPresents.size()); - - // Set as last present (value copy) - core.lastPresent = present; - Assert::IsTrue(core.lastPresent.has_value()); - Assert::AreEqual(uint64_t(54321), core.lastPresent->timestamp); - - // Modify original - copies should be independent - present.timestamp = 99999; - Assert::AreEqual(uint64_t(54321), core.pendingPresents[0].timestamp); - Assert::AreEqual(uint64_t(54321), core.lastPresent->timestamp); + swapChain.lastPresent = present; + Assert::IsTrue(swapChain.lastPresent.has_value()); + Assert::AreEqual(uint64_t(1122), swapChain.lastPresent.value().presentStartTime); + Assert::AreEqual(uint32_t(3344), swapChain.lastPresent.value().appFrameId); } TEST_METHOD(PendingPresents_VectorOperations) { - using TestCore = pmon::util::metrics::SwapChainCoreState; - TestCore core; - + pmon::util::metrics::SwapChainCoreState swapChain{}; + + // Create some mock presents + pmon::util::metrics::FrameData present[3]{}; + present[0].appFrameId = 1; + present[1].appFrameId = 2; + present[2].appFrameId = 3; + // Add multiple items - core.pendingPresents.push_back(1); - core.pendingPresents.push_back(2); - core.pendingPresents.push_back(3); + swapChain.pendingPresents.push_back(present[0]); + swapChain.pendingPresents.push_back(present[1]); + swapChain.pendingPresents.push_back(present[2]); - Assert::AreEqual(size_t(3), core.pendingPresents.size()); - Assert::AreEqual(1, core.pendingPresents[0]); - Assert::AreEqual(2, core.pendingPresents[1]); - Assert::AreEqual(3, core.pendingPresents[2]); + Assert::AreEqual(size_t(3), swapChain.pendingPresents.size()); + Assert::AreEqual(uint32_t(1), swapChain.pendingPresents[0].appFrameId); + Assert::AreEqual(uint32_t(2), swapChain.pendingPresents[1].appFrameId); + Assert::AreEqual(uint32_t(3), swapChain.pendingPresents[2].appFrameId); // Clear pending presents - core.pendingPresents.clear(); - Assert::AreEqual(size_t(0), core.pendingPresents.size()); + swapChain.pendingPresents.clear(); + Assert::AreEqual(size_t(0), swapChain.pendingPresents.size()); } TEST_METHOD(OptionalPresents_HasValue) { - using TestCore = pmon::util::metrics::SwapChainCoreState; - TestCore core; + pmon::util::metrics::SwapChainCoreState swapChain{}; // Initially empty - Assert::IsFalse(core.lastPresent.has_value()); - Assert::IsFalse(core.lastAppPresent.has_value()); - + Assert::IsFalse(swapChain.lastPresent.has_value()); + Assert::IsFalse(swapChain.lastAppPresent.has_value()); + + pmon::util::metrics::FrameData present[2]{}; + present[0].appFrameId = 1; + present[1].appFrameId = 2; + // Set lastPresent - core.lastPresent = 42; - Assert::IsTrue(core.lastPresent.has_value()); - Assert::AreEqual(42, *core.lastPresent); - Assert::IsFalse(core.lastAppPresent.has_value()); + swapChain.lastPresent = present[0]; + Assert::IsTrue(swapChain.lastPresent.has_value()); + Assert::AreEqual((uint32_t)1, swapChain.lastPresent.value().appFrameId); + Assert::IsFalse(swapChain.lastAppPresent.has_value()); // Set lastAppPresent - core.lastAppPresent = 99; - Assert::IsTrue(core.lastPresent.has_value()); - Assert::IsTrue(core.lastAppPresent.has_value()); - Assert::AreEqual(42, *core.lastPresent); - Assert::AreEqual(99, *core.lastAppPresent); + swapChain.lastAppPresent = present[1]; + Assert::IsTrue(swapChain.lastPresent.has_value()); + Assert::IsTrue(swapChain.lastAppPresent.has_value()); + Assert::AreEqual((uint32_t)1, swapChain.lastPresent.value().appFrameId); + Assert::AreEqual((uint32_t)2, swapChain.lastAppPresent.value().appFrameId); // Reset lastPresent - core.lastPresent.reset(); - Assert::IsFalse(core.lastPresent.has_value()); - Assert::IsTrue(core.lastAppPresent.has_value()); + swapChain.lastPresent.reset(); + Assert::IsFalse(swapChain.lastPresent.has_value()); + Assert::IsTrue(swapChain.lastAppPresent.has_value()); } TEST_METHOD(TimingState_AssignmentAndRetrieval) { - using TestCore = pmon::util::metrics::SwapChainCoreState; - TestCore core; - + pmon::util::metrics::SwapChainCoreState swapChain{}; + // Set timing values - core.lastSimStartTime = 1000; - core.lastDisplayedSimStartTime = 2000; - core.lastDisplayedScreenTime = 3000; - core.lastDisplayedAppScreenTime = 4000; - core.firstAppSimStartTime = 5000; + swapChain.lastSimStartTime = 1000; + swapChain.lastDisplayedSimStartTime = 2000; + swapChain.lastDisplayedScreenTime = 3000; + swapChain.lastDisplayedAppScreenTime = 4000; + swapChain.firstAppSimStartTime = 5000; // Verify retrieval - Assert::AreEqual(uint64_t(1000), core.lastSimStartTime); - Assert::AreEqual(uint64_t(2000), core.lastDisplayedSimStartTime); - Assert::AreEqual(uint64_t(3000), core.lastDisplayedScreenTime); - Assert::AreEqual(uint64_t(4000), core.lastDisplayedAppScreenTime); - Assert::AreEqual(uint64_t(5000), core.firstAppSimStartTime); + Assert::AreEqual(uint64_t(1000), swapChain.lastSimStartTime); + Assert::AreEqual(uint64_t(2000), swapChain.lastDisplayedSimStartTime); + Assert::AreEqual(uint64_t(3000), swapChain.lastDisplayedScreenTime); + Assert::AreEqual(uint64_t(4000), swapChain.lastDisplayedAppScreenTime); + Assert::AreEqual(uint64_t(5000), swapChain.firstAppSimStartTime); } TEST_METHOD(DroppedFrameTracking_AssignmentAndRetrieval) { - using TestCore = pmon::util::metrics::SwapChainCoreState; - TestCore core; + pmon::util::metrics::SwapChainCoreState swapChain{}; // Set dropped frame tracking values - core.lastReceivedNotDisplayedAllInputTime = 1111; - core.lastReceivedNotDisplayedMouseClickTime = 2222; - core.lastReceivedNotDisplayedAppProviderInputTime = 3333; - core.lastReceivedNotDisplayedPclSimStart = 4444; - core.lastReceivedNotDisplayedPclInputTime = 5555; + swapChain.lastReceivedNotDisplayedAllInputTime = 1111; + swapChain.lastReceivedNotDisplayedMouseClickTime = 2222; + swapChain.lastReceivedNotDisplayedAppProviderInputTime = 3333; + swapChain.lastReceivedNotDisplayedPclSimStart = 4444; + swapChain.lastReceivedNotDisplayedPclInputTime = 5555; // Verify retrieval - Assert::AreEqual(uint64_t(1111), core.lastReceivedNotDisplayedAllInputTime); - Assert::AreEqual(uint64_t(2222), core.lastReceivedNotDisplayedMouseClickTime); - Assert::AreEqual(uint64_t(3333), core.lastReceivedNotDisplayedAppProviderInputTime); - Assert::AreEqual(uint64_t(4444), core.lastReceivedNotDisplayedPclSimStart); - Assert::AreEqual(uint64_t(5555), core.lastReceivedNotDisplayedPclInputTime); + Assert::AreEqual(uint64_t(1111), swapChain.lastReceivedNotDisplayedAllInputTime); + Assert::AreEqual(uint64_t(2222), swapChain.lastReceivedNotDisplayedMouseClickTime); + Assert::AreEqual(uint64_t(3333), swapChain.lastReceivedNotDisplayedAppProviderInputTime); + Assert::AreEqual(uint64_t(4444), swapChain.lastReceivedNotDisplayedPclSimStart); + Assert::AreEqual(uint64_t(5555), swapChain.lastReceivedNotDisplayedPclInputTime); } TEST_METHOD(PCLatencyAccumulation_DoubleType) { - using TestCore = pmon::util::metrics::SwapChainCoreState; - TestCore core; + pmon::util::metrics::SwapChainCoreState swapChain{}; // Initially 0.0 - Assert::AreEqual(0.0, core.accumulatedInput2FrameStartTime); + Assert::AreEqual(0.0, swapChain.accumulatedInput2FrameStartTime); // Set value - core.accumulatedInput2FrameStartTime = 16.7; - Assert::AreEqual(16.7, core.accumulatedInput2FrameStartTime, 0.001); + swapChain.accumulatedInput2FrameStartTime = 16.7; + Assert::AreEqual(16.7, swapChain.accumulatedInput2FrameStartTime, 0.001); // Accumulate more - core.accumulatedInput2FrameStartTime += 8.3; - Assert::AreEqual(25.0, core.accumulatedInput2FrameStartTime, 0.001); + swapChain.accumulatedInput2FrameStartTime += 8.3; + Assert::AreEqual(25.0, swapChain.accumulatedInput2FrameStartTime, 0.001); // Reset - core.accumulatedInput2FrameStartTime = 0.0; - Assert::AreEqual(0.0, core.accumulatedInput2FrameStartTime); + swapChain.accumulatedInput2FrameStartTime = 0.0; + Assert::AreEqual(0.0, swapChain.accumulatedInput2FrameStartTime); } TEST_METHOD(AnimationErrorSource_EnumAssignment) { - using TestCore = pmon::util::metrics::SwapChainCoreState; - TestCore core; + pmon::util::metrics::SwapChainCoreState swapChain{}; // Default is CpuStart - Assert::IsTrue(core.animationErrorSource == pmon::util::metrics::AnimationErrorSource::CpuStart); + Assert::IsTrue(swapChain.animationErrorSource == pmon::util::metrics::AnimationErrorSource::CpuStart); // Change to AppProvider - core.animationErrorSource = pmon::util::metrics::AnimationErrorSource::AppProvider; - Assert::IsTrue(core.animationErrorSource == pmon::util::metrics::AnimationErrorSource::AppProvider); - Assert::IsFalse(core.animationErrorSource == pmon::util::metrics::AnimationErrorSource::CpuStart); + swapChain.animationErrorSource = pmon::util::metrics::AnimationErrorSource::AppProvider; + Assert::IsTrue(swapChain.animationErrorSource == pmon::util::metrics::AnimationErrorSource::AppProvider); + Assert::IsFalse(swapChain.animationErrorSource == pmon::util::metrics::AnimationErrorSource::CpuStart); // Change to PCLatency - core.animationErrorSource = pmon::util::metrics::AnimationErrorSource::PCLatency; - Assert::IsTrue(core.animationErrorSource == pmon::util::metrics::AnimationErrorSource::PCLatency); - Assert::IsFalse(core.animationErrorSource == pmon::util::metrics::AnimationErrorSource::AppProvider); + swapChain.animationErrorSource = pmon::util::metrics::AnimationErrorSource::PCLatency; + Assert::IsTrue(swapChain.animationErrorSource == pmon::util::metrics::AnimationErrorSource::PCLatency); + Assert::IsFalse(swapChain.animationErrorSource == pmon::util::metrics::AnimationErrorSource::AppProvider); } TEST_METHOD(NvidiaFlipDelay_AssignmentAndRetrieval) { - using TestCore = pmon::util::metrics::SwapChainCoreState; - TestCore core; + pmon::util::metrics::SwapChainCoreState swapChain{}; // Default is 0 - Assert::AreEqual(uint64_t(0), core.lastDisplayedFlipDelay); + Assert::AreEqual(uint64_t(0), swapChain.lastDisplayedFlipDelay); // Set value - core.lastDisplayedFlipDelay = 8888; - Assert::AreEqual(uint64_t(8888), core.lastDisplayedFlipDelay); + swapChain.lastDisplayedFlipDelay = 8888; + Assert::AreEqual(uint64_t(8888), swapChain.lastDisplayedFlipDelay); } - TEST_METHOD(ComplexType_SharedPtrWithRealData) + TEST_METHOD(MultipleFrameDataFields) { - // More realistic scenario with complex type - struct ComplexEvent { - uint64_t qpcTime; - std::vector displayData; - double metric; - }; - - using ComplexCore = pmon::util::metrics::SwapChainCoreState>; - ComplexCore core; - - // Create event with data - auto event = std::make_shared(); - event->qpcTime = 123456789; - event->displayData = {1, 2, 3, 4, 5}; - event->metric = 16.7; - + pmon::util::metrics::SwapChainCoreState swapChain{}; + pmon::util::metrics::FrameData present; + + present.appFrameId = 7777; + present.presentStartTime = 5555; + present.timeInPresent = 2000; + // Store in core state - core.pendingPresents.push_back(event); - core.lastPresent = event; - core.lastSimStartTime = event->qpcTime; + swapChain.pendingPresents.push_back(present); + swapChain.lastPresent = present; // Verify access - Assert::IsTrue(core.lastPresent.has_value()); - Assert::AreEqual(uint64_t(123456789), (*core.lastPresent)->qpcTime); - Assert::AreEqual(size_t(5), (*core.lastPresent)->displayData.size()); - Assert::AreEqual(16.7, (*core.lastPresent)->metric, 0.001); - Assert::AreEqual(uint64_t(123456789), core.lastSimStartTime); + Assert::IsTrue(swapChain.lastPresent.has_value()); + Assert::AreEqual(uint32_t(7777), swapChain.lastPresent.value().appFrameId); + Assert::AreEqual(uint64_t(5555), swapChain.lastPresent.value().presentStartTime); + Assert::AreEqual(uint64_t(2000), swapChain.lastPresent.value().timeInPresent); } TEST_METHOD(StateTransitions_SimulateFrameProcessing) { - // Simulate a realistic frame processing scenario - using TestCore = pmon::util::metrics::SwapChainCoreState; - TestCore core; + pmon::util::metrics::SwapChainCoreState swapChain{}; // Frame 1: First frame received - int frame1 = 1000; - core.pendingPresents.push_back(frame1); - core.lastPresent = frame1; - core.lastSimStartTime = 1000; + pmon::util::metrics::FrameData presentOne; + + presentOne.presentStartTime = 1000; + presentOne.appFrameId = 1; + swapChain.pendingPresents.push_back(presentOne); + swapChain.lastPresent = presentOne; + swapChain.lastSimStartTime = 1000; - Assert::AreEqual(size_t(1), core.pendingPresents.size()); - Assert::AreEqual(1000, *core.lastPresent); + Assert::AreEqual(size_t(1), swapChain.pendingPresents.size()); + Assert::AreEqual(true, swapChain.lastPresent.has_value()); - // Frame 2: App frame received + // Frame 2: Next frame received + pmon::util::metrics::FrameData presentTwo; + int frame2 = 2000; - core.pendingPresents.push_back(frame2); - core.lastPresent = frame2; - core.lastAppPresent = frame2; - core.lastSimStartTime = 2000; - core.firstAppSimStartTime = 2000; - - Assert::AreEqual(size_t(2), core.pendingPresents.size()); - Assert::AreEqual(2000, *core.lastAppPresent); - + swapChain.pendingPresents.push_back(presentTwo); + swapChain.lastPresent = presentTwo; + swapChain.lastSimStartTime = 2000; + Assert::AreEqual(size_t(2), swapChain.pendingPresents.size()); + // Frame 2 displayed: Update display state - core.lastDisplayedSimStartTime = 2000; - core.lastDisplayedScreenTime = 2016; // +16ms latency - core.lastDisplayedAppScreenTime = 2016; + swapChain.lastDisplayedSimStartTime = 2000; + swapChain.lastDisplayedScreenTime = 2016; // +16ms latency + swapChain.lastDisplayedAppScreenTime = 2016; // Clear pending presents (they've been processed) - core.pendingPresents.clear(); + swapChain.pendingPresents.clear(); - Assert::AreEqual(size_t(0), core.pendingPresents.size()); - Assert::AreEqual(uint64_t(2016), core.lastDisplayedScreenTime); + Assert::AreEqual(size_t(0), swapChain.pendingPresents.size()); + Assert::AreEqual(uint64_t(2016), swapChain.lastDisplayedScreenTime); - // Frame 3: Dropped frame (not displayed) - int frame3 = 3000; - core.pendingPresents.push_back(frame3); - core.lastPresent = frame3; - core.lastReceivedNotDisplayedAllInputTime = 2990; // Had input + // Frame 3 + pmon::util::metrics::FrameData presentThree; + swapChain.pendingPresents.push_back(presentThree); + swapChain.lastPresent = presentThree; + swapChain.lastReceivedNotDisplayedAllInputTime = 2990; - Assert::AreEqual(size_t(1), core.pendingPresents.size()); - Assert::AreEqual(uint64_t(2990), core.lastReceivedNotDisplayedAllInputTime); + Assert::AreEqual(size_t(1), swapChain.pendingPresents.size()); + Assert::AreEqual(uint64_t(2990), swapChain.lastReceivedNotDisplayedAllInputTime); } TEST_METHOD(CopySemantics_Independent) { // Verify that copies are independent (important for value types) - using TestCore = pmon::util::metrics::SwapChainCoreState; - TestCore core1; + pmon::util::metrics::SwapChainCoreState swapChainOne{}; + pmon::util::metrics::FrameData presents[3]; // Set state in core1 - core1.lastSimStartTime = 1234; - core1.pendingPresents.push_back(1); - core1.pendingPresents.push_back(2); - core1.lastPresent = 42; - core1.accumulatedInput2FrameStartTime = 16.7; + swapChainOne.lastSimStartTime = 1234; + swapChainOne.pendingPresents.push_back(presents[0]); + swapChainOne.pendingPresents.push_back(presents[1]); + swapChainOne.lastPresent = presents[1]; + swapChainOne.accumulatedInput2FrameStartTime = 16.7; - // Copy to core2 - TestCore core2 = core1; + pmon::util::metrics::SwapChainCoreState swapChainTwo{}; + // SwapChainOne to SwapChainTwo + swapChainTwo = swapChainOne; + // Verify core2 has same values - Assert::AreEqual(uint64_t(1234), core2.lastSimStartTime); - Assert::AreEqual(size_t(2), core2.pendingPresents.size()); - Assert::AreEqual(42, *core2.lastPresent); - Assert::AreEqual(16.7, core2.accumulatedInput2FrameStartTime, 0.001); + Assert::AreEqual(uint64_t(1234), swapChainTwo.lastSimStartTime); + Assert::AreEqual(size_t(2), swapChainTwo.pendingPresents.size()); + //Assert::AreEqual(presents[1], *swapChainTwo.lastPresent); + Assert::AreEqual(16.7, swapChainTwo.accumulatedInput2FrameStartTime, 0.001); - // Modify core2 - core2.lastSimStartTime = 5678; - core2.pendingPresents.push_back(3); - core2.lastPresent = 99; + // Modify SwapChainTwo + swapChainTwo.lastSimStartTime = 5678; + swapChainTwo.pendingPresents.push_back(presents[2]); + swapChainTwo.lastPresent = presents[2]; // Verify core1 is unchanged - Assert::AreEqual(uint64_t(1234), core1.lastSimStartTime); - Assert::AreEqual(size_t(2), core1.pendingPresents.size()); - Assert::AreEqual(42, *core1.lastPresent); + Assert::AreEqual(uint64_t(1234), swapChainOne.lastSimStartTime); + Assert::AreEqual(size_t(2), swapChainOne.pendingPresents.size()); + //Assert::AreEqual(presents[1], *swapChainOne.lastPresent); } TEST_METHOD(MoveSemantics_Efficient) { - // Verify move semantics work correctly (important for efficiency) - using TestCore = pmon::util::metrics::SwapChainCoreState; - TestCore core1; - + // Verify that copies are independent (important for value types) + pmon::util::metrics::SwapChainCoreState swapChainOne{}; + + pmon::util::metrics::FrameData presents[100]; // Set state with large vector for (int i = 0; i < 100; ++i) { - core1.pendingPresents.push_back(i); + swapChainOne.pendingPresents.push_back(presents[i]); } - core1.lastSimStartTime = 9999; - core1.lastPresent = 42; + swapChainOne.lastSimStartTime = 9999; + swapChainOne.lastPresent = presents[99]; // Move to core2 - TestCore core2 = std::move(core1); + pmon::util::metrics::SwapChainCoreState swapChainTwo = std::move(swapChainOne); // Verify core2 has the data - Assert::AreEqual(size_t(100), core2.pendingPresents.size()); - Assert::AreEqual(uint64_t(9999), core2.lastSimStartTime); - Assert::AreEqual(42, *core2.lastPresent); + Assert::AreEqual(size_t(100), swapChainTwo.pendingPresents.size()); + Assert::AreEqual(uint64_t(9999), swapChainTwo.lastSimStartTime); + //Assert::AreEqual(presents[99], *swapChainTwo.lastPresent); // Note: core1 is in moved-from state, don't test its values } diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h index aa5c13c5..054be562 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h @@ -78,9 +78,6 @@ namespace pmon::mid // - pending presents whose metrics cannot be computed until future presents are received, // - exponential averages of key metrics displayed in console output. struct fpsSwapChainData { - // Shared swap chain core state - pmon::util::metrics::SwapChainCoreState> core; - // Pending presents waiting for the next displayed present. std::vector mPendingPresents; diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index c3a1a659..e92a806c 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -24,7 +24,7 @@ namespace MetricsCoreTests { // 10MHz QPC frequency (10,000,000 ticks per second) QpcCalculator qpc(10'000'000, 0); - + // 10,000 ticks = 1 millisecond at 10MHz double result = qpc.TimestampDeltaToMilliSeconds(10'000); Assert::AreEqual(1.0, result, 0.0001); @@ -40,7 +40,7 @@ namespace MetricsCoreTests TEST_METHOD(TimestampDeltaToMilliSeconds_LargeDuration) { QpcCalculator qpc(10'000'000, 0); - + // 100,000,000 ticks = 10,000 milliseconds at 10MHz double result = qpc.TimestampDeltaToMilliSeconds(100'000'000); Assert::AreEqual(10'000.0, result, 0.01); @@ -49,7 +49,7 @@ namespace MetricsCoreTests TEST_METHOD(TimestampDeltaToUnsignedMilliSeconds_ForwardTime) { QpcCalculator qpc(10'000'000, 0); - + // Start at 1000, end at 11000 (10,000 ticks = 1ms) double result = qpc.TimestampDeltaToUnsignedMilliSeconds(1000, 11'000); Assert::AreEqual(1.0, result, 0.0001); @@ -66,7 +66,7 @@ namespace MetricsCoreTests { // Typical QPC frequency: ~10MHz QpcCalculator qpc(10'000'000, 0); - + // 16.666ms frame time at 60fps uint64_t frameTimeTicks = 166'660; double result = qpc.TimestampDeltaToUnsignedMilliSeconds(0, frameTimeTicks); @@ -77,7 +77,7 @@ namespace MetricsCoreTests { uint64_t startTime = 123'456'789; QpcCalculator qpc(10'000'000, startTime); - + Assert::AreEqual(startTime, qpc.GetStartTimestamp()); } }; @@ -96,92 +96,92 @@ namespace MetricsCoreTests nsmEvent.GPUDuration = 800; nsmEvent.GPUVideoDuration = 300; - auto snap = PresentSnapshot::FromCircularBuffer(nsmEvent); + auto frame = FrameData::CopyFrameData(nsmEvent); - Assert::AreEqual(1000ull, snap.presentStartTime); - Assert::AreEqual(2000ull, snap.readyTime); - Assert::AreEqual(500ull, snap.timeInPresent); - Assert::AreEqual(1200ull, snap.gpuStartTime); - Assert::AreEqual(800ull, snap.gpuDuration); - Assert::AreEqual(300ull, snap.gpuVideoDuration); + Assert::AreEqual(1000ull, frame.presentStartTime); + Assert::AreEqual(2000ull, frame.readyTime); + Assert::AreEqual(500ull, frame.timeInPresent); + Assert::AreEqual(1200ull, frame.gpuStartTime); + Assert::AreEqual(800ull, frame.gpuDuration); + Assert::AreEqual(300ull, frame.gpuVideoDuration); } TEST_METHOD(FromCircularBuffer_CopiesAppPropagatedData) { PmNsmPresentEvent nsmEvent{}; - nsmEvent.AppPropagatedPresentStartTime = 5000; - nsmEvent.AppPropagatedTimeInPresent = 600; - nsmEvent.AppPropagatedGPUStartTime = 5200; - nsmEvent.AppPropagatedReadyTime = 6000; - nsmEvent.AppPropagatedGPUDuration = 800; - nsmEvent.AppPropagatedGPUVideoDuration = 200; + nsmEvent.AppPropagatedPresentStartTime =5000; + nsmEvent.AppPropagatedTimeInPresent =600; + nsmEvent.AppPropagatedGPUStartTime =5200; + nsmEvent.AppPropagatedReadyTime =6000; + nsmEvent.AppPropagatedGPUDuration =800; + nsmEvent.AppPropagatedGPUVideoDuration =200; - auto snap = PresentSnapshot::FromCircularBuffer(nsmEvent); + auto frame = FrameData::CopyFrameData(nsmEvent); - Assert::AreEqual(5000ull, snap.appPropagatedPresentStartTime); - Assert::AreEqual(600ull, snap.appPropagatedTimeInPresent); - Assert::AreEqual(5200ull, snap.appPropagatedGPUStartTime); - Assert::AreEqual(6000ull, snap.appPropagatedReadyTime); - Assert::AreEqual(800ull, snap.appPropagatedGPUDuration); - Assert::AreEqual(200ull, snap.appPropagatedGPUVideoDuration); + Assert::AreEqual(5000ull, frame.appPropagatedPresentStartTime); + Assert::AreEqual(600ull, frame.appPropagatedTimeInPresent); + Assert::AreEqual(5200ull, frame.appPropagatedGPUStartTime); + Assert::AreEqual(6000ull, frame.appPropagatedReadyTime); + Assert::AreEqual(800ull, frame.appPropagatedGPUDuration); + Assert::AreEqual(200ull, frame.appPropagatedGPUVideoDuration); } TEST_METHOD(FromCircularBuffer_CopiesInstrumentedTimestamps) { PmNsmPresentEvent nsmEvent{}; - nsmEvent.AppSimStartTime = 100; - nsmEvent.AppSleepStartTime = 200; - nsmEvent.AppSleepEndTime = 250; - nsmEvent.AppRenderSubmitStartTime = 300; + nsmEvent.AppSimStartTime =100; + nsmEvent.AppSleepStartTime =200; + nsmEvent.AppSleepEndTime =250; + nsmEvent.AppRenderSubmitStartTime =300; - auto snap = PresentSnapshot::FromCircularBuffer(nsmEvent); + auto frame = FrameData::CopyFrameData(nsmEvent); - Assert::AreEqual(100ull, snap.appSimStartTime); - Assert::AreEqual(200ull, snap.appSleepStartTime); - Assert::AreEqual(250ull, snap.appSleepEndTime); - Assert::AreEqual(300ull, snap.appRenderSubmitStartTime); + Assert::AreEqual(100ull, frame.appSimStartTime); + Assert::AreEqual(200ull, frame.appSleepStartTime); + Assert::AreEqual(250ull, frame.appSleepEndTime); + Assert::AreEqual(300ull, frame.appRenderSubmitStartTime); } TEST_METHOD(FromCircularBuffer_CopiesPcLatencyData) { PmNsmPresentEvent nsmEvent{}; - nsmEvent.PclSimStartTime = 7000; - nsmEvent.PclInputPingTime = 6500; + nsmEvent.PclSimStartTime =7000; + nsmEvent.PclInputPingTime =6500; - auto snap = PresentSnapshot::FromCircularBuffer(nsmEvent); + auto frame = FrameData::CopyFrameData(nsmEvent); - Assert::AreEqual(7000ull, snap.pclSimStartTime); - Assert::AreEqual(6500ull, snap.pclInputPingTime); + Assert::AreEqual(7000ull, frame.pclSimStartTime); + Assert::AreEqual(6500ull, frame.pclInputPingTime); } TEST_METHOD(FromCircularBuffer_CopiesInputTimes) { PmNsmPresentEvent nsmEvent{}; - nsmEvent.InputTime = 8000; - nsmEvent.MouseClickTime = 8050; + nsmEvent.InputTime =8000; + nsmEvent.MouseClickTime =8050; - auto snap = PresentSnapshot::FromCircularBuffer(nsmEvent); + auto frame = FrameData::CopyFrameData(nsmEvent); - Assert::AreEqual(8000ull, snap.inputTime); - Assert::AreEqual(8050ull, snap.mouseClickTime); + Assert::AreEqual(8000ull, frame.inputTime); + Assert::AreEqual(8050ull, frame.mouseClickTime); } TEST_METHOD(FromCircularBuffer_NormalizesDisplayArrays) { PmNsmPresentEvent nsmEvent{}; - nsmEvent.DisplayedCount = 2; + nsmEvent.DisplayedCount =2; nsmEvent.Displayed_FrameType[0] = FrameType::Application; - nsmEvent.Displayed_ScreenTime[0] = 9000; + nsmEvent.Displayed_ScreenTime[0] =9000; nsmEvent.Displayed_FrameType[1] = FrameType::Repeated; - nsmEvent.Displayed_ScreenTime[1] = 9500; + nsmEvent.Displayed_ScreenTime[1] =9500; - auto snap = PresentSnapshot::FromCircularBuffer(nsmEvent); + auto frame = FrameData::CopyFrameData(nsmEvent); - Assert::AreEqual(size_t(2), snap.displayed.size()); - Assert::IsTrue(snap.displayed[0].frameType == FrameType::Application); - Assert::AreEqual(9000ull, snap.displayed[0].screenTime); - Assert::IsTrue(snap.displayed[1].frameType == FrameType::Repeated); - Assert::AreEqual(9500ull, snap.displayed[1].screenTime); + Assert::AreEqual(size_t(2), frame.displayed.size()); + Assert::IsTrue(frame.displayed[0].first == FrameType::Application); + Assert::AreEqual(9000ull, frame.displayed[0].second); + Assert::IsTrue(frame.displayed[1].first == FrameType::Repeated); + Assert::AreEqual(9500ull, frame.displayed[1].second); } TEST_METHOD(FromCircularBuffer_HandlesEmptyDisplayArray) @@ -189,25 +189,25 @@ namespace MetricsCoreTests PmNsmPresentEvent nsmEvent{}; nsmEvent.DisplayedCount = 0; - auto snap = PresentSnapshot::FromCircularBuffer(nsmEvent); + auto frame = FrameData::CopyFrameData(nsmEvent); - Assert::AreEqual(size_t(0), snap.displayed.size()); + Assert::AreEqual(size_t(0), frame.displayed.size()); } TEST_METHOD(FromCircularBuffer_CopiesMetadata) { PmNsmPresentEvent nsmEvent{}; - nsmEvent.ProcessId = 1234; - nsmEvent.ThreadId = 5678; - nsmEvent.SwapChainAddress = 0xDEADBEEF; - nsmEvent.FrameId = 42; + nsmEvent.ProcessId =1234; + nsmEvent.ThreadId =5678; + nsmEvent.SwapChainAddress =0xDEADBEEF; + nsmEvent.FrameId =42; - auto snap = PresentSnapshot::FromCircularBuffer(nsmEvent); + auto frame = FrameData::CopyFrameData(nsmEvent); - Assert::AreEqual(uint32_t(1234), snap.processId); - Assert::AreEqual(uint32_t(5678), snap.threadId); - Assert::AreEqual(uint64_t(0xDEADBEEF), snap.swapChainAddress); - Assert::AreEqual(uint32_t(42), snap.frameId); + Assert::AreEqual(uint32_t(1234), frame.processId); + Assert::AreEqual(uint32_t(5678), frame.threadId); + Assert::AreEqual(uint64_t(0xDEADBEEF), frame.swapChainAddress); + Assert::AreEqual(uint32_t(42), frame.frameId); } }; @@ -362,122 +362,521 @@ namespace MetricsCoreTests TEST_METHOD(DefaultConstruction_InitializesTimestampsToZero) { - SwapChainCoreState> state; + SwapChainCoreState swapChain; - Assert::AreEqual(0ull, state.lastSimStartTime); - Assert::AreEqual(0ull, state.lastDisplayedSimStartTime); - Assert::AreEqual(0ull, state.lastDisplayedScreenTime); - Assert::AreEqual(0ull, state.firstAppSimStartTime); + Assert::AreEqual(0ull, swapChain.lastSimStartTime); + Assert::AreEqual(0ull, swapChain.lastDisplayedSimStartTime); + Assert::AreEqual(0ull, swapChain.lastDisplayedScreenTime); + Assert::AreEqual(0ull, swapChain.firstAppSimStartTime); } TEST_METHOD(DefaultConstruction_InitializesOptionalPresentToEmpty) { - SwapChainCoreState> state; + SwapChainCoreState swapChain; - Assert::IsFalse(state.lastPresent.has_value()); - Assert::IsFalse(state.lastAppPresent.has_value()); + Assert::IsFalse(swapChain.lastPresent.has_value()); + Assert::IsFalse(swapChain.lastAppPresent.has_value()); } - TEST_METHOD(PendingPresents_CanStoreMultipleSharedPtrs) + TEST_METHOD(PendingPresents_CanStoreMultiplePendingPresents) { - SwapChainCoreState> state; + SwapChainCoreState swapChain; - auto p1 = std::make_shared(); - auto p2 = std::make_shared(); - auto p3 = std::make_shared(); + FrameData p1{}; + FrameData p2{}; + FrameData p3{}; - state.pendingPresents.push_back(p1); - state.pendingPresents.push_back(p2); - state.pendingPresents.push_back(p3); + swapChain.pendingPresents.push_back(p1); + swapChain.pendingPresents.push_back(p2); + swapChain.pendingPresents.push_back(p3); - Assert::AreEqual(size_t(3), state.pendingPresents.size()); + Assert::AreEqual(size_t(3), swapChain.pendingPresents.size()); } TEST_METHOD(LastPresent_CanBeAssigned) { - SwapChainCoreState> state; - auto event = std::make_shared(); - event->presentStartTime = 12345; + SwapChainCoreState swapChain; + FrameData p1{}; + p1.presentStartTime = 12345; + swapChain.lastPresent = p1; - state.lastPresent = event; - - Assert::IsTrue(state.lastPresent.has_value()); - Assert::AreEqual(12345ull, (*state.lastPresent)->presentStartTime); + Assert::IsTrue(swapChain.lastPresent.has_value()); + Assert::AreEqual(12345ull, swapChain.lastPresent.value().presentStartTime); } TEST_METHOD(DroppedInputTracking_InitializesToZero) { - SwapChainCoreState> state; + SwapChainCoreState swapChain; - Assert::AreEqual(0ull, state.lastReceivedNotDisplayedAllInputTime); - Assert::AreEqual(0ull, state.lastReceivedNotDisplayedMouseClickTime); - Assert::AreEqual(0ull, state.lastReceivedNotDisplayedAppProviderInputTime); + Assert::AreEqual(0ull, swapChain.lastReceivedNotDisplayedAllInputTime); + Assert::AreEqual(0ull, swapChain.lastReceivedNotDisplayedMouseClickTime); + Assert::AreEqual(0ull, swapChain.lastReceivedNotDisplayedAppProviderInputTime); } TEST_METHOD(DroppedInputTracking_CanBeUpdated) { - SwapChainCoreState> state; + SwapChainCoreState swapChain; - state.lastReceivedNotDisplayedAllInputTime = 1000; - state.lastReceivedNotDisplayedMouseClickTime = 2000; - state.lastReceivedNotDisplayedAppProviderInputTime = 3000; + swapChain.lastReceivedNotDisplayedAllInputTime = 1000; + swapChain.lastReceivedNotDisplayedMouseClickTime = 2000; + swapChain.lastReceivedNotDisplayedAppProviderInputTime = 3000; - Assert::AreEqual(1000ull, state.lastReceivedNotDisplayedAllInputTime); - Assert::AreEqual(2000ull, state.lastReceivedNotDisplayedMouseClickTime); - Assert::AreEqual(3000ull, state.lastReceivedNotDisplayedAppProviderInputTime); + Assert::AreEqual(1000ull, swapChain.lastReceivedNotDisplayedAllInputTime); + Assert::AreEqual(2000ull, swapChain.lastReceivedNotDisplayedMouseClickTime); + Assert::AreEqual(3000ull, swapChain.lastReceivedNotDisplayedAppProviderInputTime); } TEST_METHOD(PcLatencyAccumulation_InitializesToZero) { - SwapChainCoreState> state; + SwapChainCoreState swapChain; - Assert::AreEqual(0.0, state.accumulatedInput2FrameStartTime); + Assert::AreEqual(0.0, swapChain.accumulatedInput2FrameStartTime); } - TEST_METHOD(PcLatencyAccumulation_CanAccumulateDroppedFrames) + TEST_METHOD(PcLatencyAccumulation_CanAccumulateTime) { - SwapChainCoreState> state; + SwapChainCoreState swapChain; // Simulate accumulating 3 dropped frames at 16.666ms each - state.accumulatedInput2FrameStartTime += 16.666; - state.accumulatedInput2FrameStartTime += 16.666; - state.accumulatedInput2FrameStartTime += 16.666; + swapChain.accumulatedInput2FrameStartTime += 16.666; + swapChain.accumulatedInput2FrameStartTime += 16.666; + swapChain.accumulatedInput2FrameStartTime += 16.666; - Assert::AreEqual(49.998, state.accumulatedInput2FrameStartTime, 0.001); + Assert::AreEqual(49.998, swapChain.accumulatedInput2FrameStartTime, 0.001); } TEST_METHOD(AnimationErrorSource_DefaultsToCpuStart) { - SwapChainCoreState> state; + SwapChainCoreState swapChain; - Assert::IsTrue(state.animationErrorSource == AnimationErrorSource::CpuStart); + Assert::IsTrue(swapChain.animationErrorSource == AnimationErrorSource::CpuStart); } TEST_METHOD(AnimationErrorSource_CanBeChanged) { - SwapChainCoreState> state; + SwapChainCoreState swapChain; + + swapChain.animationErrorSource = AnimationErrorSource::PCLatency; + Assert::IsTrue(swapChain.animationErrorSource == AnimationErrorSource::PCLatency); + + swapChain.animationErrorSource = AnimationErrorSource::AppProvider; + Assert::IsTrue(swapChain.animationErrorSource == AnimationErrorSource::AppProvider); + } + }; + + // ============================================================================ + // SECTION 2: DisplayIndexing Calculator + // ============================================================================ + + TEST_CLASS(DisplayIndexingTests) + { + public: + TEST_METHOD(Calculate_NoDisplayedFrames_ReturnsEmptyRange) + { + FrameData present{}; + // No displayed frames + present.displayed.clear(); + + auto result = DisplayIndexing::Calculate(present, nullptr); + + Assert::AreEqual(size_t(0), result.startIndex); + Assert::AreEqual(size_t(0), result.endIndex); + Assert::AreEqual(size_t(0), result.appIndex); // No displays → appIndex = 0 + Assert::IsFalse(result.hasNextDisplayed); + } + + TEST_METHOD(Calculate_SingleDisplay_NoNext_Postponed) + { + FrameData present{}; + present.displayed.push_back({ FrameType::Application, 1000 }); + present.setFinalState(PresentResult::Presented); + + auto result = DisplayIndexing::Calculate(present, nullptr); + + // Single display with no next = postponed (empty range) + Assert::AreEqual(size_t(0), result.startIndex); + Assert::AreEqual(size_t(0), result.endIndex); // Empty! Postponed + Assert::AreEqual(size_t(0), result.appIndex); // Would be 0 if processed + Assert::IsFalse(result.hasNextDisplayed); + } + + TEST_METHOD(Calculate_MultipleDisplays_NoNext_PostponeLast) + { + FrameData present{}; + present.displayed.push_back({ FrameType::Application, 1000 }); + present.displayed.push_back({ FrameType::Repeated, 2000 }); + present.displayed.push_back({ FrameType::Repeated, 3000 }); + present.setFinalState(PresentResult::Presented); + + auto result = DisplayIndexing::Calculate(present, nullptr); + + // Process [0..1], postpone [2] + Assert::AreEqual(size_t(0), result.startIndex); + Assert::AreEqual(size_t(2), result.endIndex); // Excludes last! + Assert::AreEqual(size_t(0), result.appIndex); // App frame at index 0 + Assert::IsFalse(result.hasNextDisplayed); + } + + TEST_METHOD(Calculate_MultipleDisplays_WithNext_ProcessPostponed) + { + FrameData present{}; + present.displayed.push_back({ FrameType::Application, 1000 }); + present.displayed.push_back({ FrameType::Repeated, 2000 }); + present.displayed.push_back({ FrameType::Repeated, 3000 }); + present.setFinalState(PresentResult::Presented); + + FrameData next{}; + next.displayed.push_back({ FrameType::Application, 4000 }); + + auto result = DisplayIndexing::Calculate(present, &next); + + // Process only postponed last display [2] + Assert::AreEqual(size_t(2), result.startIndex); + Assert::AreEqual(size_t(3), result.endIndex); + Assert::AreEqual(SIZE_MAX, result.appIndex); // No app frame at [2], it's Repeated + Assert::IsTrue(result.hasNextDisplayed); + } + + TEST_METHOD(Calculate_NotDisplayed_ReturnsEmptyRange) + { + FrameData present{}; + present.displayed.push_back({ FrameType::Application, 1000 }); + present.displayed.push_back({ FrameType::Repeated, 2000 }); + // Don't set finalState = Presented, so displayed = false + + auto result = DisplayIndexing::Calculate(present, nullptr); + + // Not displayed → empty range + Assert::AreEqual(size_t(0), result.startIndex); + Assert::AreEqual(size_t(0), result.endIndex); + Assert::AreEqual(size_t(0), result.appIndex); // Fallback when displayCount > 0 but not displayed + Assert::IsFalse(result.hasNextDisplayed); + } + + TEST_METHOD(Calculate_FindsAppFrameIndex_Displayed) + { + FrameData present{}; + present.displayed.push_back({ FrameType::Repeated, 1000 }); + present.displayed.push_back({ FrameType::Application, 2000 }); + present.displayed.push_back({ FrameType::Repeated, 3000 }); + present.setFinalState(PresentResult::Presented); + + auto result = DisplayIndexing::Calculate(present, nullptr); + + // Process [0..1], postpone [2] + Assert::AreEqual(size_t(0), result.startIndex); + Assert::AreEqual(size_t(2), result.endIndex); + Assert::AreEqual(size_t(1), result.appIndex); // App at index 1 + } + + TEST_METHOD(Calculate_FindsAppFrameIndex_NotDisplayed) + { + FrameData present{}; + present.displayed.push_back({ FrameType::Repeated, 1000 }); + present.displayed.push_back({ FrameType::Application, 2000 }); + present.displayed.push_back({ FrameType::Repeated, 3000 }); + // Not displayed + + auto result = DisplayIndexing::Calculate(present, nullptr); + + // Not displayed → empty range + Assert::AreEqual(size_t(0), result.startIndex); + Assert::AreEqual(size_t(0), result.endIndex); + } + + TEST_METHOD(Calculate_AllRepeatedFrames_AppIndexInvalid) + { + FrameData present{}; + present.displayed.push_back({ FrameType::Repeated, 1000 }); + present.displayed.push_back({ FrameType::Repeated, 2000 }); + present.displayed.push_back({ FrameType::Repeated, 3000 }); + present.setFinalState(PresentResult::Presented); + + auto result = DisplayIndexing::Calculate(present, nullptr); + + // Process [0..1], postpone [2] + Assert::AreEqual(size_t(0), result.startIndex); + Assert::AreEqual(size_t(2), result.endIndex); + Assert::AreEqual(SIZE_MAX, result.appIndex); // No app frame found + } + + TEST_METHOD(Calculate_MultipleAppFrames_FindsFirst) + { + FrameData present{}; + present.displayed.push_back({ FrameType::Application, 1000 }); + present.displayed.push_back({ FrameType::Application, 2000 }); + present.displayed.push_back({ FrameType::Repeated, 3000 }); + present.setFinalState(PresentResult::Presented); + + auto result = DisplayIndexing::Calculate(present, nullptr); + + // Process [0..1], postpone [2] + Assert::AreEqual(size_t(0), result.startIndex); + Assert::AreEqual(size_t(2), result.endIndex); + Assert::AreEqual(size_t(0), result.appIndex); // First app frame + } + + TEST_METHOD(Calculate_WorksWithFrameData) + { + // Verify template works with FrameData + FrameData present{}; + present.displayed.push_back({ FrameType::Application, 1000 }); + present.setFinalState(PresentResult::Presented); + + auto result = DisplayIndexing::Calculate(present, nullptr); + + Assert::AreEqual(size_t(0), result.startIndex); + Assert::AreEqual(size_t(0), result.endIndex); // Postponed [0], nothing processed + Assert::IsTrue(result.appIndex == 0); + } + }; + + // ============================================================================ + // SECTION 3: Helper Functions + // ============================================================================ + + TEST_CLASS(CalculateCPUStartTests) + { + public: + TEST_METHOD(UsesAppPropagatedWhenAvailable) + { + // Setup: swapchain with lastAppPresent that has AppPropagated data + SwapChainCoreState swapChain{}; + FrameData lastApp{}; + lastApp.appPropagatedPresentStartTime = 1000; + lastApp.appPropagatedTimeInPresent = 50; + swapChain.lastAppPresent = lastApp; // std::optional assignment + + FrameData current{}; + current.presentStartTime = 2000; + + auto result = CalculateCPUStart(swapChain, current); + + // Should use appPropagated: 1000 + 50 = 1050 + Assert::AreEqual(1050ull, result); + } + + TEST_METHOD(FallsBackToRegularPresentStart) + { + // Setup: swapchain with lastAppPresent but NO appPropagated data + SwapChainCoreState swapChain{}; + FrameData lastApp{}; + lastApp.appPropagatedPresentStartTime = 0; // No propagated data + lastApp.presentStartTime = 1000; + lastApp.timeInPresent = 50; + swapChain.lastAppPresent = lastApp; + + FrameData current{}; + + auto result = CalculateCPUStart(swapChain, current); + + // Should use regular: 1000 + 50 = 1050 + Assert::AreEqual(1050ull, result); + } + + TEST_METHOD(UsesLastPresentWhenNoAppPresent) + { + // Setup: swapchain with lastPresent but NO lastAppPresent + SwapChainCoreState swapChain{}; + // lastAppPresent is std::nullopt by default + + FrameData lastPresent{}; + lastPresent.presentStartTime = 1000; + lastPresent.timeInPresent = 50; + swapChain.lastPresent = lastPresent; + + FrameData current{}; + current.timeInPresent = 30; + + auto result = CalculateCPUStart(swapChain, current); + + // Should use lastPresents values: 1000 + 50 (last presents start time and the + // time it spent in that present). This would equal the last presents + // stop time which is the earliest the application can start the next frame. + Assert::AreEqual(1050ull, result); + } + + TEST_METHOD(ReturnsZeroWhenNoHistory) + { + // Setup: empty chain (both optionals are std::nullopt) + SwapChainCoreState swapChain{}; + + FrameData current{}; + current.presentStartTime = 2000; + + auto result = CalculateCPUStart(swapChain, current); + + // Should return 0 when no history + Assert::AreEqual(0ull, result); + } + }; + + TEST_CLASS(CalculateSimStartTimeTests) + { + public: + TEST_METHOD(UsesCpuStartSource) + { + QpcCalculator qpc{ 10000000, 0 }; // 10 MHz for easy math + + SwapChainCoreState swapChain{}; + FrameData lastApp{}; + lastApp.presentStartTime = 1000; + lastApp.timeInPresent = 50; + swapChain.lastAppPresent = lastApp; + + FrameData current{}; + current.appSimStartTime = 5000; // Has appSim, but source is CpuStart + + auto result = CalculateSimStartTime(swapChain, current, AnimationErrorSource::CpuStart); + + // Should use CPU start calculation: 1000 + 50 = 1050 + Assert::AreEqual(1050ull, result); + } + + TEST_METHOD(UsesAppProviderSource) + { + QpcCalculator qpc{ 10000000, 0 }; + + SwapChainCoreState swapChain{}; + FrameData lastApp{}; + lastApp.presentStartTime = 1000; + lastApp.timeInPresent = 50; + swapChain.lastAppPresent = lastApp; + + FrameData current{}; + current.appSimStartTime = 5000; + + auto result = CalculateSimStartTime(swapChain, current, AnimationErrorSource::AppProvider); + + // Should use appSimStartTime + Assert::AreEqual(5000ull, result); + } + + TEST_METHOD(UsesPCLatencySource) + { + QpcCalculator qpc{ 10000000, 0 }; + + SwapChainCoreState swapChain{}; + FrameData lastApp{}; + lastApp.presentStartTime = 1000; + lastApp.timeInPresent = 50; + swapChain.lastAppPresent = lastApp; + + FrameData current{}; + current.pclSimStartTime = 6000; + + auto result = CalculateSimStartTime(swapChain, current, AnimationErrorSource::PCLatency); + + // Should use pclSimStartTime + Assert::AreEqual(6000ull, result); + } + + TEST_METHOD(AppProviderFallsBackToCpuStartWhenZero) + { + QpcCalculator qpc{ 10000000, 0 }; + + SwapChainCoreState swapChain{}; + FrameData lastApp{}; + lastApp.presentStartTime = 1000; + lastApp.timeInPresent = 50; + swapChain.lastAppPresent = lastApp; + + FrameData current{}; + current.appSimStartTime = 0; // Not available + + auto result = CalculateSimStartTime(swapChain, current, AnimationErrorSource::AppProvider); + + // Should fall back to CPU start: 1000 + 50 = 1050 + Assert::AreEqual(1050ull, result); + } + + TEST_METHOD(PCLatencyFallsBackToCpuStartWhenZero) + { + QpcCalculator qpc{ 10000000, 0 }; + + SwapChainCoreState swapChain{}; + FrameData lastApp{}; + lastApp.presentStartTime = 1000; + lastApp.timeInPresent = 50; + swapChain.lastAppPresent = lastApp; + + FrameData current{}; + current.pclSimStartTime = 0; // Not available + + auto result = CalculateSimStartTime(swapChain, current, AnimationErrorSource::PCLatency); + + // Should fall back to CPU start: 1000 + 50 = 1050 + Assert::AreEqual(1050ull, result); + } + }; + + TEST_CLASS(CalculateAnimationTimeTests) + { + public: + TEST_METHOD(ComputesRelativeTime) + { + QpcCalculator qpc{ 10000000, 0 }; // 10 MHz QPC frequency + + uint64_t firstSimStart = 1000; + uint64_t currentSimStart = 1500; // 500 ticks later + + auto result = CalculateAnimationTime(qpc, firstSimStart, currentSimStart); + + // 500 ticks at 10 MHz = 0.05 ms + Assert::AreEqual(0.05, result, 0.001); + } + + TEST_METHOD(HandlesZeroFirst) + { + QpcCalculator qpc{ 10000000, 0 }; + + uint64_t firstSimStart = 0; // Not initialized yet + uint64_t currentSimStart = 1500; + + auto result = CalculateAnimationTime(qpc, firstSimStart, currentSimStart); + + // When first is 0, should return 0 + Assert::AreEqual(0.0, result, 0.001); + } + + TEST_METHOD(HandlesSameTimestamp) + { + QpcCalculator qpc{ 10000000, 0 }; + + uint64_t firstSimStart = 1000; + uint64_t currentSimStart = 1000; // Same as first + + auto result = CalculateAnimationTime(qpc, firstSimStart, currentSimStart); + + // Same timestamp = 0 ms elapsed + Assert::AreEqual(0.0, result, 0.001); + } + + TEST_METHOD(HandlesLargeTimespan) + { + QpcCalculator qpc{ 10000000, 0 }; // 10 MHz + + uint64_t firstSimStart = 1000; + uint64_t currentSimStart = 1000 + (10000000 * 5); // +5 seconds in ticks - state.animationErrorSource = AnimationErrorSource::PCLatency; - Assert::IsTrue(state.animationErrorSource == AnimationErrorSource::PCLatency); + auto result = CalculateAnimationTime(qpc, firstSimStart, currentSimStart); - state.animationErrorSource = AnimationErrorSource::AppProvider; - Assert::IsTrue(state.animationErrorSource == AnimationErrorSource::AppProvider); + // 5 seconds = 5000 ms + Assert::AreEqual(5000.0, result, 0.1); } - TEST_METHOD(WorksWithPresentSnapshotType) + TEST_METHOD(HandlesBackwardsTime) { - // Verify template instantiation works with PresentSnapshot too - SwapChainCoreState state; + QpcCalculator qpc{ 10000000, 0 }; - PresentSnapshot snap{}; - snap.presentStartTime = 5000; + uint64_t firstSimStart = 2000; + uint64_t currentSimStart = 1000; // Earlier than first (unusual but possible) - state.pendingPresents.push_back(snap); - state.lastPresent = snap; + auto result = CalculateAnimationTime(qpc, firstSimStart, currentSimStart); - Assert::AreEqual(size_t(1), state.pendingPresents.size()); - Assert::IsTrue(state.lastPresent.has_value()); - Assert::AreEqual(5000ull, state.lastPresent->presentStartTime); + // Should handle gracefully - returns negative or 0 depending on implementation + // This tests error handling + Assert::IsTrue(result <= 0.0); } }; -} +} \ No newline at end of file diff --git a/PresentMon/PresentMon.hpp b/PresentMon/PresentMon.hpp index 5a02e8bf..7759d9cf 100644 --- a/PresentMon/PresentMon.hpp +++ b/PresentMon/PresentMon.hpp @@ -184,9 +184,6 @@ struct FrameMetrics2 { // - pending presents whose metrics cannot be computed until future presents are received, // - exponential averages of key metrics displayed in console output. struct SwapChainData { - // Shared swap chain core state - pmon::util::metrics::SwapChainCoreState> core; - // Pending presents waiting for the next displayed present. std::vector> mPendingPresents; From 512e7161b71d4e49861f5d44bc8216d86dd9d23a Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Mon, 24 Nov 2025 11:57:02 -0800 Subject: [PATCH 006/205] Added in additional QPC converter class and functions. --- .../CommonUtilities/CommonUtilities.vcxproj | 1 - .../CommonUtilities.vcxproj.filters | 3 -- IntelPresentMon/CommonUtilities/Qpc.cpp | 47 ++++++++++++++++ IntelPresentMon/CommonUtilities/Qpc.h | 35 ++++++++++++ .../CommonUtilities/mc/MetricsCalculator.cpp | 8 +-- .../CommonUtilities/mc/MetricsCalculator.h | 6 +-- .../CommonUtilities/mc/QpcCalculator.h | 45 ---------------- IntelPresentMon/UnitTests/MetricsCore.cpp | 53 ++++++++++--------- 8 files changed, 116 insertions(+), 82 deletions(-) delete mode 100644 IntelPresentMon/CommonUtilities/mc/QpcCalculator.h diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj index 409e5603..4f1c7124 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj @@ -70,7 +70,6 @@ - diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters index f065ddcd..c63131ab 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters @@ -300,9 +300,6 @@ Header Files - - Header Files - Header Files diff --git a/IntelPresentMon/CommonUtilities/Qpc.cpp b/IntelPresentMon/CommonUtilities/Qpc.cpp index de442131..90bfda37 100644 --- a/IntelPresentMon/CommonUtilities/Qpc.cpp +++ b/IntelPresentMon/CommonUtilities/Qpc.cpp @@ -33,6 +33,25 @@ namespace pmon::util return double(end - start) * period; } + double TimestampDeltaToMilliSeconds(uint64_t duration, uint64_t qpcFrequency) noexcept + { + return qpcFrequency == 0 ? 0.0 : (duration * 1000.0) / double(qpcFrequency); + } + + double TimestampDeltaToMilliSeconds(uint64_t start, uint64_t end, uint64_t qpcFrequency) noexcept + { + return (end <= start || qpcFrequency == 0) ? 0.0 : TimestampDeltaToMilliSeconds(end - start, qpcFrequency); + } + + double TimestampDeltaToSignedMilliSeconds(uint64_t start, uint64_t end, uint64_t qpcFrequency) noexcept + { + if (qpcFrequency == 0 || start == 0 || end == 0 || start == end) { + return 0.0; + } + return end > start + ? TimestampDeltaToMilliSeconds(end - start, qpcFrequency) + : -TimestampDeltaToMilliSeconds(start - end, qpcFrequency); + } QpcTimer::QpcTimer() noexcept { @@ -62,4 +81,32 @@ namespace pmon::util std::this_thread::yield(); } } + + // Duration in ticks -> ms + double QpcConverter::TicksToMilliSeconds(uint64_t ticks) const noexcept + { + return ticks * msPerTick_; + } + + // Unsigned delta (0 if end <= start or either is 0) + double QpcConverter::DeltaUnsignedMilliSeconds(uint64_t start, uint64_t end) const noexcept + { + return (end <= start || start == 0 || end == 0) ? 0.0 : TicksToMilliSeconds(end - start); + } + + // Signed delta (positive if end > start; negative if end < start; 0 if invalid) + double QpcConverter::DeltaSignedMilliSeconds(uint64_t start, uint64_t end) const noexcept + { + if (start == 0 || end == 0 || start == end) { + return 0.0; + } + return end > start ? TicksToMilliSeconds(end - start) + : -TicksToMilliSeconds(start - end); + } + + // Convenience: raw duration already a tick count (e.g. TimeInPresent) + double QpcConverter::DurationMilliSeconds(uint64_t tickCount) const noexcept + { + return TicksToMilliSeconds(tickCount); + } } \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/Qpc.h b/IntelPresentMon/CommonUtilities/Qpc.h index 9cacbe0a..40e8b3e3 100644 --- a/IntelPresentMon/CommonUtilities/Qpc.h +++ b/IntelPresentMon/CommonUtilities/Qpc.h @@ -9,6 +9,9 @@ namespace pmon::util double GetTimestampPeriodSeconds() noexcept; void SpinWaitUntilTimestamp(int64_t timestamp) noexcept; double TimestampDeltaToSeconds(int64_t start, int64_t end, double period) noexcept; + double TimestampDeltaToMilliSeconds(uint64_t duration, uint64_t qpcFrequency) noexcept; + double TimestampDeltaToMilliSeconds(uint64_t start, uint64_t end, uint64_t qpcFrequency) noexcept; + double TimestampDeltaToSignedMilliSeconds(uint64_t start, uint64_t end, uint64_t qpcFrequency) noexcept; class QpcTimer { @@ -22,4 +25,36 @@ namespace pmon::util double performanceCounterPeriod_; int64_t startTimestamp_ = 0; }; + + class QpcConverter + { + public: + QpcConverter(uint64_t qpcFrequency, uint64_t sessionStartTimestamp = 0) noexcept + : qpcFrequency_(qpcFrequency) + , msPerTick_(qpcFrequency_ == 0 ? 0.0 : 1000.0 / double(qpcFrequency_)) + , sessionStartTimestamp_(sessionStartTimestamp) + { + } + + uint64_t GetFrequency() const noexcept { return qpcFrequency_; } + double GetMilliSecondsPerTick() const noexcept { return msPerTick_; } + uint64_t GetSessionStartTimestamp() const noexcept { return sessionStartTimestamp_; } + + // Duration in ticks -> ms + double TicksToMilliSeconds(uint64_t ticks) const noexcept; + + // Unsigned delta (0 if end <= start or either is 0) + double DeltaUnsignedMilliSeconds(uint64_t start, uint64_t end) const noexcept; + + // Signed delta (positive if end > start; negative if end < start; 0 if invalid) + double DeltaSignedMilliSeconds(uint64_t start, uint64_t end) const noexcept; + + // Convenience: raw duration already a tick count (e.g. TimeInPresent) + double DurationMilliSeconds(uint64_t tickCount) const noexcept; + + private: + uint64_t qpcFrequency_{ 0 }; + double msPerTick_{ 0.0 }; + uint64_t sessionStartTimestamp_{ 0 }; + }; } \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 90b349da..b1d9be9d 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -62,7 +62,7 @@ namespace pmon::util::metrics } ComputedMetrics ComputeFrameMetrics( - const QpcCalculator& qpc, + const QpcConverter& qpc, const FrameData& present, const FrameData* nextDisplayed, const SwapChainCoreState& chain) @@ -126,14 +126,14 @@ namespace pmon::util::metrics // Helper: Calculate animation time double CalculateAnimationTime( - const QpcCalculator& qpc, + const QpcConverter& qpc, uint64_t firstAppSimStartTime, uint64_t currentSimTime) { double animationTime = 0.0; - uint64_t firstSimStartTime = firstAppSimStartTime != 0 ? firstAppSimStartTime : qpc.GetStartTimestamp(); + uint64_t firstSimStartTime = firstAppSimStartTime != 0 ? firstAppSimStartTime : qpc.GetSessionStartTimestamp(); if (currentSimTime > firstSimStartTime) { - animationTime = qpc.TimestampDeltaToMilliSeconds(firstSimStartTime, currentSimTime); + animationTime = qpc.DeltaUnsignedMilliSeconds(firstSimStartTime, currentSimTime); } return animationTime; } diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h index d74a5c8b..f0191b95 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h @@ -3,7 +3,7 @@ #pragma once #include #include -#include "QpcCalculator.h" +#include "../qpc.h" #include "MetricsTypes.h" #include "SwapChainCoreState.h" @@ -41,7 +41,7 @@ namespace pmon::util::metrics // === Pure Calculation Functions === ComputedMetrics ComputeFrameMetrics( - const QpcCalculator& qpc, + const QpcConverter& qpc, const FrameData& present, const FrameData* nextDisplayed, const SwapChainCoreState& chain); @@ -59,7 +59,7 @@ namespace pmon::util::metrics // Helper: Calculate animation time double CalculateAnimationTime( - const QpcCalculator& qpc, + const QpcConverter& qpc, uint64_t firstAppSimStartTime, uint64_t currentSimTime); } \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/mc/QpcCalculator.h b/IntelPresentMon/CommonUtilities/mc/QpcCalculator.h deleted file mode 100644 index 66345a03..00000000 --- a/IntelPresentMon/CommonUtilities/mc/QpcCalculator.h +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once -#include - -namespace pmon::util::metrics -{ - // Simple value-based QPC math utility - // No polymorphism, no environment dependencies - just QPC calculations - class QpcCalculator { - uint64_t qpcFrequency_; - uint64_t sessionStartTimestamp_; - - public: - QpcCalculator(uint64_t qpcFrequency, uint64_t sessionStartTimestamp) - : qpcFrequency_(qpcFrequency) - , sessionStartTimestamp_(sessionStartTimestamp) - { - } - - // Convert QPC duration to milliseconds - double TimestampDeltaToMilliSeconds(uint64_t duration) const { - return duration * 1000.0 / qpcFrequency_; - } - - // Convert time between two QPC timestamps to milliseconds (signed) - double TimestampDeltaToMilliSeconds(uint64_t start, uint64_t end) const - { - return start == 0 || end == 0 || start == end ? 0.0 : - end > start ? TimestampDeltaToMilliSeconds(end - start) - : -TimestampDeltaToMilliSeconds(start - end); - } - - // Convert time between two QPC timestamps to milliseconds - double TimestampDeltaToUnsignedMilliSeconds(uint64_t start, uint64_t end) const { - return (end - start) * 1000.0 / qpcFrequency_; - } - - // Get trace session start timestamp (for animation time calculations) - uint64_t GetStartTimestamp() const { - return sessionStartTimestamp_; - } - }; - -} \ No newline at end of file diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index e92a806c..4a84d581 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -1,7 +1,7 @@ // Copyright (C) 2025 Intel Corporation // SPDX-License-Identifier: MIT #include -#include +#include #include #include #include @@ -10,6 +10,7 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace pmon::util::metrics; +using namespace pmon::util; namespace MetricsCoreTests { @@ -17,68 +18,68 @@ namespace MetricsCoreTests // SECTION 1: Core Types & Foundation // ============================================================================ - TEST_CLASS(QpcCalculatorTests) + TEST_CLASS(QpcConverterTests) { public: TEST_METHOD(TimestampDeltaToMilliSeconds_BasicConversion) { // 10MHz QPC frequency (10,000,000 ticks per second) - QpcCalculator qpc(10'000'000, 0); + QpcConverter qpc(10'000'000, 0); // 10,000 ticks = 1 millisecond at 10MHz - double result = qpc.TimestampDeltaToMilliSeconds(10'000); + double result = qpc.DurationMilliSeconds(10'000); Assert::AreEqual(1.0, result, 0.0001); } TEST_METHOD(TimestampDeltaToMilliSeconds_ZeroDuration) { - QpcCalculator qpc(10'000'000, 0); - double result = qpc.TimestampDeltaToMilliSeconds(0); + QpcConverter qpc(10'000'000, 0); + double result = qpc.DurationMilliSeconds(0); Assert::AreEqual(0.0, result); } TEST_METHOD(TimestampDeltaToMilliSeconds_LargeDuration) { - QpcCalculator qpc(10'000'000, 0); + QpcConverter qpc(10'000'000, 0); // 100,000,000 ticks = 10,000 milliseconds at 10MHz - double result = qpc.TimestampDeltaToMilliSeconds(100'000'000); + double result = qpc.DurationMilliSeconds(100'000'000); Assert::AreEqual(10'000.0, result, 0.01); } TEST_METHOD(TimestampDeltaToUnsignedMilliSeconds_ForwardTime) { - QpcCalculator qpc(10'000'000, 0); + QpcConverter qpc(10'000'000, 0); // Start at 1000, end at 11000 (10,000 ticks = 1ms) - double result = qpc.TimestampDeltaToUnsignedMilliSeconds(1000, 11'000); + double result = qpc.DeltaUnsignedMilliSeconds(1000, 11'000); Assert::AreEqual(1.0, result, 0.0001); } TEST_METHOD(TimestampDeltaToUnsignedMilliSeconds_ZeroDelta) { - QpcCalculator qpc(10'000'000, 0); - double result = qpc.TimestampDeltaToUnsignedMilliSeconds(5000, 5000); + QpcConverter qpc(10'000'000, 0); + double result = qpc.DeltaUnsignedMilliSeconds(5000, 5000); Assert::AreEqual(0.0, result); } TEST_METHOD(TimestampDeltaToUnsignedMilliSeconds_TypicalFrameTime) { // Typical QPC frequency: ~10MHz - QpcCalculator qpc(10'000'000, 0); + QpcConverter qpc(10'000'000, 0); // 16.666ms frame time at 60fps uint64_t frameTimeTicks = 166'660; - double result = qpc.TimestampDeltaToUnsignedMilliSeconds(0, frameTimeTicks); + double result = qpc.DurationMilliSeconds(frameTimeTicks); Assert::AreEqual(16.666, result, 0.001); } TEST_METHOD(GetStartTimestamp_ReturnsCorrectValue) { uint64_t startTime = 123'456'789; - QpcCalculator qpc(10'000'000, startTime); + QpcConverter qpc(10'000'000, startTime); - Assert::AreEqual(startTime, qpc.GetStartTimestamp()); + Assert::AreEqual(startTime, qpc.GetSessionStartTimestamp()); } }; @@ -716,7 +717,7 @@ namespace MetricsCoreTests public: TEST_METHOD(UsesCpuStartSource) { - QpcCalculator qpc{ 10000000, 0 }; // 10 MHz for easy math + QpcConverter qpc{ 10000000, 0 }; // 10 MHz for easy math SwapChainCoreState swapChain{}; FrameData lastApp{}; @@ -735,7 +736,7 @@ namespace MetricsCoreTests TEST_METHOD(UsesAppProviderSource) { - QpcCalculator qpc{ 10000000, 0 }; + QpcConverter qpc{ 10000000, 0 }; SwapChainCoreState swapChain{}; FrameData lastApp{}; @@ -754,7 +755,7 @@ namespace MetricsCoreTests TEST_METHOD(UsesPCLatencySource) { - QpcCalculator qpc{ 10000000, 0 }; + QpcConverter qpc{ 10000000, 0 }; SwapChainCoreState swapChain{}; FrameData lastApp{}; @@ -773,7 +774,7 @@ namespace MetricsCoreTests TEST_METHOD(AppProviderFallsBackToCpuStartWhenZero) { - QpcCalculator qpc{ 10000000, 0 }; + QpcConverter qpc{ 10000000, 0 }; SwapChainCoreState swapChain{}; FrameData lastApp{}; @@ -792,7 +793,7 @@ namespace MetricsCoreTests TEST_METHOD(PCLatencyFallsBackToCpuStartWhenZero) { - QpcCalculator qpc{ 10000000, 0 }; + QpcConverter qpc{ 10000000, 0 }; SwapChainCoreState swapChain{}; FrameData lastApp{}; @@ -815,7 +816,7 @@ namespace MetricsCoreTests public: TEST_METHOD(ComputesRelativeTime) { - QpcCalculator qpc{ 10000000, 0 }; // 10 MHz QPC frequency + QpcConverter qpc{ 10000000, 0 }; // 10 MHz QPC frequency uint64_t firstSimStart = 1000; uint64_t currentSimStart = 1500; // 500 ticks later @@ -828,7 +829,7 @@ namespace MetricsCoreTests TEST_METHOD(HandlesZeroFirst) { - QpcCalculator qpc{ 10000000, 0 }; + QpcConverter qpc{ 10000000, 0 }; uint64_t firstSimStart = 0; // Not initialized yet uint64_t currentSimStart = 1500; @@ -841,7 +842,7 @@ namespace MetricsCoreTests TEST_METHOD(HandlesSameTimestamp) { - QpcCalculator qpc{ 10000000, 0 }; + QpcConverter qpc{ 10000000, 0 }; uint64_t firstSimStart = 1000; uint64_t currentSimStart = 1000; // Same as first @@ -854,7 +855,7 @@ namespace MetricsCoreTests TEST_METHOD(HandlesLargeTimespan) { - QpcCalculator qpc{ 10000000, 0 }; // 10 MHz + QpcConverter qpc{ 10000000, 0 }; // 10 MHz uint64_t firstSimStart = 1000; uint64_t currentSimStart = 1000 + (10000000 * 5); // +5 seconds in ticks @@ -867,7 +868,7 @@ namespace MetricsCoreTests TEST_METHOD(HandlesBackwardsTime) { - QpcCalculator qpc{ 10000000, 0 }; + QpcConverter qpc{ 10000000, 0 }; uint64_t firstSimStart = 2000; uint64_t currentSimStart = 1000; // Earlier than first (unusual but possible) From e0194286ad2a18bb2c1e3e9319cf9adb72c601f9 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Wed, 26 Nov 2025 08:12:49 -0800 Subject: [PATCH 007/205] Continued drafting changes for unified metric calculations --- .../CommonUtilities/CommonUtilities.vcxproj | 3 +- .../CommonUtilities.vcxproj.filters | 5 +- .../CommonUtilities/mc/MetricsAdapters.cpp | 3 - .../CommonUtilities/mc/MetricsAdapters.h | 3 - .../CommonUtilities/mc/MetricsCalculator.cpp | 86 ++- .../CommonUtilities/mc/MetricsCalculator.h | 14 +- .../CommonUtilities/mc/MetricsPolicies.cpp | 122 ----- .../CommonUtilities/mc/MetricsPolicies.h | 76 --- .../CommonUtilities/mc/MetricsTypes.h | 60 +-- .../CommonUtilities/mc/SwapChainState.cpp | 110 ++++ ...{SwapChainCoreState.h => SwapChainState.h} | 2 + .../PresentMonAPI2Tests/SwapChainTests.cpp | 26 +- .../PresentMonMiddleware/ConcreteMiddleware.h | 2 +- IntelPresentMon/UnitTests/MetricsCore.cpp | 509 +++++++++++++++++- PresentMon/PresentMon.hpp | 2 +- 15 files changed, 756 insertions(+), 267 deletions(-) delete mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsAdapters.cpp delete mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsAdapters.h delete mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsPolicies.cpp delete mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsPolicies.h create mode 100644 IntelPresentMon/CommonUtilities/mc/SwapChainState.cpp rename IntelPresentMon/CommonUtilities/mc/{SwapChainCoreState.h => SwapChainState.h} (97%) diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj index 4f1c7124..aff34859 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj @@ -70,7 +70,7 @@ - + @@ -155,6 +155,7 @@ + diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters index c63131ab..3252e88c 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters @@ -300,7 +300,7 @@ Header Files - + Header Files @@ -485,6 +485,9 @@ Source Files + + Source Files + diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsAdapters.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsAdapters.cpp deleted file mode 100644 index b284ec1c..00000000 --- a/IntelPresentMon/CommonUtilities/mc/MetricsAdapters.cpp +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright (C) 2025 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsAdapters.h b/IntelPresentMon/CommonUtilities/mc/MetricsAdapters.h deleted file mode 100644 index b284ec1c..00000000 --- a/IntelPresentMon/CommonUtilities/mc/MetricsAdapters.h +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright (C) 2025 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index b1d9be9d..d0e6da48 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -61,14 +61,98 @@ namespace pmon::util::metrics return result; } - ComputedMetrics ComputeFrameMetrics( + std::vector ComputeMetricsForPresent( const QpcConverter& qpc, const FrameData& present, const FrameData* nextDisplayed, + SwapChainCoreState& chainState) + { + std::vector results; + + const auto displayCount = present.getDisplayedCount(); + const bool isDisplayed = present.getFinalState() == PresentResult::Presented && displayCount > 0; + + // Case 1: not displayed, return single not-displayed metrics + if (!isDisplayed || displayCount == 0) { + const uint64_t screenTime = 0; + const uint64_t nextScreenTime = 0; + const bool isDisplayed = false; + const bool isAppFrame = false; + + auto metrics = ComputeFrameMetrics( + qpc, + present, + screenTime, + nextScreenTime, + isDisplayed, + isAppFrame, + chainState); + results.push_back(std::move(metrics)); + + // TODO - Check and remove this comment -> + // Matches old ReportMetricsHelper: UpdateChain is called for not-displayed frames. + chainState.UpdateAfterPresent(present); + + return results; + } + + // There is at least one displayed frame to process + const auto indexing = DisplayIndexing::Calculate(present, nextDisplayed); + + // Determine if we should update the swap chain based on nextDisplayed + const bool shouldUpdateSwapChain = (nextDisplayed != nullptr); + + for (size_t displayIndex = indexing.startIndex; displayIndex < indexing.endIndex; ++displayIndex) { + uint64_t screenTime = present.getDisplayedScreenTime(displayIndex); + uint64_t nextScreenTime = 0; + + if (displayIndex + 1 < displayCount) { + // Next display instance of the same present + nextScreenTime = present.getDisplayedScreenTime(displayIndex + 1); + } + else if (nextDisplayed != nullptr && nextDisplayed->getDisplayedCount() > 0) { + // First display of the *next* presented frame + nextScreenTime = nextDisplayed->getDisplayedScreenTime(0); + } + else { + break; // No next screen time available + } + + const bool isAppFrame = (displayIndex == indexing.appIndex); + + auto metrics = ComputeFrameMetrics( + qpc, + present, + screenTime, + nextScreenTime, + isDisplayed, + isAppFrame, + chainState); + results.push_back(std::move(metrics)); + } + + // Matches old ReportMetricsHelper: + // - Case 2 (no nextDisplayed): no UpdateChain yet. + // - Case 3 (has nextDisplayed): this is the call that finally updates the chain. + if (shouldUpdateSwapChain) { + chainState.UpdateAfterPresent(present); + } + + return results; + } + + ComputedMetrics ComputeFrameMetrics( + const QpcConverter& qpc, + const FrameData& present, + uint64_t screenTime, + uint64_t nextScreenTime, + bool isDisplayed, + bool isAppFrame, const SwapChainCoreState& chain) { ComputedMetrics result{}; + FrameMetrics& metrics = result.metrics; return result; } diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h index f0191b95..9ddd5c9f 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h @@ -3,9 +3,10 @@ #pragma once #include #include +#include #include "../qpc.h" #include "MetricsTypes.h" -#include "SwapChainCoreState.h" +#include "SwapChainState.h" namespace pmon::util::metrics { @@ -38,12 +39,21 @@ namespace pmon::util::metrics const FrameData* nextDisplayed); }; + std::vector ComputeMetricsForPresent( + const QpcConverter& qpc, + const FrameData& present, + const FrameData* nextDisplayed, + SwapChainCoreState& chainState); + // === Pure Calculation Functions === ComputedMetrics ComputeFrameMetrics( const QpcConverter& qpc, const FrameData& present, - const FrameData* nextDisplayed, + uint64_t screenTime, + uint64_t nextScreenTime, + bool isDisplayed, + bool isAppFrame, const SwapChainCoreState& chain); // Helper: Calculate CPU start time diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.cpp deleted file mode 100644 index 2438dc2e..00000000 --- a/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.cpp +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (C) 2025 Intel Corporation -// SPDX-License-Identifier: MIT -#include "MetricsTypes.h" -#include "MetricsPolicies.h" - -// Include full definitions -#include "../../../PresentMon/PresentMon.hpp" -#include "../..//PresentMonMiddleware/ConcreteMiddleware.h" - -namespace pmon::util::metrics { - -void ConsoleMetricsPolicy::OnMetricsComputed( - const FrameMetrics& metrics, - const ComputedMetrics::StateDeltas& deltas, - SwapChainData& chain, - size_t displayIndex, - bool isAppIndex) -{ - if (deltas.newAccumulatedInput2FrameStart) { - chain.core.accumulatedInput2FrameStartTime = *deltas.newAccumulatedInput2FrameStart; - } - - if (deltas.newLastReceivedPclSimStart) { - chain.core.lastReceivedNotDisplayedPclSimStart = *deltas.newLastReceivedPclSimStart; - } - - if (deltas.lastReceivedNotDisplayedAllInputTime) { - chain.core.lastReceivedNotDisplayedAllInputTime = *deltas.lastReceivedNotDisplayedAllInputTime; - } - - if (deltas.lastReceivedNotDisplayedMouseClickTime) { - chain.core.lastReceivedNotDisplayedMouseClickTime = *deltas.lastReceivedNotDisplayedMouseClickTime; - } - - // Console-specific: Update averages (UNCHANGED - not in core) - if (isRecording_) { - chain.mAvgCPUDuration = UpdateExponentialMovingAverage( - chain.mAvgCPUDuration, metrics.msCPUBusy); - chain.mAvgGPUDuration = UpdateExponentialMovingAverage( - chain.mAvgGPUDuration, metrics.msGPUBusy); - // ... etc - } - - // Write to CSV (unchanged logic) - if (isRecording_) { - WriteCsvRow(processInfo_, metrics); - } -} - -void ConsoleMetricsPolicy::OnFrameComplete( - const PresentSnapshot& present, - SwapChainData& chain) -{ - // BEFORE: Update chain state directly - // chain.mLastPresent = present.toSharedPtr(); - // chain.mLastSimStartTime = present.simStartTime; - - // AFTER: Update through .core member - // Note: Might need to reconstruct shared_ptr from snapshot, or keep original - // Alternatively, policy might track the original shared_ptr separately - chain.core.lastSimStartTime = GetSimStartTime(present); - - // Update last displayed times if this frame was displayed - if (IsDisplayed(present)) { - chain.core.lastDisplayedSimStartTime = GetSimStartTime(present); - chain.core.lastDisplayedScreenTime = present.screenTime; - } -} - -void MiddlewareMetricsPolicy::OnMetricsComputed( - const FrameMetrics& metrics, - const ComputedMetrics::StateDeltas& deltas, - pmon::mid::fpsSwapChainData& chain, - size_t displayIndex, - bool isAppIndex) -{ - // BEFORE: Apply deltas to chain fields directly - // if (deltas.newAccumulatedInput2FrameStart) { - // chain.mAccumulatedInput2FrameStartTime = *deltas.newAccumulatedInput2FrameStart; - // } - - // AFTER: Apply deltas through .core member (IDENTICAL to Console!) - if (deltas.newAccumulatedInput2FrameStart) { - chain.core.accumulatedInput2FrameStartTime = *deltas.newAccumulatedInput2FrameStart; - } - - if (deltas.newLastReceivedPclSimStart) { - chain.core.lastReceivedNotDisplayedPclSimStart = *deltas.newLastReceivedPclSimStart; - } - - if (deltas.lastReceivedNotDisplayedAllInputTime) { - chain.core.lastReceivedNotDisplayedAllInputTime = *deltas.lastReceivedNotDisplayedAllInputTime; - } - - // Middleware-specific: Push to telemetry vectors (UNCHANGED - not in core) - if (isAppIndex) { - chain.mCPUBusy.push_back(metrics.msCPUBusy); - chain.mGPULatency.push_back(metrics.msGPULatency); - chain.mDisplayLatency.push_back(metrics.msDisplayLatency); - } - - // Update InputToFsManager (unchanged logic) - pclManager_.UpdateMetrics(processId_, metrics); -} - -void MiddlewareMetricsPolicy::OnFrameComplete( - const PresentSnapshot& present, - pmon::mid::fpsSwapChainData& chain) -{ - // AFTER: Update through .core member (same pattern as Console) - chain.core.lastSimStartTime = present.simStartTime; - - if (IsDisplayed(present)) { - chain.core.lastDisplayedSimStartTime = present.simStartTime; - chain.core.lastDisplayedScreenTime = present.screenTime; - } - - // Middleware-specific: Update optimization counters (UNCHANGED) - chain.display_count++; -} - -} // namespace pmon::metrics \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.h b/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.h deleted file mode 100644 index 03f59852..00000000 --- a/IntelPresentMon/CommonUtilities/mc/MetricsPolicies.h +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (C) 2025 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once -#include "MetricsTypes.h" -#include "MetricsCalculator.h" - -struct SwapChainData; // Defined in PresentData/PresentMonTraceConsumer.hpp -struct fpsSwapChainData; // Defined in IntelPresentMon/ -struct ProcessInfo; // Defined in PresentData/PresentMonTraceConsumer.hpp -class InputToFsManager; // Defined in - -namespace pmon::util::metrics { - // Policy interface - works with forward-declared types - template - class MetricsOutputPolicy { - public: - virtual ~MetricsOutputPolicy() = default; - - virtual void OnMetricsComputed( - const FrameMetrics& metrics, - const ComputedMetrics::StateDeltas& deltas, - SwapChainT& chain, // ← Forward-declared type is fine here - size_t displayIndex, - bool isAppIndex) = 0; - - virtual void OnFrameComplete( - const PresentSnapshot& present, - SwapChainT& chain) = 0; // ← Forward-declared type is fine here - }; - - // Console policy - forward-declared types in declaration - class ConsoleMetricsPolicy : public MetricsOutputPolicy { - ProcessInfo* processInfo_; // ← Pointer to forward-declared type is fine - bool isRecording_; - bool computeAvg_; - - public: - ConsoleMetricsPolicy( - ProcessInfo* processInfo, - bool isRecording, - bool computeAvg); - - void OnMetricsComputed( - const FrameMetrics& metrics, - const ComputedMetrics::StateDeltas& deltas, - SwapChainData& chain, - size_t displayIndex, - bool isAppIndex) override; - - void OnFrameComplete( - const PresentSnapshot& present, - SwapChainData& chain) override; - }; - - // Middleware policy - forward-declared types in declaration - class MiddlewareMetricsPolicy : public MetricsOutputPolicy { - InputToFsManager& pclManager_; // ← Reference to forward-declared type is fine - uint32_t processId_; - - public: - MiddlewareMetricsPolicy( - InputToFsManager& pclManager, - uint32_t processId); - - void OnMetricsComputed( - const FrameMetrics& metrics, - const ComputedMetrics::StateDeltas& deltas, - fpsSwapChainData& chain, - size_t displayIndex, - bool isAppIndex) override; - - void OnFrameComplete( - const PresentSnapshot& present, - fpsSwapChainData& chain) override; - }; -} \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h index 358ee05a..f42cf31e 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h @@ -25,50 +25,50 @@ namespace pmon::util::metrics { // Immutable snapshot - safe for both ownership models struct FrameData { // Timing Data - uint64_t presentStartTime; - uint64_t readyTime; - uint64_t timeInPresent; - uint64_t gpuStartTime; - uint64_t gpuDuration; - uint64_t gpuVideoDuration; + uint64_t presentStartTime = 0; + uint64_t readyTime = 0; + uint64_t timeInPresent = 0; + uint64_t gpuStartTime = 0; + uint64_t gpuDuration = 0; + uint64_t gpuVideoDuration = 0; // Used to track the application work when Intel XeSS-FG is enabled - uint64_t appPropagatedPresentStartTime; - uint64_t appPropagatedTimeInPresent; - uint64_t appPropagatedGPUStartTime; - uint64_t appPropagatedReadyTime; - uint64_t appPropagatedGPUDuration; - uint64_t appPropagatedGPUVideoDuration; + uint64_t appPropagatedPresentStartTime = 0; + uint64_t appPropagatedTimeInPresent = 0; + uint64_t appPropagatedGPUStartTime = 0; + uint64_t appPropagatedReadyTime = 0; + uint64_t appPropagatedGPUDuration = 0; + uint64_t appPropagatedGPUVideoDuration = 0; // Instrumented Timestamps - uint64_t appSimStartTime; - uint64_t appSleepStartTime; - uint64_t appSleepEndTime; - uint64_t appRenderSubmitStartTime; - uint64_t appRenderSubmitEndTime; - uint64_t appPresentStartTime; - uint64_t appPresentEndTime; + uint64_t appSimStartTime = 0; + uint64_t appSleepStartTime = 0; + uint64_t appSleepEndTime = 0; + uint64_t appRenderSubmitStartTime = 0; + uint64_t appRenderSubmitEndTime = 0; + uint64_t appPresentStartTime = 0; + uint64_t appPresentEndTime = 0; std::pair appInputSample; // time, input type // Input Device Timestamps - uint64_t inputTime; // All input devices - uint64_t mouseClickTime; // Mouse click specific + uint64_t inputTime = 0; // All input devices + uint64_t mouseClickTime = 0; // Mouse click specific std::vector> displayed; // PC Latency data - uint64_t pclSimStartTime; - uint64_t pclInputPingTime; - uint64_t flipDelay; - uint32_t FlipToken; + uint64_t pclSimStartTime = 0; + uint64_t pclInputPingTime = 0; + uint64_t flipDelay = 0; + uint32_t FlipToken = 0; // Metadata PresentResult finalState; - uint32_t processId; - uint32_t threadId; - uint64_t swapChainAddress; - uint32_t frameId; - uint32_t appFrameId; + uint32_t processId = 0; + uint32_t threadId = 0; + uint64_t swapChainAddress = 0; + uint32_t frameId = 0; + uint32_t appFrameId = 0; // Setters for test setup void setFinalState(PresentResult state) { finalState = state; } diff --git a/IntelPresentMon/CommonUtilities/mc/SwapChainState.cpp b/IntelPresentMon/CommonUtilities/mc/SwapChainState.cpp new file mode 100644 index 00000000..c28faf81 --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/SwapChainState.cpp @@ -0,0 +1,110 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#include "SwapChainState.h" +#include "../PresentData/PresentMonTraceConsumer.hpp" + +namespace pmon::util::metrics { + + void SwapChainCoreState::UpdateAfterPresent(const FrameData& present) + { + const auto finalState = present.getFinalState(); + const size_t displayCnt = present.getDisplayedCount(); + + if (finalState == PresentResult::Presented) { + if (displayCnt > 0) { + const size_t lastIdx = displayCnt - 1; + const auto lastType = present.getDisplayedFrameType(lastIdx); + const bool lastIsAppFrm = + (lastType == FrameType::NotSet || lastType == FrameType::Application); + + if (lastIsAppFrm) { + const uint64_t lastScreenTime = present.getDisplayedScreenTime(lastIdx); + + // This block is the port of the big "if (p->Displayed.back().first ...)" from UpdateChain + if (animationErrorSource == AnimationErrorSource::AppProvider) { + if (present.getAppSimStartTime() != 0) { + lastDisplayedSimStartTime = present.getAppSimStartTime(); + if (firstAppSimStartTime == 0) { + firstAppSimStartTime = present.getAppSimStartTime(); + } + lastDisplayedAppScreenTime = lastScreenTime; + } + } + else if (animationErrorSource == AnimationErrorSource::PCLatency) { + if (present.getPclSimStartTime() != 0) { + lastDisplayedSimStartTime = present.getPclSimStartTime(); + if (firstAppSimStartTime == 0) { + firstAppSimStartTime = present.getPclSimStartTime(); + } + lastDisplayedAppScreenTime = lastScreenTime; + } + } + else { // AnimationErrorSource::CpuStart + // Check for app or PCL sim start and possibly change source. + if (present.getAppSimStartTime() != 0) { + animationErrorSource = AnimationErrorSource::AppProvider; + lastDisplayedSimStartTime = present.getAppSimStartTime(); + if (firstAppSimStartTime == 0) { + firstAppSimStartTime = present.getAppSimStartTime(); + } + lastDisplayedAppScreenTime = lastScreenTime; + } + else if (present.getPclSimStartTime() != 0) { + animationErrorSource = AnimationErrorSource::PCLatency; + lastDisplayedSimStartTime = present.getPclSimStartTime(); + if (firstAppSimStartTime == 0) { + firstAppSimStartTime = present.getPclSimStartTime(); + } + lastDisplayedAppScreenTime = lastScreenTime; + } + else { + // Fall back to CPU start from last app present, if any. + if (lastAppPresent.has_value()) { + const FrameData& lastApp = lastAppPresent.value(); + lastDisplayedSimStartTime = + lastApp.getPresentStartTime() + lastApp.getTimeInPresent(); + } + lastDisplayedAppScreenTime = lastScreenTime; + } + } + } + } + + // Always track "last displayed" screen time + flip delay when presented. + if (displayCnt > 0) { + const size_t lastIdx = displayCnt - 1; + lastDisplayedScreenTime = present.getDisplayedScreenTime(lastIdx); + lastDisplayedFlipDelay = present.getFlipDelay(); + } + else { + lastDisplayedScreenTime = 0; + lastDisplayedFlipDelay = 0; + } + } + + // Last app present selection (same logic as UpdateChain) + if (displayCnt > 0) { + const size_t lastIdx = displayCnt - 1; + const auto lastType = present.getDisplayedFrameType(lastIdx); + if (lastType == FrameType::NotSet || lastType == FrameType::Application) { + lastAppPresent = present; + } + } + else { + // If not displayed at all, treat this as the last app present. + lastAppPresent = present; + } + + // Last simulation start time: PCL wins over App if both exist. + if (present.getPclSimStartTime() != 0) { + lastSimStartTime = present.getPclSimStartTime(); + } + else if (present.getAppSimStartTime() != 0) { + lastSimStartTime = present.getAppSimStartTime(); + } + + // Always advance lastPresent + lastPresent = present; + } + +} // namespace pmon::util::metrics diff --git a/IntelPresentMon/CommonUtilities/mc/SwapChainCoreState.h b/IntelPresentMon/CommonUtilities/mc/SwapChainState.h similarity index 97% rename from IntelPresentMon/CommonUtilities/mc/SwapChainCoreState.h rename to IntelPresentMon/CommonUtilities/mc/SwapChainState.h index 1cf2554d..7a93a062 100644 --- a/IntelPresentMon/CommonUtilities/mc/SwapChainCoreState.h +++ b/IntelPresentMon/CommonUtilities/mc/SwapChainState.h @@ -67,6 +67,8 @@ struct SwapChainCoreState { // NVIDIA Specific Tracking uint64_t lastDisplayedFlipDelay = 0; + + void UpdateAfterPresent(const FrameData& present); }; } \ No newline at end of file diff --git a/IntelPresentMon/PresentMonAPI2Tests/SwapChainTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/SwapChainTests.cpp index f58b8749..36dfefa1 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/SwapChainTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/SwapChainTests.cpp @@ -1,7 +1,7 @@ // Copyright (C) 2025 Intel Corporation // SPDX-License-Identifier: MIT #include "CppUnitTest.h" -#include "../CommonUtilities/mc/SwapChainCoreState.h" +#include "../CommonUtilities/mc/SwapChainState.h" #include "../CommonUtilities/mc/MetricsTypes.h" #include @@ -314,29 +314,5 @@ namespace SwapChainTests Assert::AreEqual(size_t(2), swapChainOne.pendingPresents.size()); //Assert::AreEqual(presents[1], *swapChainOne.lastPresent); } - - TEST_METHOD(MoveSemantics_Efficient) - { - // Verify that copies are independent (important for value types) - pmon::util::metrics::SwapChainCoreState swapChainOne{}; - - pmon::util::metrics::FrameData presents[100]; - // Set state with large vector - for (int i = 0; i < 100; ++i) { - swapChainOne.pendingPresents.push_back(presents[i]); - } - swapChainOne.lastSimStartTime = 9999; - swapChainOne.lastPresent = presents[99]; - - // Move to core2 - pmon::util::metrics::SwapChainCoreState swapChainTwo = std::move(swapChainOne); - - // Verify core2 has the data - Assert::AreEqual(size_t(100), swapChainTwo.pendingPresents.size()); - Assert::AreEqual(uint64_t(9999), swapChainTwo.lastSimStartTime); - //Assert::AreEqual(presents[99], *swapChainTwo.lastPresent); - - // Note: core1 is in moved-from state, don't test its values - } }; } diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h index 054be562..15f7ecf5 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h @@ -9,7 +9,7 @@ #include "../CommonUtilities/Hash.h" #include "../CommonUtilities/Math.h" #include "FrameTimingData.h" -#include "../IntelPresentMon/CommonUtilities/mc/SwapChainCoreState.h" +#include "../IntelPresentMon/CommonUtilities/mc/SwapChainState.h" namespace pmapi::intro { diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index 4a84d581..6455a744 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include @@ -880,4 +880,511 @@ namespace MetricsCoreTests Assert::IsTrue(result <= 0.0); } }; + + // TEST HELPERS FOR METRICS UNIFICATION + // ============================================================================ + + using namespace pmon::util::metrics; + + // Simple helper to construct FrameData for metrics tests. + static FrameData MakeFrame( + PresentResult finalState, + uint64_t presentStartTime, + uint64_t timeInPresent, + uint64_t readyTime, + const std::vector>& displayed, + uint64_t appSimStartTime = 0, + uint64_t pclSimStartTime = 0, + uint64_t flipDelay = 0) + { + FrameData f{}; + f.presentStartTime = presentStartTime; + f.timeInPresent = timeInPresent; + f.readyTime = readyTime; + f.displayed = displayed; + f.appSimStartTime = appSimStartTime; + f.pclSimStartTime = pclSimStartTime; + f.flipDelay = flipDelay; + f.finalState = finalState; + return f; + } + + // ---------------------------------------------------------------------------- + // Proposed (GoogleTest style names) test case list (implemented with MSTest): + // + // ComputeMetricsForPresent_NotDisplayed_NoDisplays_ProducesSingleMetricsAndUpdatesChain + // ComputeMetricsForPresent_NotDisplayed_WithDisplaysButNotPresented_ProducesSingleMetricsAndUpdatesChain + // ComputeMetricsForPresent_DisplayedNoNext_SingleDisplay_PostponedChainNotUpdated + // ComputeMetricsForPresent_DisplayedNoNext_MultipleDisplays_ProcessesAllButLast + // ComputeMetricsForPresent_DisplayedWithNext_ProcessesPostponedLastAndUpdatesChain + // ComputeMetricsForPresent_DisplayedWithNext_LastDisplayIsRepeated_DoesNotUpdateLastAppPresent + // UpdateAfterPresent_AnimationSource_AppProvider_UpdatesSimStartAndFirstAppSim + // UpdateAfterPresent_AnimationSource_PCLatency_UpdatesSimStartAndFirstAppSim + // UpdateAfterPresent_AnimationSource_CpuStart_FallbackToPreviousAppPresent + // UpdateAfterPresent_AnimationSource_CpuStart_TransitionsToAppProvider + // UpdateAfterPresent_AnimationSource_CpuStart_TransitionsToPCLatency + // UpdateAfterPresent_FlipDelayTracking_PresentedWithDisplays_SetsFlipDelayAndScreenTime + // UpdateAfterPresent_FlipDelayTracking_PresentedNoDisplays_ZeroesFlipDelayAndScreenTime + // UpdateAfterPresent_NotPresented_DoesNotChangeLastDisplayedScreenTime + // + // (Pending future implementation) + // UpdateAfterPresent_InputAccumulation_DroppedFramesAccumulate (when logic added) + // ---------------------------------------------------------------------------- + + TEST_CLASS(ComputeMetricsForPresentTests) + { + public: + TEST_METHOD(ComputeMetricsForPresent_NotDisplayed_NoDisplays_ProducesSingleMetricsAndUpdatesChain) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + auto frame = MakeFrame(PresentResult::Presented, 10'000, 500, 10'500, {}); // Presented but no displays => not displayed path + auto metrics = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + + Assert::AreEqual(size_t(1), metrics.size(), L"Should produce exactly one metrics entry."); + Assert::IsTrue(chain.lastPresent.has_value(), L"Chain should be updated for not displayed."); + Assert::IsTrue(chain.lastAppPresent.has_value(), L"Not displayed frames become lastAppPresent."); + Assert::AreEqual(uint64_t(0), chain.lastDisplayedScreenTime); + Assert::AreEqual(uint64_t(0), chain.lastDisplayedFlipDelay); + } + + TEST_METHOD(ComputeMetricsForPresent_NotDisplayed_WithDisplaysButNotPresented_ProducesSingleMetricsAndUpdatesChain) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + // Simulate a frame with 'displayed' entries but finalState != Presented (treat as not displayed). + auto frame = MakeFrame(static_cast(9999), 1'000, 100, 1'200, + { { FrameType::Application, 2'000 } }); + + auto metrics = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), metrics.size()); + Assert::IsTrue(chain.lastPresent.has_value()); + Assert::IsTrue(chain.lastAppPresent.has_value()); + Assert::AreEqual(uint64_t(0), chain.lastDisplayedScreenTime, L"Not displayed path should not update displayed screen time."); + } + + TEST_METHOD(ComputeMetricsForPresent_DisplayedNoNext_SingleDisplay_PostponedChainNotUpdated) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + auto frame = MakeFrame(PresentResult::Presented, 5'000, 200, 5'500, + { { FrameType::Application, 6'000 } }); + + auto metrics = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + + Assert::AreEqual(size_t(0), metrics.size(), L"Single display is postponed => zero metrics now."); + Assert::IsFalse(chain.lastPresent.has_value(), L"Chain should NOT be updated yet."); + Assert::IsFalse(chain.lastAppPresent.has_value()); + } + + TEST_METHOD(ComputeMetricsForPresent_DisplayedNoNext_MultipleDisplays_ProcessesAllButLast) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + auto frame = MakeFrame(PresentResult::Presented, 10'000, 300, 10'800, + { + { FrameType::Application, 11'000 }, + { FrameType::Repeated, 11'500 }, + { FrameType::Repeated, 12'000 } // postponed + }); + + auto metrics = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + + Assert::AreEqual(size_t(2), metrics.size(), L"Should process all but last display."); + Assert::IsFalse(chain.lastPresent.has_value()); + Assert::IsFalse(chain.lastAppPresent.has_value()); + } + + TEST_METHOD(ComputeMetricsForPresent_DisplayedWithNext_ProcessesPostponedLastAndUpdatesChain) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + auto frame = MakeFrame(PresentResult::Presented, 10'000, 300, 10'800, + { + { FrameType::Application, 11'000 }, + { FrameType::Repeated, 11'500 }, + { FrameType::Repeated, 12'000 } + }, + 0, 0, 777); + + auto nextDisplayed = MakeFrame(PresentResult::Presented, 13'000, 250, 13'600, + { { FrameType::Application, 14'000 } }); + + // First call without nextDisplayed: postpone last + auto preMetrics = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(2), preMetrics.size()); + Assert::IsFalse(chain.lastPresent.has_value()); + + // Second call with nextDisplayed: process postponed last + update chain + auto postMetrics = ComputeMetricsForPresent(qpc, frame, &nextDisplayed, chain); + Assert::AreEqual(size_t(1), postMetrics.size(), L"Should process only the postponed last display this time."); + Assert::IsTrue(chain.lastPresent.has_value()); + Assert::AreEqual(uint64_t(12'000), chain.lastDisplayedScreenTime); + Assert::AreEqual(uint64_t(777), chain.lastDisplayedFlipDelay); + } + + TEST_METHOD(ComputeMetricsForPresent_DisplayedWithNext_LastDisplayIsRepeated_DoesNotUpdateLastAppPresent) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + // Previous app present for fallback usage. + FrameData prevApp = MakeFrame(PresentResult::Presented, 2'000, 100, 2'300, + { { FrameType::Application, 2'800 } }); + chain.lastAppPresent = prevApp; + + auto frame = MakeFrame(PresentResult::Presented, 4'000, 120, 4'300, + { + { FrameType::Application, 4'500 }, + { FrameType::Repeated, 4'900 } // last (Repeated) + }); + + auto nextDisplayed = MakeFrame(PresentResult::Presented, 5'000, 110, 5'250, + { { FrameType::Application, 5'600 } }); + + auto metrics = ComputeMetricsForPresent(qpc, frame, &nextDisplayed, chain); + Assert::AreEqual(size_t(1), metrics.size()); + + Assert::IsTrue(chain.lastPresent.has_value()); + // lastAppPresent should remain previous since last display was Repeated + Assert::IsTrue(chain.lastAppPresent.has_value()); + Assert::AreEqual(uint64_t(2'000), chain.lastAppPresent->presentStartTime); + } + }; + + TEST_CLASS(UpdateAfterPresentAnimationErrorSourceTests) + { + public: + TEST_METHOD(UpdateAfterPresent_AnimationSource_AppProvider_UpdatesSimStartAndFirstAppSim) + { + SwapChainCoreState chain{}; + chain.animationErrorSource = AnimationErrorSource::AppProvider; + + auto frame = MakeFrame(PresentResult::Presented, 1'000, 50, 1'200, + { { FrameType::Application, 1'500 } }, + 10'000 /* appSimStartTime */); + + chain.UpdateAfterPresent(frame); + + Assert::AreEqual(uint64_t(10'000), chain.lastDisplayedSimStartTime); + Assert::AreEqual(uint64_t(10'000), chain.firstAppSimStartTime); + Assert::AreEqual(uint64_t(1'500), chain.lastDisplayedAppScreenTime); + } + + TEST_METHOD(UpdateAfterPresent_AnimationSource_PCLatency_UpdatesSimStartAndFirstAppSim) + { + SwapChainCoreState chain{}; + chain.animationErrorSource = AnimationErrorSource::PCLatency; + + auto frame = MakeFrame(PresentResult::Presented, 2'000, 40, 2'300, + { { FrameType::Application, 2'700 } }, + 0 /* appSimStartTime */, 12'345 /* pclSimStart */); + + chain.UpdateAfterPresent(frame); + + Assert::AreEqual(uint64_t(12'345), chain.lastDisplayedSimStartTime); + Assert::AreEqual(uint64_t(12'345), chain.firstAppSimStartTime); + Assert::AreEqual(uint64_t(2'700), chain.lastDisplayedAppScreenTime); + } + + TEST_METHOD(UpdateAfterPresent_AnimationSource_CpuStart_FallbackToPreviousAppPresent) + { + SwapChainCoreState chain{}; + chain.animationErrorSource = AnimationErrorSource::CpuStart; + + FrameData previousApp = MakeFrame(PresentResult::Presented, 5'000, 80, 5'300, + { { FrameType::Application, 5'800 } }); + chain.lastAppPresent = previousApp; + + auto frame = MakeFrame(PresentResult::Presented, 6'000, 60, 6'250, + { { FrameType::Application, 6'700 } }, + 0, 0); + + chain.UpdateAfterPresent(frame); + + // No appSimStartTime or pclSimStartTime, fallback uses previous app present CPU end: + // 5'000 + 80 = 5'080 + Assert::AreEqual(uint64_t(5'080), chain.lastDisplayedSimStartTime); + Assert::AreEqual(uint64_t(0), chain.firstAppSimStartTime); // Not set yet + Assert::AreEqual(uint64_t(6'700), chain.lastDisplayedAppScreenTime); + } + + TEST_METHOD(UpdateAfterPresent_AnimationSource_CpuStart_TransitionsToAppProvider) + { + SwapChainCoreState chain{}; + chain.animationErrorSource = AnimationErrorSource::CpuStart; + + auto frame = MakeFrame(PresentResult::Presented, 7'000, 70, 7'400, + { { FrameType::Application, 7'900 } }, + 20'000 /* appSimStartTime */); + + chain.UpdateAfterPresent(frame); + + Assert::IsTrue(chain.animationErrorSource == AnimationErrorSource::AppProvider); + Assert::AreEqual(uint64_t(20'000), chain.lastDisplayedSimStartTime); + Assert::AreEqual(uint64_t(20'000), chain.firstAppSimStartTime); + } + + TEST_METHOD(UpdateAfterPresent_AnimationSource_CpuStart_TransitionsToPCLatency) + { + SwapChainCoreState chain{}; + chain.animationErrorSource = AnimationErrorSource::CpuStart; + + auto frame = MakeFrame(PresentResult::Presented, 8'000, 80, 8'400, + { { FrameType::Application, 8'950 } }, + 0 /* appSim */, 30'000 /* pclSim */); + + chain.UpdateAfterPresent(frame); + + Assert::IsTrue(chain.animationErrorSource == AnimationErrorSource::PCLatency); + Assert::AreEqual(uint64_t(30'000), chain.lastDisplayedSimStartTime); + Assert::AreEqual(uint64_t(30'000), chain.firstAppSimStartTime); + } + }; + + TEST_CLASS(UpdateAfterPresentFlipDelayTests) + { + public: + TEST_METHOD(UpdateAfterPresent_FlipDelayTracking_PresentedWithDisplays_SetsFlipDelayAndScreenTime) + { + SwapChainCoreState chain{}; + auto frame = MakeFrame(PresentResult::Presented, 10'000, 50, 10'300, + { + { FrameType::Application, 10'800 }, + { FrameType::Repeated, 11'000 } + }, + 0, 0, 1234 /* flipDelay */); + + chain.UpdateAfterPresent(frame); + + Assert::AreEqual(uint64_t(11'000), chain.lastDisplayedScreenTime); + Assert::AreEqual(uint64_t(1234), chain.lastDisplayedFlipDelay); + } + + TEST_METHOD(UpdateAfterPresent_FlipDelayTracking_PresentedNoDisplays_ZeroesFlipDelayAndScreenTime) + { + SwapChainCoreState chain{}; + auto frame = MakeFrame(PresentResult::Presented, 12'000, 40, 12'300, + {}, 0, 0, 9999); + + chain.UpdateAfterPresent(frame); + + Assert::AreEqual(uint64_t(0), chain.lastDisplayedScreenTime); + Assert::AreEqual(uint64_t(0), chain.lastDisplayedFlipDelay); + } + + TEST_METHOD(UpdateAfterPresent_NotPresented_DoesNotChangeLastDisplayedScreenTime) + { + SwapChainCoreState chain{}; + // Seed previous displayed state + FrameData prev = MakeFrame(PresentResult::Presented, 1'000, 30, 1'200, + { { FrameType::Application, 1'500 } }); + chain.UpdateAfterPresent(prev); + Assert::AreEqual(uint64_t(1'500), chain.lastDisplayedScreenTime); + + // Not presented frame with displays (ignored for displayed tracking) + auto frame = MakeFrame(static_cast(7777), 2'000, 25, 2'150, + { { FrameType::Application, 2'600 } }); + + chain.UpdateAfterPresent(frame); + + Assert::AreEqual(uint64_t(1'500), chain.lastDisplayedScreenTime, L"Should remain unchanged."); + } + }; + + // ============================================================================ + // ADDITIONAL TESTS: FrameType Intel_XEFG / AMD_AFMF and Displayed-Dropped-Displayed sequences + // ============================================================================ + + using namespace pmon::util::metrics; + + namespace MetricsCoreTests + { + // Small helper to build FrameData with displayed entries. + static FrameData BuildFrame( + PresentResult finalState, + uint64_t presentStart, + uint64_t timeInPresent, + const std::vector>& displayed, + uint64_t flipDelay = 0) + { + FrameData f{}; + f.presentStartTime = presentStart; + f.timeInPresent = timeInPresent; + f.readyTime = presentStart + timeInPresent; + f.displayed = displayed; + f.flipDelay = flipDelay; + f.finalState = finalState; + return f; + } + + TEST_CLASS(FrameTypeXefgAfmfIndexingTests) + { + public: + TEST_METHOD(DisplayIndexing_IntelXefg_Multi_NoNext_AppIndexIsLast) + { + // 3x Intel_XEFG then a single Application + FrameData present = BuildFrame( + PresentResult::Presented, + 10'000, 500, + { + { FrameType::Intel_XEFG, 11'000 }, + { FrameType::Intel_XEFG, 11'500 }, + { FrameType::Intel_XEFG, 12'000 }, + { FrameType::Application, 12'500 }, + }); + + auto idx = DisplayIndexing::Calculate(present, nullptr); + + // No nextDisplayed: process [0..N-2] => [0..3) + Assert::AreEqual(size_t(0), idx.startIndex); + Assert::AreEqual(size_t(3), idx.endIndex); + // App frame is at index 3 (outside processing range, postponed) + Assert::AreEqual(size_t(3), idx.appIndex); + Assert::IsFalse(idx.hasNextDisplayed); + } + + TEST_METHOD(DisplayIndexing_AmdAfmf_Multi_WithNext_AppIndexProcessed) + { + // 3x AMD_AFMF then a single Application + FrameData present = BuildFrame( + PresentResult::Presented, + 20'000, 600, + { + { FrameType::AMD_AFMF, 21'000 }, + { FrameType::AMD_AFMF, 21'500 }, + { FrameType::AMD_AFMF, 22'000 }, + { FrameType::Application, 22'500 }, + }); + + FrameData nextDisplayed = BuildFrame( + PresentResult::Presented, + 23'000, 400, + { { FrameType::Application, 24'000 } }); + + auto idx = DisplayIndexing::Calculate(present, &nextDisplayed); + + // With nextDisplayed: process postponed last only => [N-1, N) => [3, 4) + Assert::AreEqual(size_t(3), idx.startIndex); + Assert::AreEqual(size_t(4), idx.endIndex); + Assert::AreEqual(size_t(3), idx.appIndex); + Assert::IsTrue(idx.hasNextDisplayed); + } + }; + + TEST_CLASS(FrameTypeXefgAfmfMetricsTests) + { + public: + TEST_METHOD(ComputeMetricsForPresent_IntelXefg_NoNext_AppNotProcessed_ChainNotUpdated) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + // 3x Intel_XEFG then 1 Application; no nextDisplayed + FrameData present = BuildFrame( + PresentResult::Presented, + 30'000, 700, + { + { FrameType::Intel_XEFG, 31'000 }, + { FrameType::Intel_XEFG, 31'500 }, + { FrameType::Intel_XEFG, 32'000 }, + { FrameType::Application, 32'500 }, + }); + + auto metrics = ComputeMetricsForPresent(qpc, present, nullptr, chain); + + // Should process all but last => 3 metrics + Assert::AreEqual(size_t(3), metrics.size()); + // Chain update postponed until nextDisplayed + Assert::IsFalse(chain.lastPresent.has_value()); + Assert::IsFalse(chain.lastAppPresent.has_value()); + Assert::AreEqual(uint64_t(0), chain.lastDisplayedScreenTime); + Assert::AreEqual(uint64_t(0), chain.lastDisplayedFlipDelay); + } + + TEST_METHOD(ComputeMetricsForPresent_AmdAfmf_WithNext_AppProcessedAndUpdatesChain) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + // 3x AMD_AFMF then 1 Application; with nextDisplayed provided + FrameData present = BuildFrame( + PresentResult::Presented, + 40'000, 650, + { + { FrameType::AMD_AFMF, 41'000 }, + { FrameType::AMD_AFMF, 41'400 }, + { FrameType::AMD_AFMF, 41'800 }, + { FrameType::Application, 42'200 }, + }, + 999 /* flipDelay */); + + FrameData nextDisplayed = BuildFrame( + PresentResult::Presented, + 43'000, 500, + { { FrameType::Application, 44'000 } }); + + auto metrics = ComputeMetricsForPresent(qpc, present, &nextDisplayed, chain); + + // Should process only postponed last => 1 metrics + Assert::AreEqual(size_t(1), metrics.size()); + + // UpdateAfterPresent has run + Assert::IsTrue(chain.lastPresent.has_value()); + Assert::IsTrue(chain.lastAppPresent.has_value(), L"Last displayed is Application; lastAppPresent should be updated."); + Assert::AreEqual(uint64_t(42'200), chain.lastDisplayedScreenTime); + Assert::AreEqual(uint64_t(999), chain.lastDisplayedFlipDelay); + } + }; + + TEST_CLASS(DisplayedDroppedDisplayedSequenceTests) + { + public: + TEST_METHOD(Displayed_Dropped_Displayed_Sequence_IsHandledAcrossCalls) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + // A: displayed once, but no nextDisplayed yet => postponed + FrameData A = BuildFrame( + PresentResult::Presented, + 50'000, 400, + { { FrameType::Application, 51'000 } }); + + auto mA_pre = ComputeMetricsForPresent(qpc, A, nullptr, chain); + Assert::AreEqual(size_t(0), mA_pre.size(), L"Single display postponed."); + Assert::IsFalse(chain.lastPresent.has_value(), L"Chain is not updated without nextDisplayed."); + + // B: dropped (not presented/displayed) + FrameData B = BuildFrame( + PresentResult::Discarded, + 52'000, 300, + {} /* no displayed */); + + auto mB = ComputeMetricsForPresent(qpc, B, nullptr, chain); + Assert::AreEqual(size_t(1), mB.size(), L"Dropped frame goes through not-displayed path."); + Assert::IsTrue(chain.lastPresent.has_value(), L"Not-displayed path updates chain."); + Assert::IsTrue(chain.lastAppPresent.has_value(), L"Not-displayed frame becomes lastAppPresent."); + Assert::AreEqual(uint64_t(0), chain.lastDisplayedScreenTime, L"Not-displayed should leave lastDisplayedScreenTime at 0."); + + // C: displayed next; use it to process A's postponed last + FrameData C = BuildFrame( + PresentResult::Presented, + 53'000, 350, + { { FrameType::Application, 54'000 } }); + + auto mA_post = ComputeMetricsForPresent(qpc, A, &C, chain); + Assert::AreEqual(size_t(1), mA_post.size(), L"Postponed last display of A processed with nextDisplayed."); + + // Chain updated based on A (last display instance) + Assert::IsTrue(chain.lastPresent.has_value()); + Assert::AreEqual(uint64_t(51'000), chain.lastDisplayedScreenTime); + } + }; + } } \ No newline at end of file diff --git a/PresentMon/PresentMon.hpp b/PresentMon/PresentMon.hpp index 7759d9cf..1e386e63 100644 --- a/PresentMon/PresentMon.hpp +++ b/PresentMon/PresentMon.hpp @@ -29,7 +29,7 @@ which is controlled from MainThread based on user input or timer. #include "../PresentData/PresentMonTraceConsumer.hpp" #include "../PresentData/PresentMonTraceSession.hpp" -#include "../IntelPresentMon/CommonUtilities/mc/SwapChainCoreState.h" +#include "../IntelPresentMon/CommonUtilities/mc/SwapChainState.h" #include #include From e07a32d54dfaea0fc7a984aa486dc33a161f23f3 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Wed, 26 Nov 2025 09:02:25 -0800 Subject: [PATCH 008/205] Removed extraneous BuildFrame call and properly placed ULTs in correct ULT bucket --- IntelPresentMon/UnitTests/MetricsCore.cpp | 344 ++++++++++------------ 1 file changed, 158 insertions(+), 186 deletions(-) diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index 6455a744..48dcdfd1 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -1196,195 +1196,167 @@ namespace MetricsCoreTests Assert::AreEqual(uint64_t(1'500), chain.lastDisplayedScreenTime, L"Should remain unchanged."); } }; + TEST_CLASS(FrameTypeXefgAfmfIndexingTests) + { + public: + TEST_METHOD(DisplayIndexing_IntelXefg_Multi_NoNext_AppIndexIsLast) + { + // 3x Intel_XEFG then a single Application + FrameData present = MakeFrame( + PresentResult::Presented, + 10'000, 500, 20'000, + { + { FrameType::Intel_XEFG, 11'000 }, + { FrameType::Intel_XEFG, 11'500 }, + { FrameType::Intel_XEFG, 12'000 }, + { FrameType::Application, 12'500 }, + }); + + auto idx = DisplayIndexing::Calculate(present, nullptr); + + // No nextDisplayed: process [0..N-2] => [0..3) + Assert::AreEqual(size_t(0), idx.startIndex); + Assert::AreEqual(size_t(3), idx.endIndex); + // App frame is at index 3 (outside processing range, postponed) + Assert::AreEqual(size_t(3), idx.appIndex); + Assert::IsFalse(idx.hasNextDisplayed); + } + + TEST_METHOD(DisplayIndexing_AmdAfmf_Multi_WithNext_AppIndexProcessed) + { + // 3x AMD_AFMF then a single Application + FrameData present = MakeFrame( + PresentResult::Presented, + 20'000, 600, 30'000, + { + { FrameType::AMD_AFMF, 21'000 }, + { FrameType::AMD_AFMF, 21'500 }, + { FrameType::AMD_AFMF, 22'000 }, + { FrameType::Application, 22'500 }, + }); + + FrameData nextDisplayed = MakeFrame( + PresentResult::Presented, + 23'000, 400, 30'500, + { { FrameType::Application, 24'000 } }); + + auto idx = DisplayIndexing::Calculate(present, &nextDisplayed); + + // With nextDisplayed: process postponed last only => [N-1, N) => [3, 4) + Assert::AreEqual(size_t(3), idx.startIndex); + Assert::AreEqual(size_t(4), idx.endIndex); + Assert::AreEqual(size_t(3), idx.appIndex); + Assert::IsTrue(idx.hasNextDisplayed); + } + }; - // ============================================================================ - // ADDITIONAL TESTS: FrameType Intel_XEFG / AMD_AFMF and Displayed-Dropped-Displayed sequences - // ============================================================================ + TEST_CLASS(FrameTypeXefgAfmfMetricsTests) + { + public: + TEST_METHOD(ComputeMetricsForPresent_IntelXefg_NoNext_AppNotProcessed_ChainNotUpdated) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; - using namespace pmon::util::metrics; + // 3x Intel_XEFG then 1 Application; no nextDisplayed + FrameData present = MakeFrame( + PresentResult::Presented, + 30'000, 700, 40'000, + { + { FrameType::Intel_XEFG, 31'000 }, + { FrameType::Intel_XEFG, 31'500 }, + { FrameType::Intel_XEFG, 32'000 }, + { FrameType::Application, 32'500 }, + }); + + auto metrics = ComputeMetricsForPresent(qpc, present, nullptr, chain); + + // Should process all but last => 3 metrics + Assert::AreEqual(size_t(3), metrics.size()); + // Chain update postponed until nextDisplayed + Assert::IsFalse(chain.lastPresent.has_value()); + Assert::IsFalse(chain.lastAppPresent.has_value()); + Assert::AreEqual(uint64_t(0), chain.lastDisplayedScreenTime); + Assert::AreEqual(uint64_t(0), chain.lastDisplayedFlipDelay); + } - namespace MetricsCoreTests - { - // Small helper to build FrameData with displayed entries. - static FrameData BuildFrame( - PresentResult finalState, - uint64_t presentStart, - uint64_t timeInPresent, - const std::vector>& displayed, - uint64_t flipDelay = 0) - { - FrameData f{}; - f.presentStartTime = presentStart; - f.timeInPresent = timeInPresent; - f.readyTime = presentStart + timeInPresent; - f.displayed = displayed; - f.flipDelay = flipDelay; - f.finalState = finalState; - return f; - } - - TEST_CLASS(FrameTypeXefgAfmfIndexingTests) - { - public: - TEST_METHOD(DisplayIndexing_IntelXefg_Multi_NoNext_AppIndexIsLast) - { - // 3x Intel_XEFG then a single Application - FrameData present = BuildFrame( - PresentResult::Presented, - 10'000, 500, - { - { FrameType::Intel_XEFG, 11'000 }, - { FrameType::Intel_XEFG, 11'500 }, - { FrameType::Intel_XEFG, 12'000 }, - { FrameType::Application, 12'500 }, - }); - - auto idx = DisplayIndexing::Calculate(present, nullptr); - - // No nextDisplayed: process [0..N-2] => [0..3) - Assert::AreEqual(size_t(0), idx.startIndex); - Assert::AreEqual(size_t(3), idx.endIndex); - // App frame is at index 3 (outside processing range, postponed) - Assert::AreEqual(size_t(3), idx.appIndex); - Assert::IsFalse(idx.hasNextDisplayed); - } - - TEST_METHOD(DisplayIndexing_AmdAfmf_Multi_WithNext_AppIndexProcessed) - { - // 3x AMD_AFMF then a single Application - FrameData present = BuildFrame( - PresentResult::Presented, - 20'000, 600, - { - { FrameType::AMD_AFMF, 21'000 }, - { FrameType::AMD_AFMF, 21'500 }, - { FrameType::AMD_AFMF, 22'000 }, - { FrameType::Application, 22'500 }, - }); - - FrameData nextDisplayed = BuildFrame( - PresentResult::Presented, - 23'000, 400, - { { FrameType::Application, 24'000 } }); - - auto idx = DisplayIndexing::Calculate(present, &nextDisplayed); - - // With nextDisplayed: process postponed last only => [N-1, N) => [3, 4) - Assert::AreEqual(size_t(3), idx.startIndex); - Assert::AreEqual(size_t(4), idx.endIndex); - Assert::AreEqual(size_t(3), idx.appIndex); - Assert::IsTrue(idx.hasNextDisplayed); - } - }; + TEST_METHOD(ComputeMetricsForPresent_AmdAfmf_WithNext_AppProcessedAndUpdatesChain) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; - TEST_CLASS(FrameTypeXefgAfmfMetricsTests) - { - public: - TEST_METHOD(ComputeMetricsForPresent_IntelXefg_NoNext_AppNotProcessed_ChainNotUpdated) - { - QpcConverter qpc(10'000'000, 0); - SwapChainCoreState chain{}; - - // 3x Intel_XEFG then 1 Application; no nextDisplayed - FrameData present = BuildFrame( - PresentResult::Presented, - 30'000, 700, - { - { FrameType::Intel_XEFG, 31'000 }, - { FrameType::Intel_XEFG, 31'500 }, - { FrameType::Intel_XEFG, 32'000 }, - { FrameType::Application, 32'500 }, - }); - - auto metrics = ComputeMetricsForPresent(qpc, present, nullptr, chain); - - // Should process all but last => 3 metrics - Assert::AreEqual(size_t(3), metrics.size()); - // Chain update postponed until nextDisplayed - Assert::IsFalse(chain.lastPresent.has_value()); - Assert::IsFalse(chain.lastAppPresent.has_value()); - Assert::AreEqual(uint64_t(0), chain.lastDisplayedScreenTime); - Assert::AreEqual(uint64_t(0), chain.lastDisplayedFlipDelay); - } - - TEST_METHOD(ComputeMetricsForPresent_AmdAfmf_WithNext_AppProcessedAndUpdatesChain) - { - QpcConverter qpc(10'000'000, 0); - SwapChainCoreState chain{}; - - // 3x AMD_AFMF then 1 Application; with nextDisplayed provided - FrameData present = BuildFrame( - PresentResult::Presented, - 40'000, 650, - { - { FrameType::AMD_AFMF, 41'000 }, - { FrameType::AMD_AFMF, 41'400 }, - { FrameType::AMD_AFMF, 41'800 }, - { FrameType::Application, 42'200 }, - }, - 999 /* flipDelay */); - - FrameData nextDisplayed = BuildFrame( - PresentResult::Presented, - 43'000, 500, - { { FrameType::Application, 44'000 } }); - - auto metrics = ComputeMetricsForPresent(qpc, present, &nextDisplayed, chain); - - // Should process only postponed last => 1 metrics - Assert::AreEqual(size_t(1), metrics.size()); - - // UpdateAfterPresent has run - Assert::IsTrue(chain.lastPresent.has_value()); - Assert::IsTrue(chain.lastAppPresent.has_value(), L"Last displayed is Application; lastAppPresent should be updated."); - Assert::AreEqual(uint64_t(42'200), chain.lastDisplayedScreenTime); - Assert::AreEqual(uint64_t(999), chain.lastDisplayedFlipDelay); - } - }; + // 3x AMD_AFMF then 1 Application; with nextDisplayed provided + FrameData present = MakeFrame( + PresentResult::Presented, + 40'000, 650, 50'000, + { + { FrameType::AMD_AFMF, 41'000 }, + { FrameType::AMD_AFMF, 41'400 }, + { FrameType::AMD_AFMF, 41'800 }, + { FrameType::Application, 42'200 }, + }, + 39'500, /* appSimStartTime*/ + 0, /* pclSimStartTime*/ + 999 /* flipDelay*/); + + FrameData nextDisplayed = MakeFrame( + PresentResult::Presented, + 43'000, 500, 50'500, + { { FrameType::Application, 44'000 } }); + + auto metrics = ComputeMetricsForPresent(qpc, present, &nextDisplayed, chain); + + // Should process only postponed last => 1 metrics + Assert::AreEqual(size_t(1), metrics.size()); - TEST_CLASS(DisplayedDroppedDisplayedSequenceTests) - { - public: - TEST_METHOD(Displayed_Dropped_Displayed_Sequence_IsHandledAcrossCalls) - { - QpcConverter qpc(10'000'000, 0); - SwapChainCoreState chain{}; - - // A: displayed once, but no nextDisplayed yet => postponed - FrameData A = BuildFrame( - PresentResult::Presented, - 50'000, 400, - { { FrameType::Application, 51'000 } }); - - auto mA_pre = ComputeMetricsForPresent(qpc, A, nullptr, chain); - Assert::AreEqual(size_t(0), mA_pre.size(), L"Single display postponed."); - Assert::IsFalse(chain.lastPresent.has_value(), L"Chain is not updated without nextDisplayed."); - - // B: dropped (not presented/displayed) - FrameData B = BuildFrame( - PresentResult::Discarded, - 52'000, 300, - {} /* no displayed */); - - auto mB = ComputeMetricsForPresent(qpc, B, nullptr, chain); - Assert::AreEqual(size_t(1), mB.size(), L"Dropped frame goes through not-displayed path."); - Assert::IsTrue(chain.lastPresent.has_value(), L"Not-displayed path updates chain."); - Assert::IsTrue(chain.lastAppPresent.has_value(), L"Not-displayed frame becomes lastAppPresent."); - Assert::AreEqual(uint64_t(0), chain.lastDisplayedScreenTime, L"Not-displayed should leave lastDisplayedScreenTime at 0."); - - // C: displayed next; use it to process A's postponed last - FrameData C = BuildFrame( - PresentResult::Presented, - 53'000, 350, - { { FrameType::Application, 54'000 } }); - - auto mA_post = ComputeMetricsForPresent(qpc, A, &C, chain); - Assert::AreEqual(size_t(1), mA_post.size(), L"Postponed last display of A processed with nextDisplayed."); - - // Chain updated based on A (last display instance) - Assert::IsTrue(chain.lastPresent.has_value()); - Assert::AreEqual(uint64_t(51'000), chain.lastDisplayedScreenTime); - } - }; - } + // UpdateAfterPresent has run + Assert::IsTrue(chain.lastPresent.has_value()); + Assert::IsTrue(chain.lastAppPresent.has_value(), L"Last displayed is Application; lastAppPresent should be updated."); + Assert::AreEqual(uint64_t(42'200), chain.lastDisplayedScreenTime); + Assert::AreEqual(uint64_t(999), chain.lastDisplayedFlipDelay); + } + }; TEST_CLASS(DisplayedDroppedDisplayedSequenceTests) + { + public: + TEST_METHOD(Displayed_Dropped_Displayed_Sequence_IsHandledAcrossCalls) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + // A: displayed once, but no nextDisplayed yet => postponed + FrameData A = MakeFrame( + PresentResult::Presented, + 50'000, 400, 50'500, + { { FrameType::Application, 51'000 } }); + + auto mA_pre = ComputeMetricsForPresent(qpc, A, nullptr, chain); + Assert::AreEqual(size_t(0), mA_pre.size(), L"Single display postponed."); + Assert::IsFalse(chain.lastPresent.has_value(), L"Chain is not updated without nextDisplayed."); + + // B: dropped (not presented/displayed) + FrameData B = MakeFrame( + PresentResult::Discarded, + 52'000, 300, 52'400, + {} /* no displayed */); + + auto mB = ComputeMetricsForPresent(qpc, B, nullptr, chain); + Assert::AreEqual(size_t(1), mB.size(), L"Dropped frame goes through not-displayed path."); + Assert::IsTrue(chain.lastPresent.has_value(), L"Not-displayed path updates chain."); + Assert::IsTrue(chain.lastAppPresent.has_value(), L"Not-displayed frame becomes lastAppPresent."); + Assert::AreEqual(uint64_t(0), chain.lastDisplayedScreenTime, L"Not-displayed should leave lastDisplayedScreenTime at 0."); + + // C: displayed next; use it to process A's postponed last + FrameData C = MakeFrame( + PresentResult::Presented, + 53'000, 350, 53'400, + { { FrameType::Application, 54'000 } }); + + auto mA_post = ComputeMetricsForPresent(qpc, A, &C, chain); + Assert::AreEqual(size_t(1), mA_post.size(), L"Postponed last display of A processed with nextDisplayed."); + + // Chain updated based on A (last display instance) + Assert::IsTrue(chain.lastPresent.has_value()); + Assert::AreEqual(uint64_t(51'000), chain.lastDisplayedScreenTime); + } + }; } \ No newline at end of file From 5fe152b771947bbbd480f22bef7c8fd676fbba17 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Mon, 1 Dec 2025 10:02:41 -0800 Subject: [PATCH 009/205] Adding in support for msInPresentApi and ULTs --- .../CommonUtilities/mc/MetricsCalculator.cpp | 32 ++ IntelPresentMon/UnitTests/MetricsCore.cpp | 289 ++++++++++++++++++ 2 files changed, 321 insertions(+) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index d0e6da48..576a136b 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -141,6 +141,30 @@ namespace pmon::util::metrics return results; } + void CalculateBasePresentMetrics( + const QpcConverter& qpc, + const FrameData& present, + const SwapChainCoreState& swapChain, + FrameMetrics& out) + { + out.timeInSeconds = present.presentStartTime; + + // Calculate the delta from the previous present (if one exists) + // to the current present + if (swapChain.lastPresent.has_value()) { + out.msBetweenPresents = qpc.DeltaUnsignedMilliSeconds( + swapChain.lastPresent->getPresentStartTime(), + present.getPresentStartTime()); + } else { + out.msBetweenPresents = 0.0; + } + + out.msInPresentApi = qpc.DurationMilliSeconds(present.getTimeInPresent()); + out.msUntilRenderComplete = qpc.DeltaSignedMilliSeconds( + present.getPresentStartTime(), + present.getReadyTime()); + } + ComputedMetrics ComputeFrameMetrics( const QpcConverter& qpc, const FrameData& present, @@ -154,6 +178,14 @@ namespace pmon::util::metrics ComputedMetrics result{}; FrameMetrics& metrics = result.metrics; + CalculateBasePresentMetrics( + qpc, + present, + chain, + metrics); + + metrics.cpuStartQpc = CalculateCPUStart(chain, present); + return result; } diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index 48dcdfd1..2d2058cd 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -1359,4 +1359,293 @@ namespace MetricsCoreTests Assert::AreEqual(uint64_t(51'000), chain.lastDisplayedScreenTime); } }; + + TEST_CLASS(MetricsValueTests) + { + TEST_METHOD(ComputeMetricsForPresent_NotDisplayed_msBetweenPresents_UsesLastPresentDelta) + { + // 10MHz QPC frequency + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + // First frame: not displayed path (Presented but no Displayed entries) + auto first = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, + /*timeInPresent*/ 10'000, + /*readyTime*/ 1'020'000, + /*displayed*/{}); // no displayed frames => not-displayed path + + auto firstMetrics = ComputeMetricsForPresent(qpc, first, nullptr, chain); + + // We should get exactly one metrics entry + Assert::AreEqual(size_t(1), firstMetrics.size(), L"First not-displayed frame should produce one metrics entry."); + + // With no prior lastPresent, msBetweenPresents should be zero + Assert::AreEqual( + 0.0, + firstMetrics[0].metrics.msBetweenPresents, + 0.0001, + L"First frame should have msBetweenPresents == 0."); + + // Chain should now treat this as lastPresent / lastAppPresent + Assert::IsTrue(chain.lastPresent.has_value()); + if (!chain.lastPresent.has_value()) + { + Assert::Fail(L"lastPresent was unexpectedly empty."); + return; + } + const auto& last = chain.lastPresent.value(); + Assert::AreEqual(uint64_t(1'000'000), last.presentStartTime); + + // Second frame: also not displayed, later in time + auto second = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'016'660, // ~16.666 ms later at 10MHz + /*timeInPresent*/ 10'000, + /*readyTime*/ 1'036'660, + /*displayed*/{}); + + auto secondMetrics = ComputeMetricsForPresent(qpc, second, nullptr, chain); + + Assert::AreEqual( + size_t(1), + secondMetrics.size(), + L"Second not-displayed frame should also produce one metrics entry."); + + // Expected delta: use the same converter the implementation uses + double expectedDelta = qpc.DeltaUnsignedMilliSeconds( + first.presentStartTime, + second.presentStartTime); + + Assert::AreEqual( + expectedDelta, + secondMetrics[0].metrics.msBetweenPresents, + 0.0001, + L"msBetweenPresents should equal the unsigned delta between lastPresent and current presentStartTime."); + } + TEST_METHOD(ComputeMetricsForPresent_NotDisplayed_BaseTimingAndCpuStart_AreCorrect) + { + // 10 MHz QPC: 10,000,000 ticks per second + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + // First frame: not displayed, becomes the baseline lastPresent/lastAppPresent. + FrameData first = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, // 0.1s + /*timeInPresent*/ 200'000, // 0.02s + /*readyTime*/ 1'500'000, // 0.15s → 50 ms after start + /*displayed*/{} // no displays => "not displayed" path + ); + + auto firstMetricsList = ComputeMetricsForPresent(qpc, first, nullptr, chain); + Assert::AreEqual( + size_t(1), + firstMetricsList.size(), + L"First not-displayed frame should produce one metrics entry."); + + const auto& firstMetrics = firstMetricsList[0].metrics; + + uint64_t expectedTimeInSecondsFirst = first.presentStartTime; + Assert::AreEqual( + expectedTimeInSecondsFirst, + firstMetrics.timeInSeconds, + L"timeInSeconds should come from QpcToSeconds(presentStartTime)."); + + // No prior lastPresent → msBetweenPresents should be 0 + Assert::AreEqual( + 0.0, + firstMetrics.msBetweenPresents, + 0.0001, + L"First frame should have msBetweenPresents == 0."); + + // msInPresentApi = delta for TimeInPresent + double expectedMsInPresentFirst = qpc.DurationMilliSeconds(first.timeInPresent); + Assert::AreEqual( + expectedMsInPresentFirst, + firstMetrics.msInPresentApi, + 0.0001, + L"msInPresentApi should equal QpcDeltaToMilliSeconds(timeInPresent)."); + + // msUntilRenderComplete = delta between PresentStart and Ready + double expectedMsUntilRenderCompleteFirst = + qpc.DeltaUnsignedMilliSeconds(first.presentStartTime, first.readyTime); + Assert::AreEqual( + expectedMsUntilRenderCompleteFirst, + firstMetrics.msUntilRenderComplete, + 0.0001, + L"msUntilRenderComplete should equal delta from PresentStartTime to ReadyTime."); + + // With no prior present, CalculateCPUStart should return 0 → cpuStartQpc == 0 + Assert::AreEqual( + uint64_t(0), + firstMetrics.cpuStartQpc, + L"First frame with no history should have cpuStartQpc == 0."); + + // Chain must now have lastPresent/lastAppPresent set to 'first' + Assert::IsTrue(chain.lastPresent.has_value(), L"Expected lastPresent to be set."); + if (!chain.lastPresent.has_value()) { + Assert::Fail(L"lastPresent was unexpectedly empty."); + return; + } + const auto& lastAfterFirst = chain.lastPresent.value(); + Assert::AreEqual(first.presentStartTime, lastAfterFirst.presentStartTime); + + // ------------------------------------------------------------------------- + // Second frame: also not displayed, later in time. + // This should: + // - compute msBetweenPresents based on first→second start times + // - keep msInPresentApi/msUntilRenderComplete consistent + // - use CalculateCPUStart based on 'first' as lastAppPresent + // ------------------------------------------------------------------------- + + FrameData second = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'016'000, // slightly later than first + /*timeInPresent*/ 300'000, // 0.03s + /*readyTime*/ 1'516'000, // 0.5s after first start + /*displayed*/{} // still "not displayed" path + ); + + auto secondMetricsList = ComputeMetricsForPresent(qpc, second, nullptr, chain); + Assert::AreEqual( + size_t(1), + secondMetricsList.size(), + L"Second not-displayed frame should produce one metrics entry."); + + const auto& secondMetrics = secondMetricsList[0].metrics; + + // msBetweenPresents should be based on lastPresent.start -> second.start + double expectedBetween = + qpc.DeltaUnsignedMilliSeconds(first.presentStartTime, second.presentStartTime); + Assert::AreEqual( + expectedBetween, + secondMetrics.msBetweenPresents, + 0.0001, + L"msBetweenPresents should equal delta between lastPresent and current presentStart."); + + // msInPresentApi / msUntilRenderComplete for second + double expectedMsInPresentSecond = qpc.DurationMilliSeconds(second.timeInPresent); + double expectedMsUntilRenderCompleteSecond = + qpc.DeltaUnsignedMilliSeconds(second.presentStartTime, second.readyTime); + + Assert::AreEqual( + expectedMsInPresentSecond, + secondMetrics.msInPresentApi, + 0.0001, + L"Second frame msInPresentApi should match timeInPresent."); + Assert::AreEqual( + expectedMsUntilRenderCompleteSecond, + secondMetrics.msUntilRenderComplete, + 0.0001, + L"Second frame msUntilRenderComplete should match start→ready delta."); + + // cpuStartQpc for second should come from CalculateCPUStart: + // lastAppPresent == first (no propagated times) → first.start + first.timeInPresent + uint64_t expectedCpuStartSecond = first.presentStartTime + first.timeInPresent; + Assert::AreEqual( + expectedCpuStartSecond, + secondMetrics.cpuStartQpc, + L"cpuStartQpc should match CalculateCPUStart from lastAppPresent."); + } + TEST_METHOD(ComputeMetricsForPresent_DisplayedWithNext_BaseTimingAndCpuStart_AreCorrect) + { + // 10 MHz QPC + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + // Baseline frame: Presented but not displayed → not-displayed path + FrameData first = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, + /*timeInPresent*/ 200'000, + /*readyTime*/ 1'500'000, + /*displayed*/{}); // no displays + + auto firstMetricsList = ComputeMetricsForPresent(qpc, first, nullptr, chain); + Assert::AreEqual( + size_t(1), + firstMetricsList.size(), + L"Baseline not-displayed frame should produce one metrics entry."); + + // Chain should now have lastPresent/lastAppPresent == first + Assert::IsTrue(chain.lastPresent.has_value(), L"Expected lastPresent to be set after baseline frame."); + if (!chain.lastPresent.has_value()) { + Assert::Fail(L"lastPresent was unexpectedly empty after baseline frame."); + return; + } + + // Second frame: Presented + one displayed instance, processed with a nextDisplayed + FrameData second = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'016'000, // slightly later than first + /*timeInPresent*/ 300'000, + /*readyTime*/ 1'616'000, + std::vector>{ + { FrameType::Application, 2'000'000 } // one displayed instance + }); + + // Dummy nextDisplayed with at least one display so the "with next" path is taken + FrameData nextDisplayed = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 2'100'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 2'200'000, + std::vector>{ + { FrameType::Application, 2'300'000 } + }); + + auto secondMetricsList = ComputeMetricsForPresent(qpc, second, &nextDisplayed, chain); + + Assert::AreEqual( + size_t(1), + secondMetricsList.size(), + L"Displayed-with-next frame should produce one metrics entry (postponed last display)."); + + const auto& secondMetrics = secondMetricsList[0].metrics; + + // timeInSeconds from presentStartTime + auto expectedTimeInSecondsSecond = second.presentStartTime; + Assert::AreEqual( + expectedTimeInSecondsSecond, + secondMetrics.timeInSeconds, + L"timeInSeconds should match QpcToSeconds(presentStartTime) for displayed frame."); + + // msBetweenPresents: lastPresent.start (first) → second.start + double expectedBetween = + qpc.DeltaUnsignedMilliSeconds(first.presentStartTime, second.presentStartTime); + Assert::AreEqual( + expectedBetween, + secondMetrics.msBetweenPresents, + 0.0001, + L"msBetweenPresents should match delta between lastPresent and current presentStart for displayed frame."); + + // msInPresentApi from timeInPresent + double expectedMsInPresentSecond = qpc.DurationMilliSeconds(second.timeInPresent); + Assert::AreEqual( + expectedMsInPresentSecond, + secondMetrics.msInPresentApi, + 0.0001, + L"msInPresentApi should match QpcDeltaToMilliSeconds(timeInPresent) for displayed frame."); + + // msUntilRenderComplete from start → ready + double expectedMsUntilRenderCompleteSecond = + qpc.DeltaUnsignedMilliSeconds(second.presentStartTime, second.readyTime); + Assert::AreEqual( + expectedMsUntilRenderCompleteSecond, + secondMetrics.msUntilRenderComplete, + 0.0001, + L"msUntilRenderComplete should match start→ready delta for displayed frame."); + + // cpuStartQpc should come from CalculateCPUStart using baseline frame as lastAppPresent: + // (no propagated times) → first.start + first.timeInPresent + uint64_t expectedCpuStartSecond = first.presentStartTime + first.timeInPresent; + Assert::AreEqual( + expectedCpuStartSecond, + secondMetrics.cpuStartQpc, + L"cpuStartQpc for displayed frame should match CalculateCPUStart based on lastAppPresent."); + } + + }; } \ No newline at end of file From ebeaffa51dc88517e0452fc945e3e140a506136b Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Mon, 1 Dec 2025 12:30:01 -0800 Subject: [PATCH 010/205] Added base display metrics and ULTs --- .../CommonUtilities/mc/MetricsCalculator.cpp | 103 ++++ IntelPresentMon/UnitTests/MetricsCore.cpp | 452 +++++++++++++++++- 2 files changed, 554 insertions(+), 1 deletion(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 576a136b..2ec3495a 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -7,6 +7,63 @@ namespace pmon::util::metrics { + namespace + { + // Helper dedicated to computing msUntilDisplayed, matching legacy ReportMetricsHelper behavior. + void ComputeMsUntilDisplayed( + const QpcConverter& qpc, + const FrameData& present, + bool isDisplayed, + uint64_t screenTime, + FrameMetrics& out) + { + if (isDisplayed) { + out.msUntilDisplayed = qpc.DeltaUnsignedMilliSeconds( + present.getPresentStartTime(), + screenTime); + } + else { + out.msUntilDisplayed = 0.0; + } + } + + // Helper dedicated to computing msBetweenDisplayChange, matching legacy ReportMetricsHelper behavior. + void ComputeMsBetweenDisplayChange( + const QpcConverter& qpc, + const SwapChainCoreState& chain, + bool isDisplayed, + uint64_t screenTime, + FrameMetrics& out) + { + if (isDisplayed) { + out.msBetweenDisplayChange = qpc.DeltaUnsignedMilliSeconds( + chain.lastDisplayedScreenTime, + screenTime); + } + else { + out.msBetweenDisplayChange = 0.0; + } + } + + // Helper dedicated to computing msDisplayedTime, matching legacy ReportMetricsHelper behavior. + void ComputeMsDisplayedTime( + const QpcConverter& qpc, + bool isDisplayed, + uint64_t screenTime, + uint64_t nextScreenTime, + FrameMetrics& out) + { + if (isDisplayed) { + out.msDisplayedTime = qpc.DeltaUnsignedMilliSeconds( + screenTime, + nextScreenTime); + } + else { + out.msDisplayedTime = 0.0; + } + } + } + DisplayIndexing DisplayIndexing::Calculate( const FrameData& present, const FrameData* nextDisplayed) @@ -165,6 +222,43 @@ namespace pmon::util::metrics present.getReadyTime()); } + void CalculateDisplayMetrics( + const QpcConverter& qpc, + const FrameData& present, + const SwapChainCoreState& swapChain, + bool isDisplayed, + uint64_t screenTime, + uint64_t nextScreenTime, + FrameMetrics& metrics) { + + // msUntilDisplayed depends only on whether this display instance is displayed and its screen time + ComputeMsUntilDisplayed( + qpc, + present, + isDisplayed, + screenTime, + metrics); + + // msBetweenDisplayChange depends on previous displayed screen time and the current screen time + ComputeMsBetweenDisplayChange( + qpc, + swapChain, + isDisplayed, + screenTime, + metrics); + + // msDisplayedTime depends on the current screen time and the next screen time + ComputeMsDisplayedTime( + qpc, + isDisplayed, + screenTime, + nextScreenTime, + metrics); + + // screenTimeQpc is simply the current screen time + metrics.screenTimeQpc = screenTime; + } + ComputedMetrics ComputeFrameMetrics( const QpcConverter& qpc, const FrameData& present, @@ -184,6 +278,15 @@ namespace pmon::util::metrics chain, metrics); + CalculateDisplayMetrics( + qpc, + present, + chain, + isDisplayed, + screenTime, + nextScreenTime, + metrics); + metrics.cpuStartQpc = CalculateCPUStart(chain, present); return result; diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index 2d2058cd..517faf6a 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -1315,7 +1315,8 @@ namespace MetricsCoreTests Assert::AreEqual(uint64_t(42'200), chain.lastDisplayedScreenTime); Assert::AreEqual(uint64_t(999), chain.lastDisplayedFlipDelay); } - }; TEST_CLASS(DisplayedDroppedDisplayedSequenceTests) + }; + TEST_CLASS(DisplayedDroppedDisplayedSequenceTests) { public: TEST_METHOD(Displayed_Dropped_Displayed_Sequence_IsHandledAcrossCalls) @@ -1646,6 +1647,455 @@ namespace MetricsCoreTests secondMetrics.cpuStartQpc, L"cpuStartQpc for displayed frame should match CalculateCPUStart based on lastAppPresent."); } + }; + TEST_CLASS(MsUntilDisplayedTests) { + public: + TEST_METHOD(NotDisplayed_ReturnsZero) + { + QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; + // Not displayed: Presented but no displayed entries + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 10'000; + frame.readyTime = 1'010'000; + frame.setFinalState(PresentResult::Presented); + // No displayed entries + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + Assert::AreEqual(0.0, m.msUntilDisplayed, 0.0001); + } + TEST_METHOD(Displayed_ReturnsDeltaFromPresentStartToScreenTime) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 2'000'000; // start + frame.timeInPresent = 20'000; + frame.readyTime = 2'050'000; + frame.setFinalState(PresentResult::Presented); + // Single displayed; will be postponed unless nextDisplayed provided + frame.displayed.push_back({ FrameType::Application, 2'500'000 }); + + FrameData next{}; // provide nextDisplayed to process postponed + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 3'000'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + double expected = qpc.DeltaUnsignedMilliSeconds(frame.getPresentStartTime(), frame.getDisplayedScreenTime(0)); + Assert::AreEqual(expected, m.msUntilDisplayed, 0.0001); + } + TEST_METHOD(DisplayedGeneratedFrame_AlsoReturnsDelta) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 5'000'000; + frame.timeInPresent = 15'000; + frame.readyTime = 5'030'000; + frame.setFinalState(PresentResult::Presented); + // Displayed generated frame (e.g., Repeated/Composed/Desktop depending on enum) + frame.displayed.push_back({ FrameType::Intel_XEFG, 5'100'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 6'000'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + double expected = qpc.DeltaUnsignedMilliSeconds(frame.getPresentStartTime(), frame.getDisplayedScreenTime(0)); + Assert::AreEqual(expected, m.msUntilDisplayed, 0.0001); + } + }; + TEST_CLASS(MsDisplayedTimeTests) + { + public: + TEST_METHOD(NotDisplayed_ReturnsZero) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 10'000; + frame.readyTime = 1'010'000; + frame.setFinalState(PresentResult::Presented); + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + Assert::AreEqual(0.0, m.msDisplayedTime, 0.0001); + } + + TEST_METHOD(DisplayedSingleDisplay_WithNextDisplay_ReturnsDeltaToNextScreenTime) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 2'000'000; + frame.timeInPresent = 20'000; + frame.readyTime = 2'050'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 2'500'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 2'800'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + double expected = qpc.DeltaUnsignedMilliSeconds(2'500'000, 2'800'000); + Assert::AreEqual(expected, m.msDisplayedTime, 0.0001); + } + + TEST_METHOD(DisplayedMultipleDisplays_ProcessEachWithNextScreenTime) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 3'000'000; + frame.timeInPresent = 30'000; + frame.readyTime = 3'050'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 3'100'000 }); + frame.displayed.push_back({ FrameType::Repeated, 3'400'000 }); + frame.displayed.push_back({ FrameType::Repeated, 3'700'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 4'000'000 }); + + auto results1 = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(2), results1.size()); + + double expected0 = qpc.DeltaUnsignedMilliSeconds(3'100'000, 3'400'000); + Assert::AreEqual(expected0, results1[0].metrics.msDisplayedTime, 0.0001); + + double expected1 = qpc.DeltaUnsignedMilliSeconds(3'400'000, 3'700'000); + Assert::AreEqual(expected1, results1[1].metrics.msDisplayedTime, 0.0001); + + auto results2 = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results2.size()); + + double expected2 = qpc.DeltaUnsignedMilliSeconds(3'700'000, 4'000'000); + Assert::AreEqual(expected2, results2[0].metrics.msDisplayedTime, 0.0001); + } + }; + + TEST_CLASS(MsBetweenDisplayChangeTests) + { + public: + TEST_METHOD(FirstDisplayedFrame_NoChainHistory_ReturnsZero) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 5'000'000; + frame.timeInPresent = 50'000; + frame.readyTime = 5'100'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 5'500'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 6'000'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + Assert::AreEqual(0.0, m.msBetweenDisplayChange, 0.0001); + } + + TEST_METHOD(SubsequentDisplayedFrame_UsesChainLastDisplayedScreenTime) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + chain.lastDisplayedScreenTime = 4'000'000; + + FrameData frame{}; + frame.presentStartTime = 5'000'000; + frame.timeInPresent = 50'000; + frame.readyTime = 5'100'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 5'500'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 6'000'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + double expected = qpc.DeltaUnsignedMilliSeconds(4'000'000, 5'500'000); + Assert::AreEqual(expected, m.msBetweenDisplayChange, 0.0001); + } + + TEST_METHOD(NotDisplayed_ReturnsZero) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + chain.lastDisplayedScreenTime = 4'000'000; + + FrameData frame{}; + frame.presentStartTime = 5'000'000; + frame.timeInPresent = 50'000; + frame.readyTime = 5'100'000; + frame.setFinalState(PresentResult::Presented); + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + Assert::AreEqual(0.0, m.msBetweenDisplayChange, 0.0001); + } + + TEST_METHOD(MultipleDisplays_EachComputesDeltaFromPrior) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + chain.lastDisplayedScreenTime = 3'000'000; + + FrameData frame{}; + frame.presentStartTime = 5'000'000; + frame.timeInPresent = 50'000; + frame.readyTime = 5'100'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 5'500'000 }); + frame.displayed.push_back({ FrameType::Repeated, 5'800'000 }); + frame.displayed.push_back({ FrameType::Repeated, 6'100'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 6'400'000 }); + + auto results1 = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(2), results1.size()); + + double expected0 = qpc.DeltaUnsignedMilliSeconds(3'000'000, 5'500'000); + Assert::AreEqual(expected0, results1[0].metrics.msBetweenDisplayChange, 0.0001); + + double expected1 = qpc.DeltaUnsignedMilliSeconds(3'000'000, 5'800'000); + Assert::AreEqual(expected1, results1[1].metrics.msBetweenDisplayChange, 0.0001); + + auto results2 = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results2.size()); + + double expected2 = qpc.DeltaUnsignedMilliSeconds(3'000'000, 6'100'000); + Assert::AreEqual(expected2, results2[0].metrics.msBetweenDisplayChange, 0.0001); + } + }; + + TEST_CLASS(MsFlipDelayTests) + { + public: + TEST_METHOD(NotDisplayed_ReturnsZero) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 7'000'000; + frame.timeInPresent = 70'000; + frame.readyTime = 7'100'000; + frame.flipDelay = 5'000; + frame.setFinalState(PresentResult::Presented); + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + if (m.msFlipDelay.has_value()) { + Assert::AreEqual(0.0, m.msFlipDelay.value(), 0.0001); + } + } + + TEST_METHOD(Displayed_WithFlipDelay_ReturnsFlipDelayInMs) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 7'000'000; + frame.timeInPresent = 70'000; + frame.readyTime = 7'100'000; + frame.flipDelay = 100'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 7'500'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 8'000'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + if (m.msFlipDelay.has_value()) { + double expected = qpc.DurationMilliSeconds(100'000); + Assert::AreEqual(expected, m.msFlipDelay.value(), 0.0001); + } + } + + TEST_METHOD(Displayed_WithoutFlipDelay_ReturnsZero) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 7'000'000; + frame.timeInPresent = 70'000; + frame.readyTime = 7'100'000; + frame.flipDelay = 0; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 7'500'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 8'000'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + if (m.msFlipDelay.has_value()) { + Assert::AreEqual(0.0, m.msFlipDelay.value(), 0.0001); + } + } + + TEST_METHOD(DisplayedWithGeneratedFrame_AlsoIncludesFlipDelay) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 7'000'000; + frame.timeInPresent = 70'000; + frame.readyTime = 7'100'000; + frame.flipDelay = 50'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Repeated, 7'500'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 8'000'000 }); + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + if (m.msFlipDelay.has_value()) { + double expected = qpc.DurationMilliSeconds(50'000); + Assert::AreEqual(expected, m.msFlipDelay.value(), 0.0001); + } + } + }; + + TEST_CLASS(ScreenTimeQpcTests) + { + public: + TEST_METHOD(NotDisplayed_ReturnsZero) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 9'000'000; + frame.timeInPresent = 90'000; + frame.readyTime = 9'100'000; + frame.setFinalState(PresentResult::Presented); + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + Assert::AreEqual(uint64_t(0), m.screenTimeQpc); + } + + TEST_METHOD(DisplayedSingleFrame_EqualsScreenTime) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 9'000'000; + frame.timeInPresent = 90'000; + frame.readyTime = 9'100'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 9'500'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 10'000'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + Assert::AreEqual(uint64_t(9'500'000), m.screenTimeQpc); + } + + TEST_METHOD(DisplayedMultipleFrames_EachHasOwnScreenTime) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 9'000'000; + frame.timeInPresent = 90'000; + frame.readyTime = 9'100'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 9'500'000 }); + frame.displayed.push_back({ FrameType::Repeated, 9'800'000 }); + frame.displayed.push_back({ FrameType::Repeated, 10'100'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 10'400'000 }); + + auto results1 = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(2), results1.size()); + Assert::AreEqual(uint64_t(9'500'000), results1[0].metrics.screenTimeQpc); + Assert::AreEqual(uint64_t(9'800'000), results1[1].metrics.screenTimeQpc); + + auto results2 = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results2.size()); + Assert::AreEqual(uint64_t(10'100'000), results2[0].metrics.screenTimeQpc); + } + + TEST_METHOD(DisplayedGeneratedFrame_EqualsGeneratedFrameScreenTime) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 9'000'000; + frame.timeInPresent = 90'000; + frame.readyTime = 9'100'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Repeated, 9'700'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 10'000'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + Assert::AreEqual(uint64_t(9'700'000), m.screenTimeQpc); + } }; } \ No newline at end of file From d83a3e997e52cc49323e067fe5682e74d253ca9b Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Mon, 1 Dec 2025 17:38:41 -0800 Subject: [PATCH 011/205] Adding FlipDelay support and ULTs. Still WIP. --- .../CommonUtilities/mc/MetricsCalculator.cpp | 45 +++- .../CommonUtilities/mc/MetricsCalculator.h | 2 +- .../CommonUtilities/mc/MetricsTypes.cpp | 4 +- .../CommonUtilities/mc/MetricsTypes.h | 4 +- IntelPresentMon/UnitTests/MetricsCore.cpp | 235 ++++++++++++++++-- 5 files changed, 262 insertions(+), 28 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 2ec3495a..9fa233ce 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -62,6 +62,40 @@ namespace pmon::util::metrics out.msDisplayedTime = 0.0; } } + + void ComputeMsFlipDelay( + const QpcConverter& qpc, + const FrameData& present, + bool isDisplayed, + FrameMetrics& out) + { + if (isDisplayed || present.getFlipDelay() != 0) { + out.msFlipDelay = + qpc.DurationMilliSeconds(present.getFlipDelay()); + } + else { + out.msFlipDelay = 0.0; + } + } + + void AdjustScreenTimeForCollapsedPresentNV( + const FrameData& present, + FrameData* nextDisplayedPresent, + uint64_t& screenTime, + uint64_t& nextScreenTime) + { + // nextDisplayedPresent should always be non-null for NV GPU. + if (present.flipDelay && screenTime > nextScreenTime && nextDisplayedPresent) { + // If screenTime that is adjusted by flipDelay is larger than nextScreenTime, + // it implies this present is a collapsed present, or a runt frame. + // So we adjust the screenTime and flipDelay of nextDisplayedPresent, + // effectively making nextScreenTime equals to screenTime. + + nextDisplayedPresent->flipDelay += (screenTime - nextScreenTime); + nextScreenTime = screenTime; + nextDisplayedPresent->displayed[0].second = nextScreenTime; + } + } } DisplayIndexing DisplayIndexing::Calculate( @@ -121,7 +155,7 @@ namespace pmon::util::metrics std::vector ComputeMetricsForPresent( const QpcConverter& qpc, const FrameData& present, - const FrameData* nextDisplayed, + FrameData* nextDisplayed, SwapChainCoreState& chainState) { std::vector results; @@ -175,6 +209,8 @@ namespace pmon::util::metrics break; // No next screen time available } + AdjustScreenTimeForCollapsedPresentNV(present, nextDisplayed, screenTime, nextScreenTime); + const bool isAppFrame = (displayIndex == indexing.appIndex); auto metrics = ComputeFrameMetrics( @@ -255,6 +291,13 @@ namespace pmon::util::metrics nextScreenTime, metrics); + // msFlipDelay depends if the current present has a flip delay + ComputeMsFlipDelay( + qpc, + present, + isDisplayed, + metrics); + // screenTimeQpc is simply the current screen time metrics.screenTimeQpc = screenTime; } diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h index 9ddd5c9f..26b2e320 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h @@ -42,7 +42,7 @@ namespace pmon::util::metrics std::vector ComputeMetricsForPresent( const QpcConverter& qpc, const FrameData& present, - const FrameData* nextDisplayed, + FrameData* nextDisplayed, SwapChainCoreState& chainState); // === Pure Calculation Functions === diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp index c326b91a..e8243243 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp @@ -40,7 +40,7 @@ namespace pmon::util::metrics { frame.pclSimStartTime = p.PclSimStartTime; frame.pclInputPingTime = p.PclInputPingTime; frame.flipDelay = p.FlipDelay; - frame.FlipToken = p.FlipToken; + frame.flipToken = p.FlipToken; // Normalize parallel arrays to vector frame.displayed.reserve(p.DisplayedCount); @@ -94,7 +94,7 @@ namespace pmon::util::metrics { frame.pclSimStartTime = p->PclSimStartTime; frame.pclInputPingTime = p->PclInputPingTime; frame.flipDelay = p->FlipDelay; - frame.FlipToken = p->FlipToken; + frame.flipToken = p->FlipToken; frame.displayed = p->Displayed; diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h index f42cf31e..951dc240 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h @@ -60,7 +60,7 @@ namespace pmon::util::metrics { uint64_t pclSimStartTime = 0; uint64_t pclInputPingTime = 0; uint64_t flipDelay = 0; - uint32_t FlipToken = 0; + uint32_t flipToken = 0; // Metadata PresentResult finalState; @@ -114,7 +114,7 @@ namespace pmon::util::metrics { // Vendor-specific uint64_t getFlipDelay() const { return flipDelay; } - uint32_t getFlipToken() const { return FlipToken; } + uint32_t getFlipToken() const { return flipToken; } // Metadata PresentResult getFinalState() const { return finalState; } diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index 517faf6a..d19da4c1 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -909,28 +909,6 @@ namespace MetricsCoreTests return f; } - // ---------------------------------------------------------------------------- - // Proposed (GoogleTest style names) test case list (implemented with MSTest): - // - // ComputeMetricsForPresent_NotDisplayed_NoDisplays_ProducesSingleMetricsAndUpdatesChain - // ComputeMetricsForPresent_NotDisplayed_WithDisplaysButNotPresented_ProducesSingleMetricsAndUpdatesChain - // ComputeMetricsForPresent_DisplayedNoNext_SingleDisplay_PostponedChainNotUpdated - // ComputeMetricsForPresent_DisplayedNoNext_MultipleDisplays_ProcessesAllButLast - // ComputeMetricsForPresent_DisplayedWithNext_ProcessesPostponedLastAndUpdatesChain - // ComputeMetricsForPresent_DisplayedWithNext_LastDisplayIsRepeated_DoesNotUpdateLastAppPresent - // UpdateAfterPresent_AnimationSource_AppProvider_UpdatesSimStartAndFirstAppSim - // UpdateAfterPresent_AnimationSource_PCLatency_UpdatesSimStartAndFirstAppSim - // UpdateAfterPresent_AnimationSource_CpuStart_FallbackToPreviousAppPresent - // UpdateAfterPresent_AnimationSource_CpuStart_TransitionsToAppProvider - // UpdateAfterPresent_AnimationSource_CpuStart_TransitionsToPCLatency - // UpdateAfterPresent_FlipDelayTracking_PresentedWithDisplays_SetsFlipDelayAndScreenTime - // UpdateAfterPresent_FlipDelayTracking_PresentedNoDisplays_ZeroesFlipDelayAndScreenTime - // UpdateAfterPresent_NotPresented_DoesNotChangeLastDisplayedScreenTime - // - // (Pending future implementation) - // UpdateAfterPresent_InputAccumulation_DroppedFramesAccumulate (when logic added) - // ---------------------------------------------------------------------------- - TEST_CLASS(ComputeMetricsForPresentTests) { public: @@ -2098,4 +2076,217 @@ namespace MetricsCoreTests Assert::AreEqual(uint64_t(9'700'000), m.screenTimeQpc); } }; + TEST_CLASS(NvCollapsedPresentTests) + { + public: + TEST_METHOD(NvCollapsedPresent_CurrentFrame_AdjustsScreenTimeAndFlipDelayLikeNV1) + { + // Mirrors AdjustScreenTimeForCollapsedPresentNV1 behavior: + // When lastDisplayedScreenTime > currentScreenTime and lastDisplayedFlipDelay > 0, + // the current frame's screenTime and flipDelay are adjusted upward. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + // Prior displayed frame had a screen time of 5'000'000 with flip delay of 100'000 ticks + chain.lastDisplayedScreenTime = 5'000'000; + chain.lastDisplayedFlipDelay = 100'000; + + // Current frame (collapsed/runt) has an earlier raw screen time + FrameData current{}; + current.presentStartTime = 4'000'000; + current.timeInPresent = 50'000; + current.readyTime = 4'100'000; + current.flipDelay = 50'000; // Original flip delay for this frame + current.setFinalState(PresentResult::Presented); + // Raw screen time (3'500'000) is earlier than lastDisplayedScreenTime (5'000'000) + current.displayed.push_back({ FrameType::Application, 3'500'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 6'000'000 }); + + auto results = ComputeMetricsForPresent(qpc, current, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& metrics = results[0].metrics; + + // NV1 adjustment: screenTime should be raised to lastDisplayedScreenTime + Assert::AreEqual(uint64_t(5'000'000), metrics.screenTimeQpc, + L"NV1 should adjust screenTime to lastDisplayedScreenTime (5'000'000)"); + + // NV1 adjustment: flipDelay should be increased by the difference + // effectiveFlipDelay = 50'000 + (5'000'000 - 3'500'000) = 50'000 + 1'500'000 = 1'550'000 + uint64_t expectedEffectiveFlipDelay = 50'000 + (5'000'000 - 3'500'000); + double expectedMsFlipDelay = qpc.DeltaUnsignedMilliSeconds(0, expectedEffectiveFlipDelay); + + Assert::IsTrue(metrics.msFlipDelay.has_value(), + L"msFlipDelay should be set for displayed frame"); + if (metrics.msFlipDelay.has_value()) { + Assert::AreEqual(expectedMsFlipDelay, metrics.msFlipDelay.value(), 0.0001, + L"NV1 should adjust flipDelay to account for screenTime catch-up"); + } + } + + TEST_METHOD(NvCollapsedPresent_NextFrame_AdjustsNextScreenTimeAndFlipDelayLikeNV2) + { + // Mirrors AdjustScreenTimeForCollapsedPresentNV behavior: + // When current frame's screenTime > nextFrame's screenTime and current has flipDelay, + // the next frame's screenTime and flipDelay are adjusted upward. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + // First frame: collapsed present with significant flipDelay + // Its adjusted screenTime will be later than the next frame's raw screenTime + FrameData first{}; + first.presentStartTime = 4'000'000; + first.timeInPresent = 50'000; + first.readyTime = 4'100'000; + first.flipDelay = 200'000; // 0.02ms at 10MHz + first.setFinalState(PresentResult::Presented); + // First's screen time is 5'500'000 + first.displayed.push_back({ FrameType::Application, 5'500'000 }); + + // Second frame (next displayed) + FrameData second{}; + second.presentStartTime = 5'000'000; + second.timeInPresent = 40'000; + second.readyTime = 5'100'000; + second.flipDelay = 100'000; // Original flip delay for second frame + second.setFinalState(PresentResult::Presented); + // Second's raw screen time is 5'000'000, which is EARLIER than first's (5'500'000) + // This triggers NV2 adjustment + second.displayed.push_back({ FrameType::Application, 5'000'000 }); + + // Process first frame with second as nextDisplayed + auto resultsFirst = ComputeMetricsForPresent(qpc, first, &second, chain); + Assert::AreEqual(size_t(1), resultsFirst.size()); + + // Now process second frame (which should have been adjusted by NV2) + FrameData third{}; + third.setFinalState(PresentResult::Presented); + third.displayed.push_back({ FrameType::Application, 6'000'000 }); + + auto resultsSecond = ComputeMetricsForPresent(qpc, second, &third, chain); + Assert::AreEqual(size_t(1), resultsSecond.size()); + const auto& secondMetrics = resultsSecond[0].metrics; + + // NV2 adjustment: second's screenTime should be raised to first's screenTime + // when first.screenTime (5'500'000) > second.screenTime (5'000'000) + Assert::AreEqual(uint64_t(5'500'000), secondMetrics.screenTimeQpc, + L"NV2 should adjust second's screenTime to first's screenTime (5'500'000)"); + + // NV2 adjustment: second's flipDelay should be increased by the difference + // effectiveSecondFlipDelay = 100'000 + (5'500'000 - 5'000'000) = 100'000 + 500'000 = 600'000 + uint64_t expectedEffectiveFlipDelaySecond = 100'000 + (5'500'000 - 5'000'000); + double expectedMsFlipDelaySecond = qpc.DurationMilliSeconds(expectedEffectiveFlipDelaySecond); + + Assert::IsTrue(secondMetrics.msFlipDelay.has_value(), + L"msFlipDelay should be set for displayed frame"); + if (secondMetrics.msFlipDelay.has_value()) { + Assert::AreEqual(expectedMsFlipDelaySecond, secondMetrics.msFlipDelay.value(), 0.0001, + L"NV2 should adjust second's flipDelay to account for screenTime catch-up"); + } + } + + TEST_METHOD(NvCollapsedPresent_NoCollapse_ScreenTimesAndFlipDelaysUnchanged) + { + // Sanity check: when there is NO collapsed present condition, + // screen times and flip delays should pass through unchanged. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + // Prior displayed frame with screen time and flip delay + chain.lastDisplayedScreenTime = 3'000'000; + chain.lastDisplayedFlipDelay = 50'000; + + // Current frame with LATER screen time (no collapse) + FrameData current{}; + current.presentStartTime = 4'000'000; + current.timeInPresent = 50'000; + current.readyTime = 4'100'000; + current.flipDelay = 75'000; + current.setFinalState(PresentResult::Presented); + // Current screen time is LATER than lastDisplayedScreenTime, so no NV1 adjustment + current.displayed.push_back({ FrameType::Application, 4'000'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 5'000'000 }); + + auto results = ComputeMetricsForPresent(qpc, current, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& metrics = results[0].metrics; + + // No NV1 adjustment: screenTime should remain unchanged + Assert::AreEqual(uint64_t(4'000'000), metrics.screenTimeQpc, + L"No collapse: screenTime should remain at original value"); + + // No adjustment to flipDelay: should use original 75'000 + double expectedMsFlipDelay = qpc.DurationMilliSeconds(75'000); + + Assert::IsTrue(metrics.msFlipDelay.has_value(), + L"msFlipDelay should be set for displayed frame"); + if (metrics.msFlipDelay.has_value()) { + Assert::AreEqual(expectedMsFlipDelay, metrics.msFlipDelay.value(), 0.0001, + L"No collapse: flipDelay should remain at original value"); + } + } + + TEST_METHOD(NvCollapsedPresent_NV2_OnlyAdjustsWhenFirstScreenTimeGreaterThanSecond) + { + // NV2 should only adjust when first.screenTime > second.screenTime. + // This test verifies that when second.screenTime >= first.screenTime, + // no adjustment occurs. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + // First frame with flip delay + FrameData first{}; + first.presentStartTime = 4'000'000; + first.timeInPresent = 50'000; + first.readyTime = 4'100'000; + first.flipDelay = 100'000; + first.setFinalState(PresentResult::Presented); + // First screen time is 5'000'000 + first.displayed.push_back({ FrameType::Application, 5'000'000 }); + + // Second frame with screen time >= first (no collapse condition) + FrameData second{}; + second.presentStartTime = 5'000'000; + second.timeInPresent = 40'000; + second.readyTime = 5'100'000; + second.flipDelay = 50'000; + second.setFinalState(PresentResult::Presented); + // Second screen time is equal to first (5'000'000), so NV2 should NOT adjust + second.displayed.push_back({ FrameType::Application, 5'000'000 }); + + auto resultsFirst = ComputeMetricsForPresent(qpc, first, &second, chain); + Assert::AreEqual(size_t(1), resultsFirst.size()); + + FrameData third{}; + third.setFinalState(PresentResult::Presented); + third.displayed.push_back({ FrameType::Application, 6'000'000 }); + + auto resultsSecond = ComputeMetricsForPresent(qpc, second, &third, chain); + Assert::AreEqual(size_t(1), resultsSecond.size()); + const auto& secondMetrics = resultsSecond[0].metrics; + + // NV2 should NOT adjust: second's screenTime should remain at 5'000'000 + Assert::AreEqual(uint64_t(5'000'000), secondMetrics.screenTimeQpc, + L"NV2: when second.screenTime >= first.screenTime, no adjustment should occur"); + + // flipDelay should remain at original 50'000 + double expectedMsFlipDelay = qpc.DurationMilliSeconds(50'000); + + Assert::IsTrue(secondMetrics.msFlipDelay.has_value(), + L"msFlipDelay should be set for displayed frame"); + if (secondMetrics.msFlipDelay.has_value()) { + Assert::AreEqual(expectedMsFlipDelay, secondMetrics.msFlipDelay.value(), 0.0001, + L"NV2: when no collapse, flipDelay should remain unchanged"); + } + } + }; } \ No newline at end of file From e87bac2948e3a0aa57d7d474919a2355864cd243 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Tue, 2 Dec 2025 11:28:42 -0800 Subject: [PATCH 012/205] Fixed flip delay issue and updated nv collapsed present tests --- .../CommonUtilities/mc/MetricsCalculator.cpp | 2 +- IntelPresentMon/UnitTests/MetricsCore.cpp | 52 +------------------ 2 files changed, 3 insertions(+), 51 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 9fa233ce..a08662c0 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -69,7 +69,7 @@ namespace pmon::util::metrics bool isDisplayed, FrameMetrics& out) { - if (isDisplayed || present.getFlipDelay() != 0) { + if (isDisplayed && present.getFlipDelay() != 0) { out.msFlipDelay = qpc.DurationMilliSeconds(present.getFlipDelay()); } diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index d19da4c1..4ec90240 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -2079,55 +2079,7 @@ namespace MetricsCoreTests TEST_CLASS(NvCollapsedPresentTests) { public: - TEST_METHOD(NvCollapsedPresent_CurrentFrame_AdjustsScreenTimeAndFlipDelayLikeNV1) - { - // Mirrors AdjustScreenTimeForCollapsedPresentNV1 behavior: - // When lastDisplayedScreenTime > currentScreenTime and lastDisplayedFlipDelay > 0, - // the current frame's screenTime and flipDelay are adjusted upward. - - QpcConverter qpc(10'000'000, 0); - SwapChainCoreState chain{}; - - // Prior displayed frame had a screen time of 5'000'000 with flip delay of 100'000 ticks - chain.lastDisplayedScreenTime = 5'000'000; - chain.lastDisplayedFlipDelay = 100'000; - - // Current frame (collapsed/runt) has an earlier raw screen time - FrameData current{}; - current.presentStartTime = 4'000'000; - current.timeInPresent = 50'000; - current.readyTime = 4'100'000; - current.flipDelay = 50'000; // Original flip delay for this frame - current.setFinalState(PresentResult::Presented); - // Raw screen time (3'500'000) is earlier than lastDisplayedScreenTime (5'000'000) - current.displayed.push_back({ FrameType::Application, 3'500'000 }); - - FrameData next{}; - next.setFinalState(PresentResult::Presented); - next.displayed.push_back({ FrameType::Application, 6'000'000 }); - - auto results = ComputeMetricsForPresent(qpc, current, &next, chain); - Assert::AreEqual(size_t(1), results.size()); - const auto& metrics = results[0].metrics; - - // NV1 adjustment: screenTime should be raised to lastDisplayedScreenTime - Assert::AreEqual(uint64_t(5'000'000), metrics.screenTimeQpc, - L"NV1 should adjust screenTime to lastDisplayedScreenTime (5'000'000)"); - - // NV1 adjustment: flipDelay should be increased by the difference - // effectiveFlipDelay = 50'000 + (5'000'000 - 3'500'000) = 50'000 + 1'500'000 = 1'550'000 - uint64_t expectedEffectiveFlipDelay = 50'000 + (5'000'000 - 3'500'000); - double expectedMsFlipDelay = qpc.DeltaUnsignedMilliSeconds(0, expectedEffectiveFlipDelay); - - Assert::IsTrue(metrics.msFlipDelay.has_value(), - L"msFlipDelay should be set for displayed frame"); - if (metrics.msFlipDelay.has_value()) { - Assert::AreEqual(expectedMsFlipDelay, metrics.msFlipDelay.value(), 0.0001, - L"NV1 should adjust flipDelay to account for screenTime catch-up"); - } - } - - TEST_METHOD(NvCollapsedPresent_NextFrame_AdjustsNextScreenTimeAndFlipDelayLikeNV2) + TEST_METHOD(NvCollapsedPresent_AdjustsNextScreenTimeAndFlipDelay) { // Mirrors AdjustScreenTimeForCollapsedPresentNV behavior: // When current frame's screenTime > nextFrame's screenTime and current has flipDelay, @@ -2234,7 +2186,7 @@ namespace MetricsCoreTests } } - TEST_METHOD(NvCollapsedPresent_NV2_OnlyAdjustsWhenFirstScreenTimeGreaterThanSecond) + TEST_METHOD(NvCollapsedPresent_OnlyAdjustsWhenFirstScreenTimeGreaterThanSecond) { // NV2 should only adjust when first.screenTime > second.screenTime. // This test verifies that when second.screenTime >= first.screenTime, From 0f722063c6f84eba2dfb8d5730c8fb8650b0a819 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Tue, 2 Dec 2025 20:07:04 -0800 Subject: [PATCH 013/205] Adding in display latency and ready time latency with ULTs --- .../CommonUtilities/mc/MetricsCalculator.cpp | 51 +- IntelPresentMon/UnitTests/MetricsCore.cpp | 560 ++++++++++++++++++ 2 files changed, 609 insertions(+), 2 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index a08662c0..26a522da 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -70,14 +70,44 @@ namespace pmon::util::metrics FrameMetrics& out) { if (isDisplayed && present.getFlipDelay() != 0) { - out.msFlipDelay = - qpc.DurationMilliSeconds(present.getFlipDelay()); + out.msFlipDelay = qpc.DurationMilliSeconds(present.getFlipDelay()); } else { out.msFlipDelay = 0.0; } } + void ComputeMsDisplayLatency( + const QpcConverter& qpc, + const SwapChainCoreState& swapChain, + const FrameData& present, + bool isDisplayed, + uint64_t screenTime, + FrameMetrics& out) + { + const auto cpuStart = CalculateCPUStart(swapChain, present); + if (isDisplayed && cpuStart != 0) { + out.msDisplayLatency = qpc.DeltaUnsignedMilliSeconds(cpuStart, screenTime); + } else { + out.msDisplayLatency = 0.0; + } + } + + void ComputeMsReadyTimeToDisplayLatency( + const QpcConverter& qpc, + const FrameData& present, + bool isDisplayed, + uint64_t screenTime, + FrameMetrics& out) + { + if (isDisplayed && present.getReadyTime() != 0) { + out.msReadyTimeToDisplayLatency = qpc.DeltaUnsignedMilliSeconds(present.getReadyTime(), screenTime); + } + else { + out.msReadyTimeToDisplayLatency = 0.0; + } + } + void AdjustScreenTimeForCollapsedPresentNV( const FrameData& present, FrameData* nextDisplayedPresent, @@ -297,6 +327,23 @@ namespace pmon::util::metrics present, isDisplayed, metrics); + + // msDisplayLatency is the cpu start time to the current screen time + ComputeMsDisplayLatency( + qpc, + swapChain, + present, + isDisplayed, + screenTime, + metrics); + + // msReadyTimeToDisplayLatency is ready time to the current screen time + ComputeMsReadyTimeToDisplayLatency( + qpc, + present, + isDisplayed, + screenTime, + metrics); // screenTimeQpc is simply the current screen time metrics.screenTimeQpc = screenTime; diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index 4ec90240..2fe067e8 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -2241,4 +2241,564 @@ namespace MetricsCoreTests } } }; + TEST_CLASS(DisplayLatencyTests) + { + public: + TEST_METHOD(DisplayLatency_SimpleCase_PositiveDelta) + { + // Scenario: Single displayed frame with well-separated timestamps. + // cpuStart = 1'000'000, screenTime = 2'000'000 + // QPC frequency 10 MHz → 1'000'000 ticks = 0.1 ms + // Expected: msDisplayLatency ≈ 0.1 ms + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 50'000; + frame.readyTime = 1'100'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 2'000'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 2'500'000 }); + + // Set up chain with prior app present to establish cpuStart + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + chain.lastAppPresent = priorApp; + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + // cpuStart = 800'000 + 200'000 = 1'000'000 + // msDisplayLatency = screenTime - cpuStart = 2'000'000 - 1'000'000 = 1'000'000 ticks = 0.1 ms + double expected = qpc.DeltaUnsignedMilliSeconds(1'000'000, 2'000'000); + Assert::AreEqual(expected, m.msDisplayLatency, 0.0001); + } + + TEST_METHOD(DisplayLatency_CpuStartEqualsScreenTime) + { + // Scenario: CPU work finishes exactly when frame displays (degenerate case). + // cpuStart = screenTime = 2'000'000 + // Expected: msDisplayLatency = 0.0 ms + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 50'000; + frame.readyTime = 1'100'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 2'000'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 2'500'000 }); + + FrameData priorApp{}; + priorApp.presentStartTime = 1'700'000; + priorApp.timeInPresent = 300'000; + chain.lastAppPresent = priorApp; + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + // cpuStart = 1'700'000 + 300'000 = 2'000'000 + // msDisplayLatency = 2'000'000 - 2'000'000 = 0 + Assert::AreEqual(0.0, m.msDisplayLatency, 0.0001); + } + + TEST_METHOD(DisplayLatency_NotDisplayed_ReturnsZero) + { + // Scenario: Frame with no displayed entries (not displayed). + // Expected: msDisplayLatency = 0.0 ms + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 50'000; + frame.readyTime = 1'100'000; + frame.setFinalState(PresentResult::Presented); + // No displayed entries + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + Assert::AreEqual(0.0, m.msDisplayLatency, 0.0001); + } + + TEST_METHOD(DisplayLatency_ZeroCpuStart) + { + // Scenario: No prior chain history; cpuStart defaults to 0. + // cpuStart = 0, screenTime = 3'000'000 + // Expected: msDisplayLatency ≈ 0.3 ms + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + // No prior app present set + + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 50'000; + frame.readyTime = 1'100'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 3'000'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 3'500'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + // cpuStart = 0 (no prior app present) + // msDisplayLatency = 3'000'000 - 0 = 3'000'000 ticks = 0.3 ms + double expected = qpc.DeltaUnsignedMilliSeconds(0, 3'000'000); + Assert::AreEqual(expected, m.msDisplayLatency, 0.0001); + } + }; + + TEST_CLASS(ReadyTimeToDisplayLatencyTests) + { + public: + TEST_METHOD(ReadyTimeToDisplay_SimpleCase_PositiveDelta) + { + // Scenario: Single displayed frame with GPU ready time before screen time. + // readyTime = 1'500'000, screenTime = 2'000'000 + // QPC 10 MHz: 500'000 ticks = 0.05 ms + // Expected: msReadyTimeToDisplayLatency ≈ 0.05 ms + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 50'000; + frame.readyTime = 1'500'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 2'000'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 2'500'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + // msReadyTimeToDisplayLatency = screenTime - readyTime = 2'000'000 - 1'500'000 = 500'000 ticks = 0.05 ms + double expected = qpc.DeltaUnsignedMilliSeconds(1'500'000, 2'000'000); + Assert::IsTrue(m.msReadyTimeToDisplayLatency.has_value()); + Assert::AreEqual(expected, m.msReadyTimeToDisplayLatency.value(), 0.0001); + } + + TEST_METHOD(ReadyTimeToDisplay_ReadyTimeEqualsScreenTime) + { + // Scenario: GPU finishes exactly when frame displays. + // readyTime = screenTime = 2'000'000 + // Expected: msReadyTimeToDisplayLatency = 0.0 ms + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 50'000; + frame.readyTime = 2'000'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 2'000'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 2'500'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + Assert::IsTrue(m.msReadyTimeToDisplayLatency.has_value()); + Assert::AreEqual(0.0, m.msReadyTimeToDisplayLatency.value(), 0.0001); + } + + TEST_METHOD(ReadyTimeToDisplay_NotDisplayed_ReturnsZero) + { + // Scenario: Frame with no displayed entries. + // Expected: msReadyTimeToDisplayLatency = 0.0 ms + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 50'000; + frame.readyTime = 1'500'000; + frame.setFinalState(PresentResult::Presented); + // No displayed entries + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + Assert::IsTrue(m.msReadyTimeToDisplayLatency.has_value()); + Assert::AreEqual(0.0, m.msReadyTimeToDisplayLatency.value(), 0.0001); + } + + TEST_METHOD(ReadyTimeToDisplay_ReadyTimeZero) + { + // Scenario: Ready time not set (edge case, readyTime = 0). + // readyTime = 0, screenTime = 2'000'000 + // Expected: msReadyTimeToDisplayLatency ≈ 0.2 ms (2'000'000 ticks) + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 50'000; + frame.readyTime = 0; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 2'000'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 2'500'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + // msReadyTimeToDisplayLatency = 2'000'000 - 0 = 2'000'000 ticks = 0.2 ms + double expected = qpc.DeltaUnsignedMilliSeconds(0, 2'000'000); + Assert::IsTrue(m.msReadyTimeToDisplayLatency.has_value()); + Assert::AreEqual(expected, m.msReadyTimeToDisplayLatency.value(), 0.0001); + } + }; + + TEST_CLASS(MultiDisplayLatencyTests) + { + public: + TEST_METHOD(DisplayLatency_MultipleDisplays_EachComputesIndependently) + { + // Scenario: Single FrameData with 3 displayed instances (e.g., frame interpolation). + // Display 0: screenTime = 2'000'000 + // Display 1: screenTime = 2'100'000 + // Display 2: screenTime = 2'200'000 + // cpuStart = 1'000'000 (same for all) + // QPC 10 MHz + // Expected: + // Metrics[0].msDisplayLatency ≈ 0.1 ms + // Metrics[1].msDisplayLatency ≈ 0.11 ms + // Metrics[2].msDisplayLatency ≈ 0.12 ms + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 50'000; + frame.readyTime = 1'100'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 2'000'000 }); + frame.displayed.push_back({ FrameType::Repeated, 2'100'000 }); + frame.displayed.push_back({ FrameType::Repeated, 2'200'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 2'500'000 }); + + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + chain.lastAppPresent = priorApp; + + // First call without next: process [0..1] + auto results1 = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(2), results1.size()); + + // First display: cpuStart = 1'000'000, screenTime = 2'000'000 → 0.1 ms + double expected0 = qpc.DeltaUnsignedMilliSeconds(1'000'000, 2'000'000); + Assert::AreEqual(expected0, results1[0].metrics.msDisplayLatency, 0.0001); + + // Second display: cpuStart = 1'000'000, screenTime = 2'100'000 → 0.11 ms + double expected1 = qpc.DeltaUnsignedMilliSeconds(1'000'000, 2'100'000); + Assert::AreEqual(expected1, results1[1].metrics.msDisplayLatency, 0.0001); + + // Second call with next: process [2] + auto results2 = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results2.size()); + + // Third display: cpuStart = 1'000'000, screenTime = 2'200'000 → 0.12 ms + double expected2 = qpc.DeltaUnsignedMilliSeconds(1'000'000, 2'200'000); + Assert::AreEqual(expected2, results2[0].metrics.msDisplayLatency, 0.0001); + } + + TEST_METHOD(ReadyTimeToDisplay_MultipleDisplays_IndependentDeltas) + { + // Scenario: Multiple displays, each with different screenTime, but same readyTime. + // readyTime = 1'500'000 (single value for the frame) + // Display 0: screenTime = 2'000'000 + // Display 1: screenTime = 2'100'000 + // QPC 10 MHz + // Expected: + // Metrics[0].msReadyTimeToDisplayLatency ≈ 0.05 ms (500'000 ticks) + // Metrics[1].msReadyTimeToDisplayLatency ≈ 0.06 ms (600'000 ticks) + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 50'000; + frame.readyTime = 1'500'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 2'000'000 }); + frame.displayed.push_back({ FrameType::Repeated, 2'100'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 2'500'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + + // Display 0: readyTime = 1'500'000, screenTime = 2'000'000 → 0.05 ms + double expected0 = qpc.DeltaUnsignedMilliSeconds(1'500'000, 2'000'000); + Assert::IsTrue(results[0].metrics.msReadyTimeToDisplayLatency.has_value()); + Assert::AreEqual(expected0, results[0].metrics.msReadyTimeToDisplayLatency.value(), 0.0001); + } + }; + + TEST_CLASS(NvCollapsedPresentLatencyTests) + { + public: + TEST_METHOD(DisplayLatency_NvCollapsed_AdjustedScreenTime) + { + // Scenario: NV collapse adjustment modifies screenTime before metric computation. + // Raw screenTime = 3'000'000 + // chain.lastDisplayedScreenTime = 4'000'000 (adjusted upward by NV1 logic) + // cpuStart = 1'000'000 + // QPC 10 MHz + // Assume the unified code applies NV adjustment so effective screenTime = 4'000'000 + // Expected: msDisplayLatency ≈ 0.3 ms (using adjusted screenTime 4'000'000 − 1'000'000) + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 50'000; + frame.readyTime = 1'100'000; + frame.flipDelay = 50'000; + frame.setFinalState(PresentResult::Presented); + // Raw screen time is 4'000'000, greater than next screen time + frame.displayed.push_back({ FrameType::Application, 4'000'000 }); + + FrameData next{}; + next.presentStartTime = 2'000'000; + next.timeInPresent = 50'000; + next.readyTime = 2'100'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 3'000'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + // After NV adjustment: screenTime = 4'000'000 + // msDisplayLatency = 4'000'000 - 1'100'000 = 3'000'000 ticks = 0.3 ms + double expected = qpc.DeltaUnsignedMilliSeconds(1'100'000, 4'000'000); + Assert::AreEqual(expected, m.msDisplayLatency, 0.0001); + } + + TEST_METHOD(ReadyTimeToDisplay_NvCollapsed_UsesAdjustedScreenTime) + { + // Scenario: NV collapse adjustment affects ReadyTimeToDisplay metric. + // Adjusted screenTime = 4'000'000 + // readyTime = 1'500'000 + // QPC 10 MHz + // Expected: msReadyTimeToDisplayLatency ≈ 0.25 ms (4'000'000 − 1'500'000 = 2'500'000 ticks) + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + chain.lastDisplayedScreenTime = 4'000'000; + chain.lastDisplayedFlipDelay = 100'000; + + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 50'000; + frame.readyTime = 1'500'000; + frame.flipDelay = 50'000; + frame.setFinalState(PresentResult::Presented); + // Raw screen time is 3'000'000, earlier than lastDisplayedScreenTime + frame.displayed.push_back({ FrameType::Application, 3'000'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 4'500'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + // After NV1 adjustment: screenTime = 4'000'000 + // msReadyTimeToDisplayLatency = 4'000'000 - 1'500'000 = 2'500'000 ticks = 0.25 ms + double expected = qpc.DeltaUnsignedMilliSeconds(1'500'000, 4'000'000); + Assert::IsTrue(m.msReadyTimeToDisplayLatency.has_value()); + Assert::AreEqual(expected, m.msReadyTimeToDisplayLatency.value(), 0.0001); + } + }; + + TEST_CLASS(DisplayLatencyEdgeCasesTests) + { + public: + TEST_METHOD(DisplayLatency_ScreenTimeBeforeCpuStart) + { + // Scenario: Timestamps appear out-of-order (screen time earlier than CPU start). + // cpuStart = 3'000'000 + // screenTime = 2'000'000 (earlier) + // This is unusual but should be handled gracefully (likely as 0 or negative value). + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 50'000; + frame.readyTime = 1'100'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 2'000'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 2'500'000 }); + + FrameData priorApp{}; + priorApp.presentStartTime = 2'500'000; + priorApp.timeInPresent = 500'000; + chain.lastAppPresent = priorApp; + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + // cpuStart = 2'500'000 + 500'000 = 3'000'000 + // screenTime = 2'000'000 (earlier than cpuStart) + // Result should be 0 or negative (implementation dependent) + Assert::IsTrue(m.msDisplayLatency <= 0.0 || m.msDisplayLatency == 0.0); + } + + TEST_METHOD(ReadyTimeToDisplay_ScreenTimeBeforeReadyTime) + { + // Scenario: Frame displays before GPU finishes (should not happen in practice). + // readyTime = 3'000'000 + // screenTime = 2'000'000 (earlier) + // Expected: 0 or negative value (defensive behavior) + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 50'000; + frame.readyTime = 3'000'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 2'000'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 2'500'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + // screenTime = 2'000'000, readyTime = 3'000'000 + // Result should be 0 or negative + Assert::IsTrue(m.msReadyTimeToDisplayLatency <= 0.0 || m.msReadyTimeToDisplayLatency == 0.0); + } + + TEST_METHOD(DisplayLatency_FirstFrame_NoPriorAppPresent) + { + // Scenario: First frame in swapchain; no prior lastAppPresent in chain state. + // cpuStart derived from lastPresent only (fallback). + // Single display with screenTime = 2'000'000 + // Expected: msDisplayLatency computed correctly using fallback CPU start + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + // No lastAppPresent set + + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 50'000; + frame.readyTime = 1'100'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 2'000'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 2'500'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + // No prior present, so cpuStart = 0 + // msDisplayLatency = 2'000'000 - 0 = 2'000'000 ticks = 0.2 ms + double expected = qpc.DeltaUnsignedMilliSeconds(0, 2'000'000); + Assert::AreEqual(expected, m.msDisplayLatency, 0.0001); + } + + TEST_METHOD(DisplayLatency_FrameWithAppPropagatedData) + { + // Scenario: lastAppPresent has appPropagatedPresentStartTime and appPropagatedTimeInPresent set. + // CPU start should use these. + // appPropagatedPresentStartTime = 800'000 + // appPropagatedTimeInPresent = 150'000 + // screenTime = 2'000'000 + // Expected cpuStart = 800'000 + 150'000 = 950'000 + // Expected msDisplayLatency ≈ 0.1055 ms (2'000'000 − 950'000 = 1'050'000 ticks) + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 50'000; + frame.readyTime = 1'100'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 2'000'000 }); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 2'500'000 }); + + FrameData priorApp{}; + priorApp.presentStartTime = 1'000'000; + priorApp.timeInPresent = 200'000; + priorApp.appPropagatedPresentStartTime = 800'000; + priorApp.appPropagatedTimeInPresent = 150'000; + chain.lastAppPresent = priorApp; + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + // cpuStart = 800'000 + 150'000 = 950'000 + // msDisplayLatency = 2'000'000 - 950'000 = 1'050'000 ticks = 0.105 ms + double expected = qpc.DeltaUnsignedMilliSeconds(950'000, 2'000'000); + Assert::AreEqual(expected, m.msDisplayLatency, 0.0001); + } + }; } \ No newline at end of file From c1ca9403aaaf1cedee2015b083a271adcdc57183 Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 20 Nov 2025 17:04:04 +0900 Subject: [PATCH 014/205] add shmring template container --- IntelPresentMon/Interprocess/source/ShmRing.h | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 IntelPresentMon/Interprocess/source/ShmRing.h diff --git a/IntelPresentMon/Interprocess/source/ShmRing.h b/IntelPresentMon/Interprocess/source/ShmRing.h new file mode 100644 index 00000000..4a2405d8 --- /dev/null +++ b/IntelPresentMon/Interprocess/source/ShmRing.h @@ -0,0 +1,62 @@ +#pragma once +#include "SharedMemoryTypes.h" +#include "../../CommonUtilities/Exception.h" +#include "../../CommonUtilities/log/Log.h" + +namespace pmon::ipc +{ + template + class ShmRing + { + public: + ShmRing(size_t capacity, ShmSegmentManager* pSegmentManager) + : + data_{ capacity, pSegmentManager->get_allocator() } + { + if (capacity < ReadBufferSize * 2) { + throw std::logic_error{ "The capacity of a ShmRing must be at least double its ReadBufferSize" }; + } + } + void Push(const T& val) + { + data_[IndexFromSerial_(nextWriteSerial_)] = val; + nextWriteSerial_++; + } + const T& At(size_t serial) const + { + // adds a one element buffer to help prevent reading partially updated data + // note this is much less buffer than GetSerialRange because that range + // returns the "safe" range considering processing time, while this check + // determines whether actual stale data is being accessed at this instant + if (serial + data_.size() <= nextWriteSerial_) { + pmlog_warn("Reading stale serial").pmwatch(serial); + } + else if (nextWriteSerial_ <= serial) { + pmlog_warn("Reading nonexistent serial").pmwatch(serial); + } + return data_[IndexFromSerial_(serial)]; + } + std::pair GetSerialRange() const + { + if (nextWriteSerial_ < data_.size()) { + return { 0, nextWriteSerial_ }; + } + else { + // lock current next serial to keep the returned range logically consistent + const size_t serial = nextWriteSerial_; + // once we have looped around the vector, we need to maintain a buffer + // to help avoid the client reading partially-updated data (data race) + return { nextWriteSerial_ - data_.size() + ReadBufferSize, nextWriteSerial_ }; + } + } + private: + // functions + size_t IndexFromSerial_(size_t serial) const + { + return serial % data_.size(); + } + // data + std::atomic nextWriteSerial_ = 0; + ShmVector data_; + }; +} \ No newline at end of file From 8f49109bcb21cd32c38bd98f3c74b056b9fd96c4 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 26 Nov 2025 10:00:07 +0900 Subject: [PATCH 015/205] first draft of required data structures & interfaces --- IntelPresentMon/CommonUtilities/Exception.h | 2 +- .../Interprocess/Interprocess.vcxproj | 9 +- .../Interprocess/Interprocess.vcxproj.filters | 21 +++ .../Interprocess/source/DataStores.h | 50 ++++++ .../source/FrameDataPlaceholder.h | 106 ++++++++++++ .../Interprocess/source/HistoryRing.h | 151 ++++++++++++++++++ .../Interprocess/source/Interprocess.h | 23 ++- .../source/IntrospectionCapsLookup.h | 13 +- .../Interprocess/source/MetricCapabilities.h | 19 +++ .../Interprocess/source/SharedMemoryTypes.h | 3 + IntelPresentMon/Interprocess/source/ShmRing.h | 9 +- .../Interprocess/source/StreamedDataSegment.h | 27 ++++ .../Interprocess/source/TelemetryMap.h | 81 ++++++++++ 13 files changed, 501 insertions(+), 13 deletions(-) create mode 100644 IntelPresentMon/Interprocess/source/DataStores.h create mode 100644 IntelPresentMon/Interprocess/source/FrameDataPlaceholder.h create mode 100644 IntelPresentMon/Interprocess/source/HistoryRing.h create mode 100644 IntelPresentMon/Interprocess/source/MetricCapabilities.h create mode 100644 IntelPresentMon/Interprocess/source/StreamedDataSegment.h create mode 100644 IntelPresentMon/Interprocess/source/TelemetryMap.h diff --git a/IntelPresentMon/CommonUtilities/Exception.h b/IntelPresentMon/CommonUtilities/Exception.h index beb8bf1f..0e959640 100644 --- a/IntelPresentMon/CommonUtilities/Exception.h +++ b/IntelPresentMon/CommonUtilities/Exception.h @@ -3,7 +3,7 @@ #include #include #include -#include +#include "../PresentMonAPI2/PresentMonAPI.h" namespace pmon::util { diff --git a/IntelPresentMon/Interprocess/Interprocess.vcxproj b/IntelPresentMon/Interprocess/Interprocess.vcxproj index eb26dfbc..38614eaa 100644 --- a/IntelPresentMon/Interprocess/Interprocess.vcxproj +++ b/IntelPresentMon/Interprocess/Interprocess.vcxproj @@ -1,4 +1,4 @@ - + @@ -27,6 +27,9 @@ + + + @@ -55,6 +58,10 @@ + + + + diff --git a/IntelPresentMon/Interprocess/Interprocess.vcxproj.filters b/IntelPresentMon/Interprocess/Interprocess.vcxproj.filters index 6187839f..05e59783 100644 --- a/IntelPresentMon/Interprocess/Interprocess.vcxproj.filters +++ b/IntelPresentMon/Interprocess/Interprocess.vcxproj.filters @@ -143,5 +143,26 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/source/DataStores.h b/IntelPresentMon/Interprocess/source/DataStores.h new file mode 100644 index 00000000..a78b1bb2 --- /dev/null +++ b/IntelPresentMon/Interprocess/source/DataStores.h @@ -0,0 +1,50 @@ +#pragma once +#include "SharedMemoryTypes.h" +#include "ShmRing.h" +#include "TelemetryMap.h" +#include "../../CommonUtilities/Exception.h" +#include "../../CommonUtilities/log/Log.h" +#include "../../PresentMonAPI2/PresentMonAPI.h" +#include "FrameDataPlaceholder.h" +#include + +// these data stores intended to be hosted within StreamedDataSegment instances via template +// they provide the interface that the middleware will use to access frame/telemetry data +// as well as the interface the service will use to publish same + +namespace pmon::ipc +{ + struct FrameDataStore + { + struct Statics + { + uint32_t processId; + ShmString applicationName; + } statics; + ShmRing frameData; + }; + + struct GpuDataStore + { + struct Statics + { + PM_DEVICE_VENDOR vendor; + ShmString name; + double sustainedPowerLimit; + uint64_t memSize; + uint64_t maxMemBandwidth; + } statics; + TelemetryMap telemetryData; + }; + + struct SystemDataStore + { + struct Statics + { + PM_DEVICE_VENDOR cpuVendor; + ShmString cpuName; + double cpuPowerLimit; + } statics; + TelemetryMap telemetryData; + }; +} \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/source/FrameDataPlaceholder.h b/IntelPresentMon/Interprocess/source/FrameDataPlaceholder.h new file mode 100644 index 00000000..ee11286b --- /dev/null +++ b/IntelPresentMon/Interprocess/source/FrameDataPlaceholder.h @@ -0,0 +1,106 @@ +#pragma once +#include "../../../PresentData/PresentMonTraceConsumer.hpp" + +namespace pmon::ipc +{ + // temporary; replace with structure dictated by new PresentMon calculation library + struct PmFrameData + { + uint64_t PresentStartTime; // QPC value of the first event related to the + // Present (D3D9, DXGI, or DXGK Present_Start) + uint32_t ProcessId; // ID of the process that presented + uint32_t ThreadId; // ID of the thread that presented + uint64_t TimeInPresent; // QPC duration between runtime present start and end + uint64_t GPUStartTime; // QPC value when the frame's first DMA packet started + uint64_t ReadyTime; // QPC value when the frame's last DMA packet completed + + uint64_t GPUDuration; // QPC duration during which a frame's DMA packet was + // running on any node + uint64_t GPUVideoDuration; // QPC duration during which a frame's DMA packet was + // running on a video node (if mTrackGPUVideo==true) + uint64_t InputTime; // Earliest QPC value for all keyboard/mouse input used by this frame + uint64_t MouseClickTime; // Earliest QPC value when the mouse was clicked and used by this frame + + // Used to track the application work when Intel XeSS-FG is enabled + uint64_t AppPropagatedPresentStartTime; // Propogated QPC value of the first event related to the Present (D3D9, DXGI, or DXGK Present_Start) + uint64_t AppPropagatedTimeInPresent; // Propogated QPC duration of the Present call (only applicable for D3D9/DXGI) + uint64_t AppPropagatedGPUStartTime; // Propogated QPC value when the frame's first DMA packet started + uint64_t AppPropagatedReadyTime; // Propogated QPC value when the frame's last DMA packet completed + uint64_t AppPropagatedGPUDuration; // Propogated QPC duration during which a frame's DMA packet was running on + uint64_t AppPropagatedGPUVideoDuration; // Propogated QPC duration during which a frame's DMA packet was running on + // a video node (if mTrackGPUVideo==true) + + uint64_t AppSleepStartTime; // QPC value of app sleep start time provided by Intel App Provider + uint64_t AppSleepEndTime; // QPC value of app sleep end time provided by Intel App Provider + uint64_t AppSimStartTime; // QPC value of app sim start time provided by Intel App Provider + uint64_t AppSimEndTime; // QPC value of app sim end time provided by Intel App Provider + uint64_t AppRenderSubmitStartTime; // QPC value of app render submit start time provided by Intel App Provider + uint64_t AppRenderSubmitEndTime; // QPC value of app render submit end time provided by Intel App Provider + uint64_t AppPresentStartTime; // QPC value of app present start time provided by Intel App Provider + uint64_t AppPresentEndTime; // QPC value of app present end time provided by Intel App Provider + uint64_t AppInputTime; // QPC value of app input time provided by Intel App Provider + InputDeviceType AppInputType; // Input type provided by Intel App Provider + + uint64_t PclInputPingTime; // QPC value of input ping time provided by the PC Latency ETW event + uint64_t PclSimStartTime; // QPC value of app sim start time provided by the PC Latency ETW event + uint64_t FlipDelay; // QPC timestamp delta of FlipDelay calculated using the NV DisplayDriver FlipRequest event. + uint32_t FlipToken; // Flip token from the NV DisplayDriver FlipRequest event. + + // Extra present parameters obtained through DXGI or D3D9 present + uint64_t SwapChainAddress; + int32_t SyncInterval; + uint32_t PresentFlags; + + // (FrameType, DisplayedQPC) for each time the frame was displayed + uint64_t Displayed_ScreenTime[16]; + FrameType Displayed_FrameType[16]; + uint32_t DisplayedCount; + + // Keys used to index into PMTraceConsumer's tracking data structures: + uint64_t CompositionSurfaceLuid; // mPresentByWin32KPresentHistoryToken + uint64_t Win32KPresentCount; // mPresentByWin32KPresentHistoryToken + uint64_t Win32KBindId; // mPresentByWin32KPresentHistoryToken + uint64_t DxgkPresentHistoryToken; // mPresentByDxgkPresentHistoryToken + uint64_t + DxgkPresentHistoryTokenData; // mPresentByDxgkPresentHistoryTokenData + uint64_t DxgkContext; // mPresentByDxgkContext + uint64_t Hwnd; // mLastPresentByWindow + uint32_t QueueSubmitSequence; // mPresentBySubmitSequence + uint32_t RingIndex; // mTrackedPresents and mCompletedPresents + + // Properties deduced by watching events through present pipeline + uint32_t DestWidth; + uint32_t DestHeight; + uint32_t DriverThreadId; + + uint32_t FrameId; // ID for the logical frame that this Present is associated with. + + Runtime Runtime; + PresentMode PresentMode; + PresentResult FinalState; + InputDeviceType InputType; + FrameType FrameType; + + bool SupportsTearing; + bool WaitForFlipEvent; + bool WaitForMPOFlipEvent; + bool SeenDxgkPresent; + bool SeenWin32KEvents; + bool SeenInFrameEvent; // This present has gotten a Win32k TokenStateChanged + // event into InFrame state + bool GpuFrameCompleted; // This present has already seen an event that caused + // GpuTrace::CompleteFrame() to be called. + bool IsCompleted; // All expected events have been observed + bool IsLost; // This PresentEvent was found in an unexpected state or is too + // old + + // Whether this PresentEvent is currently stored in + // PMTraceConsumer::mPresentsWaitingForDWM + bool PresentInDwmWaitingStruct; + + // QPC time of last presented frame + uint64_t last_present_qpc; + // QPC time of the last displayed frame + uint64_t last_displayed_qpc; + }; +} diff --git a/IntelPresentMon/Interprocess/source/HistoryRing.h b/IntelPresentMon/Interprocess/source/HistoryRing.h new file mode 100644 index 00000000..89eaf67a --- /dev/null +++ b/IntelPresentMon/Interprocess/source/HistoryRing.h @@ -0,0 +1,151 @@ +#pragma once +#include "ShmRing.h" + +namespace pmon::ipc +{ + // wrapper around ShmRing that adds the ability to search/address by timestamp + // meant for use with telemetry data + template + class HistoryRing + { + public: + // types + struct Sample + { + T value; + uint64_t timestamp; + }; + // functions + HistoryRing(size_t capacity, ShmVector::allocator_type& alloc) + : + samples_{ capacity, alloc } + { + if (capacity < ReadBufferSize * 2) { + throw std::logic_error{ "The capacity of a ShmRing must be at least double its ReadBufferSize" }; + } + } + void Push(const T& value, uint64_t timestamp) + { + samples_.Push({ value, timestamp }); + } + const Sample& At(size_t serial) const + { + return samples_.At(serial); + } + std::pair GetSerialRange() const + { + return samples_.GetSerialRange(); + } + bool Empty() const + { + return samples_.Empty(); + } + // First serial with timestamp >= given timestamp. + // If all samples have timestamp < given timestamp, returns last (one past end). + size_t LowerBoundSerial(uint64_t timestamp) const + { + return BoundSerial_(timestamp); + } + // First serial with timestamp > given timestamp. + // If all samples have timestamp <= given timestamp, returns last (one past end). + size_t UpperBoundSerial(uint64_t timestamp) const + { + return BoundSerial_(timestamp); + } + // Find the serial whose timestamp is closest to the given timestamp. + // If the timestamp is outside the stored range, clamps to first/last. + size_t NearestSerial(uint64_t timestamp) const + { + // First serial with timestamp >= requested + size_t serial = LowerBoundSerial(timestamp); + + // Check whether the previous sample is actually closer. + // but only if there is a sample available before this one + if (serial > samples_.GetSerialRange().first) { + const auto nextTimestamp = At(serial).timestamp; + const auto prevTimestamp = At(serial - 1).timestamp; + const uint64_t dPrev = timestamp - prevTimestamp; + const uint64_t dNext = nextTimestamp - timestamp; + if (dPrev <= dNext) { + --serial; + } + } + + return serial; + } + // Calls func(sample) for each sample whose timestamp is in [start, end]. + // Returns the number of samples visited. + template + size_t ForEachInTimestampRange(uint64_t start, uint64_t end, F&& func) const + { + const auto range = samples_.GetSerialRange(); + + // Find the first sample with timestamp >= start + size_t serial = LowerBoundSerial(start); + + size_t count = 0; + // Walk forward until we leave the [start, end] window or hit last + for (; serial < range.second; ++serial) { + const Sample& s = At(serial); + if (s.timestamp > end) { + break; + } + // s.timestamp is guaranteed >= start by LowerBoundSerial + std::forward(func)(s); + ++count; + } + + return count; + } + + private: + // types + enum class BoundKind + { + Lower, + Upper + }; + // functions + // Shared binary search implementation for LowerBoundSerial / UpperBoundSerial. + template + size_t BoundSerial_(uint64_t timestamp) const + { + auto range = samples_.GetSerialRange(); + size_t first = range.first; + size_t last = range.second; // one past end + + size_t lo = first; + size_t hi = last; + + // Standard lower/upper bound style search over [first, last) + while (lo < hi) { + size_t mid = lo + (hi - lo) / 2; + const Sample& s = At(mid); + + if constexpr (Kind == BoundKind::Lower) { + // First with s.timestamp >= timestamp + if (s.timestamp < timestamp) { + lo = mid + 1; + } + else { + hi = mid; + } + } + else { + // First with s.timestamp > timestamp + if (s.timestamp <= timestamp) { + lo = mid + 1; + } + else { + hi = mid; + } + } + } + + return lo; // in [first, last] + } + // data + ShmRing samples_; + }; + +} \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/source/Interprocess.h b/IntelPresentMon/Interprocess/source/Interprocess.h index 2b2d59b5..62cd5cf2 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.h +++ b/IntelPresentMon/Interprocess/source/Interprocess.h @@ -2,11 +2,9 @@ #include #include #include -#include "../../ControlLib/PresentMonPowerTelemetry.h" -#include "../../ControlLib/CpuTelemetryInfo.h" #include "../../PresentMonAPI2/PresentMonAPI.h" - -struct PM_INTROSPECTION_ROOT; +#include "DataStores.h" +#include "MetricCapabilities.h" namespace pmon::ipc { @@ -20,9 +18,16 @@ namespace pmon::ipc public: virtual ~ServiceComms() = default; virtual intro::IntrospectionRoot& GetIntrospectionRoot() = 0; - virtual void RegisterGpuDevice(PM_DEVICE_VENDOR vendor, std::string deviceName, const GpuTelemetryBitset& gpuCaps) = 0; + virtual void RegisterGpuDevice(PM_DEVICE_VENDOR vendor, std::string deviceName, const MetricCapabilities& caps) = 0; virtual void FinalizeGpuDevices() = 0; - virtual void RegisterCpuDevice(PM_DEVICE_VENDOR vendor, std::string deviceName, const CpuTelemetryBitset& cpuCaps) = 0; + virtual void RegisterCpuDevice(PM_DEVICE_VENDOR vendor, std::string deviceName, const MetricCapabilities& caps) = 0; + + // data store access + virtual FrameDataStore& MakeFrameDataStore(uint32_t pid) = 0; + virtual void RemoveFrameDataStore(uint32_t pid) = 0; + virtual FrameDataStore& GetFrameDataStore(uint32_t pid) = 0; + virtual GpuDataStore& GetGpuDataStore(uint32_t deviceId) = 0; + virtual SystemDataStore& GetSystemDataStore() = 0; }; class MiddlewareComms @@ -30,6 +35,12 @@ namespace pmon::ipc public: virtual ~MiddlewareComms() = default; virtual const PM_INTROSPECTION_ROOT* GetIntrospectionRoot(uint32_t timeoutMs = 2000) = 0; + + // data store access + virtual const FrameDataStore& GetFrameDataStore(uint32_t pid) const = 0; + virtual const GpuDataStore& GetGpuDataStore(uint32_t deviceId) const = 0; + virtual const SystemDataStore& GetSystemDataStore() const = 0; + }; std::unique_ptr MakeServiceComms(std::optional sharedMemoryName = {}); diff --git a/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h b/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h index 43e763e2..197958b6 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h +++ b/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h @@ -3,6 +3,7 @@ #include #include "../../ControlLib/PresentMonPowerTelemetry.h" #include "../../ControlLib/CpuTelemetryInfo.h" +#include "MetricCapabilities.h" @@ -39,8 +40,6 @@ namespace pmon::ipc::intro static constexpr auto gpuCapBitArray = std::array{ GpuTelemetryCapBits::max_fan_speed_0, GpuTelemetryCapBits::max_fan_speed_1, GpuTelemetryCapBits::max_fan_speed_2, GpuTelemetryCapBits::max_fan_speed_3, GpuTelemetryCapBits::max_fan_speed_4, }; }; - //template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBitArray = std::array{ - // GpuTelemetryCapBits::psu_info_0, GpuTelemetryCapBits::psu_info_1, GpuTelemetryCapBits::psu_info_2, GpuTelemetryCapBits::psu_info_3, GpuTelemetryCapBits::psu_info_4, }; }; template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_mem_size; }; template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_mem_used; }; template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_mem_used; }; @@ -78,5 +77,13 @@ namespace pmon::ipc::intro template concept IsCpuMetric = requires { T::cpuCapBit; }; template concept IsManualDisableMetric = requires { typename T::ManualDisable; }; - // TODO: compile-time verify that all cap bits are covered (how?) + MetricCapabilities ConvertBitset(const GpuTelemetryCapBits& bits) + { + return {}; + } + + MetricCapabilities ConvertBitset(const CpuTelemetryCapBits& bits) + { + return {}; + } } \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/source/MetricCapabilities.h b/IntelPresentMon/Interprocess/source/MetricCapabilities.h new file mode 100644 index 00000000..5f843f66 --- /dev/null +++ b/IntelPresentMon/Interprocess/source/MetricCapabilities.h @@ -0,0 +1,19 @@ +#pragma once +#include "../../PresentMonAPI2/PresentMonAPI.h" +#include + +namespace pmon::ipc +{ + // capabilities typically communicated from telemetry providers to be collected in here + // used to determine which metrics available for queries, which ShmRings need to be + // allocated elements, etc. + class MetricCapabilities + { + public: + void Set(PM_METRIC metricId, size_t arraySize); + void Merge(const MetricCapabilities& capsToMerge); + size_t Check(PM_METRIC metricId) const; + private: + std::unordered_map caps_; + }; +} \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/source/SharedMemoryTypes.h b/IntelPresentMon/Interprocess/source/SharedMemoryTypes.h index 16aaeea8..85d089fe 100644 --- a/IntelPresentMon/Interprocess/source/SharedMemoryTypes.h +++ b/IntelPresentMon/Interprocess/source/SharedMemoryTypes.h @@ -2,6 +2,7 @@ #include #include #include +#include #include namespace pmon::ipc @@ -17,6 +18,8 @@ namespace pmon::ipc using ShmVector = bip::vector>; template using ShmUniquePtr = typename bip::managed_unique_ptr::type; + template + using ShmMap = bip::map, ShmAllocator>>; namespace impl { template diff --git a/IntelPresentMon/Interprocess/source/ShmRing.h b/IntelPresentMon/Interprocess/source/ShmRing.h index 4a2405d8..8bf5d583 100644 --- a/IntelPresentMon/Interprocess/source/ShmRing.h +++ b/IntelPresentMon/Interprocess/source/ShmRing.h @@ -5,13 +5,14 @@ namespace pmon::ipc { + // shared memory ring buffer for broadcast template class ShmRing { public: - ShmRing(size_t capacity, ShmSegmentManager* pSegmentManager) + ShmRing(size_t capacity, ShmVector::allocator_type& alloc) : - data_{ capacity, pSegmentManager->get_allocator() } + data_{ capacity, alloc } { if (capacity < ReadBufferSize * 2) { throw std::logic_error{ "The capacity of a ShmRing must be at least double its ReadBufferSize" }; @@ -49,6 +50,10 @@ namespace pmon::ipc return { nextWriteSerial_ - data_.size() + ReadBufferSize, nextWriteSerial_ }; } } + bool Empty() const + { + return nextWriteSerial_ == 0; + } private: // functions size_t IndexFromSerial_(size_t serial) const diff --git a/IntelPresentMon/Interprocess/source/StreamedDataSegment.h b/IntelPresentMon/Interprocess/source/StreamedDataSegment.h new file mode 100644 index 00000000..4af27972 --- /dev/null +++ b/IntelPresentMon/Interprocess/source/StreamedDataSegment.h @@ -0,0 +1,27 @@ +#pragma once +#include "SharedMemoryTypes.h" + +namespace pmon::ipc +{ + // manages shared memory segment and hosts data store T + template + class StreamedDataSegment + { + public: + StreamedDataSegment(const std::string& segmentName, size_t size) + : + shm_{ bip::create_only } + {} + T& GetStore() + { + return *pData_; + } + const T& GetStore() const + { + return *pData_; + } + private: + ShmSegment shm_; + ShmUniquePtr pData_; + }; +} \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/source/TelemetryMap.h b/IntelPresentMon/Interprocess/source/TelemetryMap.h new file mode 100644 index 00000000..a10ae61b --- /dev/null +++ b/IntelPresentMon/Interprocess/source/TelemetryMap.h @@ -0,0 +1,81 @@ +#pragma once +#include "SharedMemoryTypes.h" +#include "HistoryRing.h" +#include + +namespace pmon::ipc +{ + // container for multiple history rings organized by PM_METRIC x index + class TelemetryMap + { + template + using HistoryRingVect = ShmVector>; + using MapValueType = std::variant, HistoryRingVect, HistoryRingVect>; + using MapType = ShmMap; + public: + TelemetryMap(const MapType::allocator_type& alloc) + : + ringMap_{ alloc } + { + } + template + void AddRing(int id, size_t size, size_t count) + { + // extra guard of misuse at compile time + static_assert( + std::is_same_v || + std::is_same_v || + std::is_same_v, + "Unsupported ring type for TelemetryMap" + ); + + using RingType = HistoryRing; + using RingAlloc = typename MapType::allocator_type::template rebind::other; + + // Construct an allocator for ShmRing from the map's allocator + HistoryRingVect ringAlloc(ringMap_.get_allocator()); + + // Insert (or get existing) entry for this id, constructing the correct variant alternative + auto [it, inserted] = ringMap_.emplace( + std::piecewise_construct, + std::forward_as_tuple(id), + std::forward_as_tuple( + std::in_place_type>, + ringAlloc + ) + ); + // don't allow AddRing when one already exists for the given id + if (!inserted) { + throw std::logic_error("TelemetryMap::AddRing: id already exists"); + } + + // Get the vector for this T + auto& rings = std::get>(it->second); + // construct (count) rings in the vector + rings.reserve(count); + for (size_t i = 0; i < count; ++i) { + rings.emplace_back(size, ringAlloc); + } + } + template + HistoryRingVect& FindRing(int id) + { + return std::get>(FindRingVariant(id)); + } + template + const HistoryRingVect& FindRing(int id) const + { + return std::get>(FindRingVariant(id)); + } + MapValueType& FindRingVariant(int id) + { + return ringMap_.at(id); + } + const MapValueType& FindRingVariant(int id) const + { + return ringMap_.at(id); + } + private: + MapType ringMap_; + }; +} \ No newline at end of file From 8c99c80ed4d1225935f1a17bc848d23b820d0972 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 26 Nov 2025 16:32:04 +0900 Subject: [PATCH 016/205] introspection populators use new caps set --- .../Interprocess/source/HistoryRing.h | 1 + .../Interprocess/source/Interprocess.cpp | 10 ++- .../source/IntrospectionPopulators.cpp | 85 ++++++------------- .../source/IntrospectionPopulators.h | 7 +- .../Interprocess/source/MetricCapabilities.h | 8 ++ .../source/metadata/EnumDeviceType.h | 5 +- .../PresentMonAPI2/PresentMonAPI.h | 1 + 7 files changed, 46 insertions(+), 71 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/HistoryRing.h b/IntelPresentMon/Interprocess/source/HistoryRing.h index 89eaf67a..5c3db054 100644 --- a/IntelPresentMon/Interprocess/source/HistoryRing.h +++ b/IntelPresentMon/Interprocess/source/HistoryRing.h @@ -74,6 +74,7 @@ namespace pmon::ipc return serial; } // Calls func(sample) for each sample whose timestamp is in [start, end]. + // Intended use case is calculation of stats (avg, min, %) // Returns the number of samples visited. template size_t ForEachInTimestampRange(uint64_t start, uint64_t end, F&& func) const diff --git a/IntelPresentMon/Interprocess/source/Interprocess.cpp b/IntelPresentMon/Interprocess/source/Interprocess.cpp index c8ef47e8..fa16485b 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.cpp +++ b/IntelPresentMon/Interprocess/source/Interprocess.cpp @@ -46,10 +46,11 @@ namespace pmon::ipc { return *pRoot_; } - void RegisterGpuDevice(PM_DEVICE_VENDOR vendor, std::string deviceName, const GpuTelemetryBitset& gpuCaps) override + void RegisterGpuDevice(PM_DEVICE_VENDOR vendor, std::string deviceName, const MetricCapabilities& caps) override { + // TODO: create data segment & rings auto lck = LockIntrospectionMutexExclusive_(); - intro::PopulateGpuDevice(shm_.get_segment_manager(), *pRoot_, nextDeviceIndex_++, vendor, deviceName, gpuCaps); + intro::PopulateGpuDevice(shm_.get_segment_manager(), *pRoot_, nextDeviceIndex_++, vendor, deviceName, caps); } void FinalizeGpuDevices() override { @@ -60,10 +61,11 @@ namespace pmon::ipc FinalizeIntrospection_(); } } - void RegisterCpuDevice(PM_DEVICE_VENDOR vendor, std::string deviceName, const CpuTelemetryBitset& cpuCaps) override + void RegisterCpuDevice(PM_DEVICE_VENDOR vendor, std::string deviceName, const MetricCapabilities& caps) override { + // TODO: create data segment & rings auto lck = LockIntrospectionMutexExclusive_(); - intro::PopulateCpu(shm_.get_segment_manager(), *pRoot_, vendor, deviceName, cpuCaps); + intro::PopulateCpu(shm_.get_segment_manager(), *pRoot_, vendor, deviceName, caps); introCpuComplete_ = true; if (introGpuComplete_ && introCpuComplete_) { lck.unlock(); diff --git a/IntelPresentMon/Interprocess/source/IntrospectionPopulators.cpp b/IntelPresentMon/Interprocess/source/IntrospectionPopulators.cpp index 7653e5f6..cf6ead9c 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionPopulators.cpp +++ b/IntelPresentMon/Interprocess/source/IntrospectionPopulators.cpp @@ -1,7 +1,8 @@ #include "IntrospectionHelpers.h" #include "IntrospectionMetadata.h" #include "IntrospectionTransfer.h" -#include "IntrospectionCapsLookup.h" +#include "MetricCapabilities.h" +#include "../../CommonUtilities/log/Log.h" #include #include @@ -71,81 +72,43 @@ namespace pmon::ipc::intro #undef X_REG_UNIT } - template - void RegisterGpuMetricDeviceInfo_(ShmSegmentManager* pSegmentManager, IntrospectionRoot& root, - uint32_t deviceId, const GpuTelemetryBitset& gpuCaps) + static void PopulateDeviceMetrics_(IntrospectionRoot& root, + const MetricCapabilities& caps, uint32_t deviceId) { - using Lookup = IntrospectionCapsLookup; - std::optional info; - if constexpr (IsUniversalMetric) {} - else if constexpr (IsGpuDeviceMetric) { - const auto availability = gpuCaps[size_t(Lookup::gpuCapBit)] ? - PM_METRIC_AVAILABILITY_AVAILABLE : PM_METRIC_AVAILABILITY_UNAVAILABLE; - info.emplace(deviceId, availability, 1); - } - else if constexpr (IsGpuDeviceMetricArray) { - const auto nAvailable = (uint32_t)rn::count_if(Lookup::gpuCapBitArray, [&](auto bit){ return gpuCaps[size_t(bit)]; }); - const auto availability = nAvailable > 0 ? - PM_METRIC_AVAILABILITY_AVAILABLE : PM_METRIC_AVAILABILITY_UNAVAILABLE; - info.emplace(deviceId, availability, nAvailable); - } - else if constexpr (IsGpuDeviceStaticMetric) { - info.emplace(deviceId, PM_METRIC_AVAILABILITY_AVAILABLE, 1); - } - if (info) { - if (auto i = rn::find(root.GetMetrics(), metric, [](const ShmUniquePtr& pMetric) { + for (auto&& [metric, count] : caps) { + auto i = rn::find( + root.GetMetrics(), + metric, + [](const ShmUniquePtr& pMetric) { return pMetric->GetId(); - }); i != root.GetMetrics().end()) { - (*i)->AddDeviceMetricInfo(std::move(*info)); + }); + if (i != root.GetMetrics().end()) { + const auto availability = count ? PM_METRIC_AVAILABILITY_AVAILABLE : + PM_METRIC_AVAILABILITY_UNAVAILABLE; + (*i)->AddDeviceMetricInfo(IntrospectionDeviceMetricInfo{ deviceId, availability, count }); + } + else { + pmlog_error("Metric ID not found").pmwatch(metric); } - // TODO: log metric not found } } void PopulateGpuDevice(ShmSegmentManager* pSegmentManager, IntrospectionRoot& root, uint32_t deviceId, - PM_DEVICE_VENDOR vendor, const std::string& deviceName, const GpuTelemetryBitset& gpuCaps) + PM_DEVICE_VENDOR vendor, const std::string& deviceName, const MetricCapabilities& caps) { // add the device auto charAlloc = pSegmentManager->get_allocator(); root.AddDevice(ShmMakeUnique(pSegmentManager, deviceId, - PM_DEVICE_TYPE_GRAPHICS_ADAPTER, vendor, ShmString{ deviceName.c_str(), charAlloc})); - - // populate device-metric info for this device -#define X_WALK_METRIC(metric, metric_type, unit, data_type, enum_id, device_type, ...) \ - RegisterGpuMetricDeviceInfo_(pSegmentManager, root, deviceId, gpuCaps); - - METRIC_LIST(X_WALK_METRIC) - -#undef X_WALK_METRIC - } + PM_DEVICE_TYPE_GRAPHICS_ADAPTER, vendor, ShmString{ deviceName.c_str(), charAlloc })); - template - void RegisterCpuMetricDeviceInfo_(ShmSegmentManager* pSegmentManager, IntrospectionRoot& root, - const CpuTelemetryBitset& cpuCaps) - { - using Lookup = IntrospectionCapsLookup; - if constexpr (IsCpuMetric) { - if (auto i = rn::find(root.GetMetrics(), metric, [](const ShmUniquePtr& pMetric) { - return pMetric->GetId(); - }); i != root.GetMetrics().end()) { - const auto availability = cpuCaps[size_t(Lookup::cpuCapBit)] ? - PM_METRIC_AVAILABILITY_AVAILABLE : PM_METRIC_AVAILABILITY_UNAVAILABLE; - (*i)->AddDeviceMetricInfo(IntrospectionDeviceMetricInfo{ 0, availability, 1 }); - } - } - - // TODO: log metric not found + // add the device metrics + PopulateDeviceMetrics_(root, caps, deviceId); } void PopulateCpu(ShmSegmentManager* pSegmentManager, IntrospectionRoot& root, - PM_DEVICE_VENDOR vendor, const std::string& deviceName, const CpuTelemetryBitset& cpuCaps) + PM_DEVICE_VENDOR vendor, const std::string& deviceName, const MetricCapabilities& caps) { - // populate device-metric info for the cpu -#define X_WALK_METRIC(metric, metric_type, unit, data_type, enum_id, device_type, ...) \ - RegisterCpuMetricDeviceInfo_(pSegmentManager, root, cpuCaps); - - METRIC_LIST(X_WALK_METRIC) - -#undef X_WALK_METRIC + PopulateDeviceMetrics_(root, caps, 0); } + } \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/source/IntrospectionPopulators.h b/IntelPresentMon/Interprocess/source/IntrospectionPopulators.h index 3319cd39..56ac7487 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionPopulators.h +++ b/IntelPresentMon/Interprocess/source/IntrospectionPopulators.h @@ -1,8 +1,7 @@ #pragma once #include "../../PresentMonAPI2/PresentMonAPI.h" #include "SharedMemoryTypes.h" -#include "../../ControlLib/PresentMonPowerTelemetry.h" -#include "../../ControlLib/CpuTelemetryInfo.h" +#include "MetricCapabilities.h" // TODO: forward declare the segment manager (or type erase) @@ -12,7 +11,7 @@ namespace pmon::ipc::intro void PopulateMetrics(ShmSegmentManager* pSegmentManager, struct IntrospectionRoot& root); void PopulateUnits(ShmSegmentManager* pSegmentManager, struct IntrospectionRoot& root); void PopulateGpuDevice(ShmSegmentManager* pSegmentManager, IntrospectionRoot& root, uint32_t deviceId, - PM_DEVICE_VENDOR vendor, const std::string& deviceName, const GpuTelemetryBitset& gpuCaps); + PM_DEVICE_VENDOR vendor, const std::string& deviceName, const MetricCapabilities& caps); void PopulateCpu(ShmSegmentManager* pSegmentManager, IntrospectionRoot& root, - PM_DEVICE_VENDOR vendor, const std::string& deviceName, const CpuTelemetryBitset& cpuCaps); + PM_DEVICE_VENDOR vendor, const std::string& deviceName, const MetricCapabilities& caps); } \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/source/MetricCapabilities.h b/IntelPresentMon/Interprocess/source/MetricCapabilities.h index 5f843f66..f6efbceb 100644 --- a/IntelPresentMon/Interprocess/source/MetricCapabilities.h +++ b/IntelPresentMon/Interprocess/source/MetricCapabilities.h @@ -13,6 +13,14 @@ namespace pmon::ipc void Set(PM_METRIC metricId, size_t arraySize); void Merge(const MetricCapabilities& capsToMerge); size_t Check(PM_METRIC metricId) const; + auto begin() const + { + return caps_.begin(); + } + auto end() const + { + return caps_.end(); + } private: std::unordered_map caps_; }; diff --git a/IntelPresentMon/Interprocess/source/metadata/EnumDeviceType.h b/IntelPresentMon/Interprocess/source/metadata/EnumDeviceType.h index 713fd832..6043eb48 100644 --- a/IntelPresentMon/Interprocess/source/metadata/EnumDeviceType.h +++ b/IntelPresentMon/Interprocess/source/metadata/EnumDeviceType.h @@ -3,5 +3,6 @@ // enum annotation (enum_name_fragment, key_name_fragment, name, short_name, description) #define ENUM_KEY_LIST_DEVICE_TYPE(X_) \ - X_(DEVICE_TYPE, INDEPENDENT, "Device Independent", "", "This device type is used for special device ID 0 which is reserved for metrics independent of any specific hardware device (e.g. FPS metrics)") \ - X_(DEVICE_TYPE, GRAPHICS_ADAPTER, "Graphics Adapter", "", "Graphics adapter or GPU device") \ No newline at end of file + X_(DEVICE_TYPE, INDEPENDENT, "Device Independent", "", "This device type is used for metrics independent of any specific hardware device (e.g. FPS metrics). Set device ID to 0.") \ + X_(DEVICE_TYPE, GRAPHICS_ADAPTER, "Graphics Adapter", "", "Graphics adapter or GPU device") \ + X_(DEVICE_TYPE, SYSTEM, "System", "", "Special virtual device represening core system components (such as the CPU). Set device ID to 0.") \ No newline at end of file diff --git a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h index 9f26e28c..075e0195 100644 --- a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h +++ b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h @@ -253,6 +253,7 @@ extern "C" { { PM_DEVICE_TYPE_INDEPENDENT, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, + PM_DEVICE_TYPE_SYSTEM, }; enum PM_METRIC_AVAILABILITY From b8af90585651b8a7cb4174fb83a078fd412e425b Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 3 Dec 2025 16:21:07 +0900 Subject: [PATCH 017/205] integration of new shm systems into ipc + service changes and min middle --- IntelPresentMon/CommonUtilities/Exception.h | 2 +- IntelPresentMon/Core/source/cli/CliOptions.h | 2 +- .../Interprocess/Interprocess.vcxproj | 8 +- .../Interprocess/Interprocess.vcxproj.filters | 20 +- .../Interprocess/source/DataStores.h | 20 +- .../Interprocess/source/HistoryRing.h | 2 +- .../Interprocess/source/Interprocess.cpp | 547 ++++++++++++------ .../Interprocess/source/Interprocess.h | 15 +- .../source/IntrospectionCapsLookup.h | 12 - .../source/IntrospectionPopulators.cpp | 7 +- .../source/IntrospectionTransfer.h | 27 + .../source/MetricCapabilities.cpp | 40 ++ .../Interprocess/source/MetricCapabilities.h | 6 +- .../source/MetricCapabilitiesShim.cpp | 129 +++++ .../source/MetricCapabilitiesShim.h | 10 + .../Interprocess/source/OwnedDataSegment.h | 82 +++ .../Interprocess/source/ShmNamer.cpp | 37 ++ .../Interprocess/source/ShmNamer.h | 23 + IntelPresentMon/Interprocess/source/ShmRing.h | 24 +- .../Interprocess/source/StreamedDataSegment.h | 27 - .../Interprocess/source/TelemetryMap.h | 37 +- .../Interprocess/source/ViewedDataSegment.h | 33 ++ .../Interprocess/source/metadata/MetricList.h | 16 +- IntelPresentMon/KernelProcess/winmain.cpp | 3 +- .../PresentMonMiddleware/ActionClient.h | 13 + .../ConcreteMiddleware.cpp | 9 +- .../ActionExecutionContext.h | 3 + .../PresentMonService/ActionServer.cpp | 6 +- .../PresentMonService/ActionServer.h | 3 +- .../PresentMonService/AllActions.h | 1 - .../PresentMonService/CliOptions.h | 3 +- .../PresentMonService/GlobalIdentifiers.h | 1 - .../PresentMonService/PMMainThread.cpp | 30 +- .../PresentMonService.vcxproj | 1 - .../PresentMonService.vcxproj.filters | 1 - .../acts/GetIntrospectionShmName.h | 50 -- .../PresentMonService/acts/OpenSession.h | 8 +- IntelPresentMon/Streamer/Streamer.cpp | 2 +- 38 files changed, 919 insertions(+), 341 deletions(-) create mode 100644 IntelPresentMon/Interprocess/source/MetricCapabilities.cpp create mode 100644 IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp create mode 100644 IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.h create mode 100644 IntelPresentMon/Interprocess/source/OwnedDataSegment.h create mode 100644 IntelPresentMon/Interprocess/source/ShmNamer.cpp create mode 100644 IntelPresentMon/Interprocess/source/ShmNamer.h delete mode 100644 IntelPresentMon/Interprocess/source/StreamedDataSegment.h create mode 100644 IntelPresentMon/Interprocess/source/ViewedDataSegment.h delete mode 100644 IntelPresentMon/PresentMonService/acts/GetIntrospectionShmName.h diff --git a/IntelPresentMon/CommonUtilities/Exception.h b/IntelPresentMon/CommonUtilities/Exception.h index 0e959640..a94262a0 100644 --- a/IntelPresentMon/CommonUtilities/Exception.h +++ b/IntelPresentMon/CommonUtilities/Exception.h @@ -34,7 +34,7 @@ namespace pmon::util void DoCapture_(Exception& e); - template + template auto Except(R&&...args) { E exception{ std::forward(args)... }; diff --git a/IntelPresentMon/Core/source/cli/CliOptions.h b/IntelPresentMon/Core/source/cli/CliOptions.h index 078ce0f0..1a1db1b5 100644 --- a/IntelPresentMon/Core/source/cli/CliOptions.h +++ b/IntelPresentMon/Core/source/cli/CliOptions.h @@ -21,7 +21,7 @@ namespace p2c::cli private: Group gd_{ this, "Debugging", "Aids in debugging this tool" }; public: Option controlPipe{ this, "--control-pipe", R"(\\.\pipe\pm-ctrl)", "Named pipe to connect to the service with" }; - Option shmName{ this, "--shm-name", "pm-intro-shm", "Shared memory to connect to the service with" }; + Option shmNamePrefix{ this, "--shm-name-prefix", "pm-shm", "Shared memory to connect to the service with" }; Option etwSessionName{ this, "--etw-session-name", "pm-child-etw-session", "ETW session name when lauching service as child" }; Flag svcAsChild{ this, "--svc-as-child", "Launch service as child console app" }; Flag traceExceptions{ this, "--trace-exceptions", "Add stack trace to all thrown exceptions (including SEH exceptions)" }; diff --git a/IntelPresentMon/Interprocess/Interprocess.vcxproj b/IntelPresentMon/Interprocess/Interprocess.vcxproj index 38614eaa..bcc0731b 100644 --- a/IntelPresentMon/Interprocess/Interprocess.vcxproj +++ b/IntelPresentMon/Interprocess/Interprocess.vcxproj @@ -15,6 +15,9 @@ + + + @@ -30,6 +33,7 @@ + @@ -59,9 +63,11 @@ + - + + diff --git a/IntelPresentMon/Interprocess/Interprocess.vcxproj.filters b/IntelPresentMon/Interprocess/Interprocess.vcxproj.filters index 05e59783..94250281 100644 --- a/IntelPresentMon/Interprocess/Interprocess.vcxproj.filters +++ b/IntelPresentMon/Interprocess/Interprocess.vcxproj.filters @@ -27,6 +27,15 @@ Source Files + + Source Files + + + Source Files + + + Source Files + @@ -158,11 +167,20 @@ Header Files - + Header Files Header Files + + Header Files + + + Header Files + + + Header Files + \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/source/DataStores.h b/IntelPresentMon/Interprocess/source/DataStores.h index a78b1bb2..0abf8e6f 100644 --- a/IntelPresentMon/Interprocess/source/DataStores.h +++ b/IntelPresentMon/Interprocess/source/DataStores.h @@ -16,16 +16,28 @@ namespace pmon::ipc { struct FrameDataStore { + static constexpr size_t virtualSegmentSize = 50'000'000; + FrameDataStore(ShmSegmentManager& segMan, size_t cap) + : + frameData{ cap, segMan.get_allocator() }, + statics{ .applicationName{ segMan.get_allocator() } } + {} struct Statics { uint32_t processId; ShmString applicationName; } statics; - ShmRing frameData; + ShmRing frameData; }; struct GpuDataStore { + static constexpr size_t virtualSegmentSize = 2'000'000; + GpuDataStore(ShmSegmentManager& segMan) + : + telemetryData{ segMan.get_allocator() }, + statics{ .name{ segMan.get_allocator() } } + {} struct Statics { PM_DEVICE_VENDOR vendor; @@ -39,6 +51,12 @@ namespace pmon::ipc struct SystemDataStore { + static constexpr size_t virtualSegmentSize = 1'000'000; + SystemDataStore(ShmSegmentManager& segMan) + : + telemetryData{ segMan.get_allocator() }, + statics{ .cpuName{ segMan.get_allocator() } } + {} struct Statics { PM_DEVICE_VENDOR cpuVendor; diff --git a/IntelPresentMon/Interprocess/source/HistoryRing.h b/IntelPresentMon/Interprocess/source/HistoryRing.h index 5c3db054..30c395e2 100644 --- a/IntelPresentMon/Interprocess/source/HistoryRing.h +++ b/IntelPresentMon/Interprocess/source/HistoryRing.h @@ -16,7 +16,7 @@ namespace pmon::ipc uint64_t timestamp; }; // functions - HistoryRing(size_t capacity, ShmVector::allocator_type& alloc) + HistoryRing(size_t capacity, ShmVector::allocator_type alloc) : samples_{ capacity, alloc } { diff --git a/IntelPresentMon/Interprocess/source/Interprocess.cpp b/IntelPresentMon/Interprocess/source/Interprocess.cpp index fa16485b..659a18d5 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.cpp +++ b/IntelPresentMon/Interprocess/source/Interprocess.cpp @@ -1,8 +1,12 @@ +#include "../../CommonUtilities/win/WinAPI.h" #include "Interprocess.h" #include "IntrospectionTransfer.h" #include "IntrospectionPopulators.h" #include "SharedMemoryTypes.h" +#include "OwnedDataSegment.h" +#include "ViewedDataSegment.h" #include "IntrospectionCloneAllocators.h" +#include "../../CommonUtilities/win/Security.h" #include #include #include @@ -10,191 +14,374 @@ #include "../../PresentMonService/GlobalIdentifiers.h" #include #include +#include +#include namespace pmon::ipc { - namespace bip = boost::interprocess; + namespace bip = boost::interprocess; - namespace - { - class CommsBase_ - { - protected: - static constexpr const char* defaultSegmentName_ = pmon::gid::defaultIntrospectionNsmName; - static constexpr const char* introspectionRootName_ = "in-root"; - static constexpr const char* introspectionMutexName_ = "in-mtx"; - static constexpr const char* introspectionSemaphoreName_ = "in-sem"; - }; + namespace + { + class CommsBase_ + { + protected: + static constexpr size_t frameRingSize_ = 5'000; + static constexpr size_t telemetryRingSize_ = 5'000; + static constexpr size_t introShmSize_ = 0x10'0000; + static constexpr const char* introspectionRootName_ = "in-root"; + static constexpr const char* introspectionMutexName_ = "in-mtx"; + static constexpr const char* introspectionSemaphoreName_ = "in-sem"; + }; - class ServiceComms_ : public ServiceComms, CommsBase_ - { - public: - ServiceComms_(std::optional sharedMemoryName) - : - shm_{ bip::create_only, sharedMemoryName.value_or(defaultSegmentName_).c_str(), - 0x10'0000, nullptr, Permissions_{} }, - pIntroMutex_{ ShmMakeNamedUnique( - introspectionMutexName_, shm_.get_segment_manager()) }, - pIntroSemaphore_{ ShmMakeNamedUnique( - introspectionSemaphoreName_, shm_.get_segment_manager(), 0) }, - pRoot_{ ShmMakeNamedUnique(introspectionRootName_, - shm_.get_segment_manager(), shm_.get_segment_manager()) } - { - PreInitializeIntrospection_(); - } - intro::IntrospectionRoot& GetIntrospectionRoot() override - { - return *pRoot_; - } - void RegisterGpuDevice(PM_DEVICE_VENDOR vendor, std::string deviceName, const MetricCapabilities& caps) override - { - // TODO: create data segment & rings - auto lck = LockIntrospectionMutexExclusive_(); - intro::PopulateGpuDevice(shm_.get_segment_manager(), *pRoot_, nextDeviceIndex_++, vendor, deviceName, caps); - } - void FinalizeGpuDevices() override - { - auto lck = LockIntrospectionMutexExclusive_(); - introGpuComplete_ = true; - if (introGpuComplete_ && introCpuComplete_) { - lck.unlock(); - FinalizeIntrospection_(); - } - } - void RegisterCpuDevice(PM_DEVICE_VENDOR vendor, std::string deviceName, const MetricCapabilities& caps) override - { - // TODO: create data segment & rings - auto lck = LockIntrospectionMutexExclusive_(); - intro::PopulateCpu(shm_.get_segment_manager(), *pRoot_, vendor, deviceName, caps); - introCpuComplete_ = true; - if (introGpuComplete_ && introCpuComplete_) { - lck.unlock(); - FinalizeIntrospection_(); - } - } - private: - // types - class Permissions_ - { - public: - Permissions_() - { - if (!ConvertStringSecurityDescriptorToSecurityDescriptorA( - "D:(A;OICI;GA;;;WD)", - SDDL_REVISION_1, &secAttr_.lpSecurityDescriptor, NULL)) { - throw std::runtime_error{ "Failed to create security descriptor for shared memory" }; - } - } - operator bip::permissions() - { - return bip::permissions{ &secAttr_ }; - } - private: - SECURITY_ATTRIBUTES secAttr_{ sizeof(secAttr_) }; - }; - // functions - void PreInitializeIntrospection_() - { - // populate introspection data structures at service-side - auto pSegmentManager = shm_.get_segment_manager(); - auto charAlloc = pSegmentManager->get_allocator(); - intro::PopulateEnums(pSegmentManager, *pRoot_); - intro::PopulateMetrics(pSegmentManager, *pRoot_); - intro::PopulateUnits(pSegmentManager, *pRoot_); - pRoot_->AddDevice(ShmMakeUnique(pSegmentManager, - 0, PM_DEVICE_TYPE_INDEPENDENT, PM_DEVICE_VENDOR_UNKNOWN, ShmString{ "Device-independent", charAlloc })); - } - void FinalizeIntrospection_() - { - // sort all ordered introspection entities in their pricipal containers - pRoot_->Sort(); - // release semaphore holdoff once construction is complete - for (int i = 0; i < 8; i++) { pIntroSemaphore_->post(); } - } - bip::scoped_lock LockIntrospectionMutexExclusive_() - { - const auto result = shm_.find(introspectionMutexName_); - if (!result.first) { - throw std::runtime_error{ "Failed to find introspection mutex in shared memory" }; - } - return bip::scoped_lock{ *result.first }; - } - // data - ShmSegment shm_; - ShmUniquePtr pIntroMutex_; - ShmUniquePtr pIntroSemaphore_; - ShmUniquePtr pRoot_; - uint32_t nextDeviceIndex_ = 1; - bool introGpuComplete_ = false; - bool introCpuComplete_ = false; - }; + class ServiceComms_ : public ServiceComms, CommsBase_ + { + public: + ServiceComms_(std::optional prefix) + : + namer_{ std::move(prefix) }, + shm_{ bip::create_only, namer_.MakeIntrospectionName().c_str(), + introShmSize_, nullptr, Permissions_{} }, + pIntroMutex_{ ShmMakeNamedUnique( + introspectionMutexName_, shm_.get_segment_manager()) }, + pIntroSemaphore_{ ShmMakeNamedUnique( + introspectionSemaphoreName_, shm_.get_segment_manager(), 0) }, + pRoot_{ ShmMakeNamedUnique(introspectionRootName_, + shm_.get_segment_manager(), shm_.get_segment_manager()) }, + systemShm_{ namer_.MakeSystemName(), + static_cast(Permissions_{}) } + { + PreInitializeIntrospection_(); + } + intro::IntrospectionRoot& GetIntrospectionRoot() override + { + return *pRoot_; + } + void RegisterGpuDevice(PM_DEVICE_VENDOR vendor, + std::string deviceName, + const MetricCapabilities& caps) override + { + auto lck = LockIntrospectionMutexExclusive_(); + const auto deviceId = nextDeviceIndex_++; + intro::PopulateGpuDevice( + shm_.get_segment_manager(), *pRoot_, + nextDeviceIndex_++, vendor, deviceName, caps + ); + // allocate map node and create shm segment + auto& gpuShm = gpuShms_.emplace( + std::piecewise_construct, + std::forward_as_tuple(deviceId), + std::forward_as_tuple(namer_.MakeGpuName(deviceId)) + ).first->second; + // populate rings based on caps + for (auto&& [metric, count] : caps) { + const auto dataType = + pRoot_->FindMetric(metric).GetDataTypeInfo().GetFrameType(); + gpuShm.GetStore().telemetryData.AddRing( + metric, telemetryRingSize_, count, dataType + ); + } + } + void FinalizeGpuDevices() override + { + auto lck = LockIntrospectionMutexExclusive_(); + introGpuComplete_ = true; + if (introGpuComplete_ && introCpuComplete_) { + lck.unlock(); + FinalizeIntrospection_(); + } + } + void RegisterCpuDevice(PM_DEVICE_VENDOR vendor, + std::string deviceName, + const MetricCapabilities& caps) override + { + auto lck = LockIntrospectionMutexExclusive_(); + intro::PopulateCpu( + shm_.get_segment_manager(), *pRoot_, vendor, std::move(deviceName), caps + ); + // populate rings based on caps + for (auto&& [metric, count] : caps) { + const auto dataType = + pRoot_->FindMetric(metric).GetDataTypeInfo().GetFrameType(); + systemShm_.GetStore().telemetryData.AddRing( + metric, telemetryRingSize_, count, dataType + ); + } + introCpuComplete_ = true; + if (introGpuComplete_ && introCpuComplete_) { + lck.unlock(); + FinalizeIntrospection_(); + } + } + const ShmNamer& GetNamer() const override + { + return namer_; + } + // data store access + std::shared_ptr> + MakeFrameDataSegment(uint32_t pid) override + { + // resolve out existing or new weak ptr, try and lock + auto& pWeak = frameShmWeaks_[pid]; + auto pFrameData = frameShmWeaks_[pid].lock(); + // if weak ptr was new (or expired), lock will not work and we need to construct + if (!pFrameData) { + // make a frame data store as shared ptr + pFrameData = std::make_shared>( + namer_.MakeFrameName(pid), + static_cast(Permissions_{}), + frameRingSize_ + ); + // store a weak reference + pWeak = pFrameData; + } + // remove stale elements to keep map lean + std::erase_if(frameShmWeaks_, + [](const auto& kv) { return kv.second.expired(); }); - class MiddlewareComms_ : public MiddlewareComms, CommsBase_ - { - public: - MiddlewareComms_(std::optional sharedMemoryName) - : - shm_{ bip::open_only, sharedMemoryName.value_or(defaultSegmentName_).c_str() } - {} - const PM_INTROSPECTION_ROOT* GetIntrospectionRoot(uint32_t timeoutMs) override - { - // make sure holdoff semaphore has been released - WaitOnIntrospectionHoldoff_(timeoutMs); - // acquire shared lock on introspection data - auto sharedLock = LockIntrospectionMutexForShare_(); - // find the introspection structure in shared memory - const auto result = shm_.find(introspectionRootName_); - if (!result.first) { - throw std::runtime_error{ "Failed to find introspection root in shared memory" }; - } - const auto& root = *result.first; - // probe allocator used to determine size of memory block required to hold the CAPI instrospection structure - intro::ProbeAllocator probeAllocator; - // this call to clone doesn't allocate of initialize any memory, the probe just determines required memory - root.ApiClone(probeAllocator); - // create actual allocator based on required size - ipc::intro::BlockAllocator blockAllocator{ probeAllocator.GetTotalSize() }; - // create the CAPI introspection struct on the heap, it is now the caller's responsibility to track this resource - return root.ApiClone(blockAllocator); - } - private: - // functions - void WaitOnIntrospectionHoldoff_(uint32_t timeoutMs) - { - using namespace std::chrono_literals; - using clock = std::chrono::high_resolution_clock; - const auto result = shm_.find(introspectionSemaphoreName_); - if (!result.first) { - throw std::runtime_error{ "Failed to find introspection semaphore in shared memory" }; - } - auto& sem = *result.first; - // wait for holdoff to be released (timeout after XXXms) - if (!sem.timed_wait(clock::now() + 1ms * timeoutMs)) { - throw std::runtime_error{ "timeout accessing introspection" }; - } - // return the slot we just took because holdoff should not limit entry once released - sem.post(); - } - bip::sharable_lock LockIntrospectionMutexForShare_() - { - const auto result = shm_.find(introspectionMutexName_); - if (!result.first) { - throw std::runtime_error{ "Failed to find introspection mutex in shared memory" }; - } - return bip::sharable_lock{ *result.first }; - } - // data - ShmSegment shm_; - }; - } + return pFrameData; + } + std::shared_ptr> GetFrameDataSegment(uint32_t pid) override + { + const auto it = frameShmWeaks_.find(pid); + if (it == frameShmWeaks_.end()) { + pmlog_error("No frame segment found").pmwatch(pid).raise(); + } + return it->second.lock(); + } + GpuDataStore& GetGpuDataStore(uint32_t deviceId) override + { + const auto it = gpuShms_.find(deviceId); + if (it == gpuShms_.end()) { + pmlog_error("No gpu segment found").pmwatch(deviceId).raise(); + } + return it->second.GetStore(); + } + SystemDataStore& GetSystemDataStore() override + { + return systemShm_.GetStore(); + } - std::unique_ptr MakeMiddlewareComms(std::optional sharedMemoryName) - { - return std::make_unique(std::move(sharedMemoryName)); - } + private: + // types + class Permissions_ + { + public: + Permissions_() + : + pSecDesc_{ util::win::MakeSecurityDescriptor("D:(A;OICI;GA;;;WD)") }, + secAttr_{ .nLength = sizeof(secAttr_), .lpSecurityDescriptor = pSecDesc_.get() } + {} + operator bip::permissions() + { + return bip::permissions{ &secAttr_ }; + } + private: + util::UniqueLocalPtr pSecDesc_; + SECURITY_ATTRIBUTES secAttr_{ sizeof(secAttr_) }; + }; + // functions + void PreInitializeIntrospection_() + { + // populate introspection data structures at service-side + auto pSegmentManager = shm_.get_segment_manager(); + auto charAlloc = pSegmentManager->get_allocator(); + intro::PopulateEnums(pSegmentManager, *pRoot_); + intro::PopulateMetrics(pSegmentManager, *pRoot_); + intro::PopulateUnits(pSegmentManager, *pRoot_); + pRoot_->AddDevice(ShmMakeUnique( + pSegmentManager, + 0, PM_DEVICE_TYPE_INDEPENDENT, PM_DEVICE_VENDOR_UNKNOWN, + ShmString{ "Device-independent", charAlloc } + )); + } + void FinalizeIntrospection_() + { + // sort all ordered introspection entities in their principal containers + pRoot_->Sort(); + // release semaphore holdoff once construction is complete + for (int i = 0; i < 8; i++) { pIntroSemaphore_->post(); } + } + bip::scoped_lock + LockIntrospectionMutexExclusive_() + { + const auto result = + shm_.find(introspectionMutexName_); + if (!result.first) { + throw std::runtime_error{ + "Failed to find introspection mutex in shared memory" + }; + } + return bip::scoped_lock{ *result.first }; + } - std::unique_ptr MakeServiceComms(std::optional sharedMemoryName) - { - return std::make_unique(std::move(sharedMemoryName)); - } -} \ No newline at end of file + // data + ShmNamer namer_; + ShmSegment shm_; + ShmUniquePtr pIntroMutex_; + ShmUniquePtr pIntroSemaphore_; + ShmUniquePtr pRoot_; + uint32_t nextDeviceIndex_ = 1; + bool introGpuComplete_ = false; + bool introCpuComplete_ = false; + + OwnedDataSegment systemShm_; + std::unordered_map>> frameShmWeaks_; + std::unordered_map> gpuShms_; + }; + + class MiddlewareComms_ : public MiddlewareComms, CommsBase_ + { + public: + MiddlewareComms_(std::optional prefix, std::string salt) + : + namer_{ std::move(prefix), std::move(salt) }, + shm_{ bip::open_only, namer_.MakeIntrospectionName().c_str() }, + systemShm_{ namer_.MakeSystemName() } // eager-load system segment + { + // Eager-load all GPU segments based on introspection + auto ids = GetGpuDeviceIds_(); + for (auto id : ids) { + gpuShms_.emplace( + std::piecewise_construct, + std::forward_as_tuple(id), + std::forward_as_tuple(namer_.MakeGpuName(id)) + ); + } + } + const PM_INTROSPECTION_ROOT* GetIntrospectionRoot(uint32_t timeoutMs) override + { + // make sure holdoff semaphore has been released + WaitOnIntrospectionHoldoff_(timeoutMs); + // acquire shared lock on introspection data + auto sharedLock = LockIntrospectionMutexForShare_(); + // find the introspection structure in shared memory + const auto result = shm_.find(introspectionRootName_); + if (!result.first) { + throw std::runtime_error{ "Failed to find introspection root in shared memory" }; + } + const auto& root = *result.first; + // probe allocator used to determine size of memory block required to hold + // the CAPI introspection structure + intro::ProbeAllocator probeAllocator; + // this call to clone doesn't allocate or initialize any memory, + // the probe just determines required memory + root.ApiClone(probeAllocator); + // create actual allocator based on required size + ipc::intro::BlockAllocator blockAllocator{ probeAllocator.GetTotalSize() }; + // create the CAPI introspection struct on the heap, it is now the caller's + // responsibility to track this resource + return root.ApiClone(blockAllocator); + } + void OpenFrameDataStore(uint32_t pid) override + { + // If already open, nothing to do + if (frameShms_.find(pid) != frameShms_.end()) { + return; + } + + const auto segName = namer_.MakeFrameName(pid); + frameShms_.emplace( + std::piecewise_construct, + std::forward_as_tuple(pid), + std::forward_as_tuple(segName) + ); + } + void CloseFrameDataStore(uint32_t pid) override + { + frameShms_.erase(pid); + } + // data store access + const FrameDataStore& GetFrameDataStore(uint32_t pid) const override + { + const auto it = frameShms_.find(pid); + if (it == frameShms_.end()) { + throw std::runtime_error{ "Frame data segment not open for this PID" }; + } + return it->second.GetStore(); + } + const GpuDataStore& GetGpuDataStore(uint32_t deviceId) const override + { + const auto it = gpuShms_.find(deviceId); + if (it == gpuShms_.end()) { + throw std::runtime_error{ "No GPU data segment found for this deviceId" }; + } + return it->second.GetStore(); + } + const SystemDataStore& GetSystemDataStore() const override + { + return systemShm_.GetStore(); + } + + private: + // functions + std::vector GetGpuDeviceIds_() + { + // make sure holdoff semaphore has been released + WaitOnIntrospectionHoldoff_(1500); + // acquire shared lock on introspection data + auto sharedLock = LockIntrospectionMutexForShare_(); + // find the introspection structure in shared memory + const auto result = shm_.find(introspectionRootName_); + if (!result.first) { + throw std::runtime_error{ "Failed to find introspection root in shared memory" }; + } + std::vector ids; + for (auto& p : result.first->GetDevices()) { + ids.push_back(p->GetId()); + } + return ids; + } + void WaitOnIntrospectionHoldoff_(uint32_t timeoutMs) + { + using namespace std::chrono_literals; + using clock = std::chrono::high_resolution_clock; + const auto result = shm_.find(introspectionSemaphoreName_); + if (!result.first) { + throw std::runtime_error{ + "Failed to find introspection semaphore in shared memory" + }; + } + auto& sem = *result.first; + // wait for holdoff to be released (timeout after timeoutMs) + if (!sem.timed_wait(clock::now() + 1ms * timeoutMs)) { + throw std::runtime_error{ "timeout accessing introspection" }; + } + // return the slot we just took because holdoff should not limit entry once released + sem.post(); + } + bip::sharable_lock + LockIntrospectionMutexForShare_() + { + const auto result = + shm_.find(introspectionMutexName_); + if (!result.first) { + throw std::runtime_error{ + "Failed to find introspection mutex in shared memory" + }; + } + return bip::sharable_lock{ *result.first }; + } + + // data + ShmNamer namer_; + ShmSegment shm_; // introspection shm + + ViewedDataSegment systemShm_; + std::unordered_map> gpuShms_; + std::unordered_map> frameShms_; + }; + } + + std::unique_ptr + MakeServiceComms(std::optional prefix) + { + return std::make_unique(std::move(prefix)); + } + + std::unique_ptr + MakeMiddlewareComms(std::string prefix, std::string salt) + { + return std::make_unique(std::move(prefix), std::move(salt)); + } +} diff --git a/IntelPresentMon/Interprocess/source/Interprocess.h b/IntelPresentMon/Interprocess/source/Interprocess.h index 62cd5cf2..c8984115 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.h +++ b/IntelPresentMon/Interprocess/source/Interprocess.h @@ -4,7 +4,9 @@ #include #include "../../PresentMonAPI2/PresentMonAPI.h" #include "DataStores.h" +#include "OwnedDataSegment.h" #include "MetricCapabilities.h" +#include "ShmNamer.h" namespace pmon::ipc { @@ -21,11 +23,11 @@ namespace pmon::ipc virtual void RegisterGpuDevice(PM_DEVICE_VENDOR vendor, std::string deviceName, const MetricCapabilities& caps) = 0; virtual void FinalizeGpuDevices() = 0; virtual void RegisterCpuDevice(PM_DEVICE_VENDOR vendor, std::string deviceName, const MetricCapabilities& caps) = 0; + virtual const ShmNamer& GetNamer() const = 0; // data store access - virtual FrameDataStore& MakeFrameDataStore(uint32_t pid) = 0; - virtual void RemoveFrameDataStore(uint32_t pid) = 0; - virtual FrameDataStore& GetFrameDataStore(uint32_t pid) = 0; + virtual std::shared_ptr> MakeFrameDataSegment(uint32_t pid) = 0; + virtual std::shared_ptr> GetFrameDataSegment(uint32_t pid) = 0; virtual GpuDataStore& GetGpuDataStore(uint32_t deviceId) = 0; virtual SystemDataStore& GetSystemDataStore() = 0; }; @@ -40,9 +42,10 @@ namespace pmon::ipc virtual const FrameDataStore& GetFrameDataStore(uint32_t pid) const = 0; virtual const GpuDataStore& GetGpuDataStore(uint32_t deviceId) const = 0; virtual const SystemDataStore& GetSystemDataStore() const = 0; - + virtual void OpenFrameDataStore(uint32_t pid) = 0; + virtual void CloseFrameDataStore(uint32_t pid) = 0; }; - std::unique_ptr MakeServiceComms(std::optional sharedMemoryName = {}); - std::unique_ptr MakeMiddlewareComms(std::optional sharedMemoryName = {}); + std::unique_ptr MakeServiceComms(std::optional prefix = {}); + std::unique_ptr MakeMiddlewareComms(std::string prefix, std::string salt); } \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h b/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h index 197958b6..fca73c79 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h +++ b/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h @@ -3,8 +3,6 @@ #include #include "../../ControlLib/PresentMonPowerTelemetry.h" #include "../../ControlLib/CpuTelemetryInfo.h" -#include "MetricCapabilities.h" - namespace pmon::ipc::intro @@ -76,14 +74,4 @@ namespace pmon::ipc::intro template concept IsGpuDeviceStaticMetric = requires { typename T::GpuDeviceStatic; }; template concept IsCpuMetric = requires { T::cpuCapBit; }; template concept IsManualDisableMetric = requires { typename T::ManualDisable; }; - - MetricCapabilities ConvertBitset(const GpuTelemetryCapBits& bits) - { - return {}; - } - - MetricCapabilities ConvertBitset(const CpuTelemetryCapBits& bits) - { - return {}; - } } \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/source/IntrospectionPopulators.cpp b/IntelPresentMon/Interprocess/source/IntrospectionPopulators.cpp index cf6ead9c..da4c26eb 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionPopulators.cpp +++ b/IntelPresentMon/Interprocess/source/IntrospectionPopulators.cpp @@ -1,6 +1,7 @@ #include "IntrospectionHelpers.h" #include "IntrospectionMetadata.h" #include "IntrospectionTransfer.h" +#include "IntrospectionCapsLookup.h" #include "MetricCapabilities.h" #include "../../CommonUtilities/log/Log.h" #include @@ -45,7 +46,7 @@ namespace pmon::ipc::intro PREFERRED_UNIT_LIST(X_PREF_UNIT) -#undef X_REG_METRIC +#undef X_PREF_UNIT #define X_REG_METRIC(metric, metric_type, unit, data_type_polled, data_type_frame, enum_id, device_type, ...) { \ auto pMetric = ShmMakeUnique(pSegmentManager, pSegmentManager, \ @@ -85,10 +86,10 @@ namespace pmon::ipc::intro if (i != root.GetMetrics().end()) { const auto availability = count ? PM_METRIC_AVAILABILITY_AVAILABLE : PM_METRIC_AVAILABILITY_UNAVAILABLE; - (*i)->AddDeviceMetricInfo(IntrospectionDeviceMetricInfo{ deviceId, availability, count }); + (*i)->AddDeviceMetricInfo(IntrospectionDeviceMetricInfo{ deviceId, availability, (uint32_t)count }); } else { - pmlog_error("Metric ID not found").pmwatch(metric); + pmlog_error("Metric ID not found").pmwatch((int)metric); } } } diff --git a/IntelPresentMon/Interprocess/source/IntrospectionTransfer.h b/IntelPresentMon/Interprocess/source/IntrospectionTransfer.h index de828893..324081a6 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionTransfer.h +++ b/IntelPresentMon/Interprocess/source/IntrospectionTransfer.h @@ -6,6 +6,7 @@ #include #include #include "../../PresentMonAPI2/PresentMonAPI.h" +#include "../../CommonUtilities/Exception.h" #include "SharedMemoryTypes.h" #include #include @@ -245,6 +246,10 @@ namespace pmon::ipc::intro { return id_ < rhs.id_; } + uint32_t GetId() const + { + return id_; + } private: uint32_t id_; PM_DEVICE_TYPE type_; @@ -314,6 +319,10 @@ namespace pmon::ipc::intro } return pSelf; } + PM_DATA_TYPE GetFrameType() const + { + return frameType_; + } private: PM_DATA_TYPE polledType_; PM_DATA_TYPE frameType_; @@ -447,6 +456,10 @@ namespace pmon::ipc::intro { return id_; } + const IntrospectionDataTypeInfo& GetDataTypeInfo() const + { + return *pTypeInfo_; + } bool operator<(const IntrospectionMetric& rhs) const { return id_ < rhs.id_; @@ -491,6 +504,20 @@ namespace pmon::ipc::intro { return metrics_.GetElements(); } + std::span> GetDevices() + { + return devices_.GetElements(); + } + IntrospectionMetric& FindMetric(PM_METRIC metric) + { + for (auto& p : GetMetrics()) { + if (p->GetId() == metric) { + return *p; + } + } + throw util::Except<>("Metric ID not found in introspection"); + } + using ApiType = PM_INTROSPECTION_ROOT; template const ApiType* ApiClone(V voidAlloc) const diff --git a/IntelPresentMon/Interprocess/source/MetricCapabilities.cpp b/IntelPresentMon/Interprocess/source/MetricCapabilities.cpp new file mode 100644 index 00000000..e1a2e0ab --- /dev/null +++ b/IntelPresentMon/Interprocess/source/MetricCapabilities.cpp @@ -0,0 +1,40 @@ +#include "MetricCapabilities.h" + +namespace pmon::ipc +{ + void MetricCapabilities::Set(PM_METRIC metricId, size_t arraySize) + { + if (arraySize == 0) { + // Zero-sized capabilities are effectively "not available"; + // ignore rather than storing. + return; + } + + auto it = caps_.find(metricId); + if (it == caps_.end()) { + caps_.emplace(metricId, arraySize); + } + else { + it->second = arraySize; + } + } + + void MetricCapabilities::Merge(const MetricCapabilities& capsToMerge) + { + // Union of capabilities; for overlapping metrics overwrite + for (const auto& kv : capsToMerge.caps_) { + const auto metricId = kv.first; + const auto arraySize = kv.second; + Set(metricId, arraySize); + } + } + + size_t MetricCapabilities::Check(PM_METRIC metricId) const noexcept + { + auto it = caps_.find(metricId); + if (it == caps_.end()) { + return 0; // not present / not available + } + return it->second; + } +} diff --git a/IntelPresentMon/Interprocess/source/MetricCapabilities.h b/IntelPresentMon/Interprocess/source/MetricCapabilities.h index f6efbceb..dfc410cf 100644 --- a/IntelPresentMon/Interprocess/source/MetricCapabilities.h +++ b/IntelPresentMon/Interprocess/source/MetricCapabilities.h @@ -12,12 +12,12 @@ namespace pmon::ipc public: void Set(PM_METRIC metricId, size_t arraySize); void Merge(const MetricCapabilities& capsToMerge); - size_t Check(PM_METRIC metricId) const; - auto begin() const + size_t Check(PM_METRIC metricId) const noexcept; + auto begin() const noexcept { return caps_.begin(); } - auto end() const + auto end() const noexcept { return caps_.end(); } diff --git a/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp b/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp new file mode 100644 index 00000000..427d2210 --- /dev/null +++ b/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp @@ -0,0 +1,129 @@ +#include "MetricCapabilitiesShim.h" +#include "IntrospectionCapsLookup.h" +#include "../../CommonUtilities/third/reflect.hpp" + +#include + +namespace pmon::ipc::intro +{ + namespace detail + { + using MetricEnum = PM_METRIC; + using MetricUnderlying = std::underlying_type_t; + + // Probe underlying values in [0, MaxMetricUnderlying) + constexpr MetricUnderlying MaxMetricUnderlying = 256; + + // Is this underlying value actually one of the enum's declared enumerators? + template + consteval bool IsValidMetricEnum() + { + constexpr auto e = static_cast(Value); + constexpr auto name = reflect::enum_name(e); + return !name.empty(); + } + + // xxxCapBits is std::bitset + template + bool HasCap(const BitsType& bits, Index index) + { + return bits.test(static_cast(index)); + } + + // GPU per-metric accumulation (only instantiated for valid enum values) + template + void AccumulateGpuCapability(MetricCapabilities& caps, + const GpuTelemetryBitset& bits) + { + constexpr auto metricEnum = static_cast(Value); + using Lookup = IntrospectionCapsLookup; + + // Single GPU capability bit -> metric present if bit set + if constexpr (IsGpuDeviceMetric) { + if (HasCap(bits, Lookup::gpuCapBit)) { + caps.Set(metricEnum, 1); + } + } + + // Array GPU capability bits (fan speeds, etc.) + if constexpr (IsGpuDeviceMetricArray) { + std::size_t count = 0; + for (auto flag : Lookup::gpuCapBitArray) { + if (HasCap(bits, flag)) { + ++count; + } + } + if (count > 0) { + caps.Set(metricEnum, count); + } + } + + // Static GPU metrics: name/vendor/etc. + if constexpr (IsGpuDeviceStaticMetric) { + caps.Set(metricEnum, 1); + } + } + + // CPU per-metric accumulation (only instantiated for valid enum values) + template + void AccumulateCpuCapability(MetricCapabilities& caps, + const CpuTelemetryBitset& bits) + { + constexpr auto metricEnum = static_cast(Value); + using Lookup = IntrospectionCapsLookup; + + // CPU metrics gated by a capability bit + if constexpr (IsCpuMetric) { + if (HasCap(bits, Lookup::cpuCapBit)) { + caps.Set(metricEnum, 1); + } + } + + // Metrics that exist but are intended for manual disable by default + if constexpr (IsManualDisableMetric) { + caps.Set(metricEnum, 1); + } + } + + // Compile-time recursion over underlying values [0, MaxMetricUnderlying) + // GPU: only call AccumulateGpuCapability when Value is a real enumerator + template + void ConvertGpuBitsRecursive(MetricCapabilities& caps, + const GpuTelemetryBitset& bits) + { + if constexpr (Value < MaxMetricUnderlying) { + if constexpr (IsValidMetricEnum()) { + AccumulateGpuCapability(caps, bits); + } + ConvertGpuBitsRecursive(caps, bits); + } + } + + // CPU: same pattern + template + void ConvertCpuBitsRecursive(MetricCapabilities& caps, + const CpuTelemetryBitset& bits) + { + if constexpr (Value < MaxMetricUnderlying) { + if constexpr (IsValidMetricEnum()) { + AccumulateCpuCapability(caps, bits); + } + ConvertCpuBitsRecursive(caps, bits); + } + } + } // namespace detail + + MetricCapabilities ConvertBitset(const GpuTelemetryBitset& bits) + { + MetricCapabilities caps; + detail::ConvertGpuBitsRecursive<0>(caps, bits); + return caps; + } + + MetricCapabilities ConvertBitset(const CpuTelemetryBitset& bits) + { + MetricCapabilities caps; + detail::ConvertCpuBitsRecursive<0>(caps, bits); + return caps; + } +} diff --git a/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.h b/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.h new file mode 100644 index 00000000..d0fba726 --- /dev/null +++ b/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.h @@ -0,0 +1,10 @@ +#pragma once +#include "MetricCapabilities.h" +#include "../../ControlLib/PresentMonPowerTelemetry.h" +#include "../../ControlLib/CpuTelemetryInfo.h" + +namespace pmon::ipc::intro +{ + MetricCapabilities ConvertBitset(const GpuTelemetryBitset& bits); + MetricCapabilities ConvertBitset(const CpuTelemetryBitset& bits); +} \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/source/OwnedDataSegment.h b/IntelPresentMon/Interprocess/source/OwnedDataSegment.h new file mode 100644 index 00000000..d8371990 --- /dev/null +++ b/IntelPresentMon/Interprocess/source/OwnedDataSegment.h @@ -0,0 +1,82 @@ +#pragma once +#include "SharedMemoryTypes.h" +#include "MetricCapabilities.h" +#include + +namespace pmon::ipc +{ + // manages shared memory segment and hosts data store T + template + class OwnedDataSegment + { + public: + // No ACL version + // For FrameDataStore: pass (ringCapacity) + // For GpuDataStore/SystemDataStore: pass no extra args + template + OwnedDataSegment(const std::string& segmentName, + StoreArgs&&... storeArgs) + : + shm_{ bip::create_only, segmentName.c_str(), T::virtualSegmentSize }, + pData_{ MakeStore_(std::forward(storeArgs)...) } + {} + + // ACL version + template + OwnedDataSegment(const std::string& segmentName, + const bip::permissions& perms, + StoreArgs&&... storeArgs) + : + shm_{ bip::create_only, segmentName.c_str(), T::virtualSegmentSize, + nullptr, perms }, + pData_{ MakeStore_(std::forward(storeArgs)...) } + {} + + T& GetStore() { return *pData_; } + const T& GetStore() const { return *pData_; } + + private: + static constexpr const char* name_ = "seg-dat"; + + // Helper to enforce a single size_t-like argument for FrameDataStore + template + static size_t GetFrameCapacity_(Arg&& arg) + { + static_assert(std::is_convertible_v, + "FrameDataStore capacity must be convertible to size_t"); + return static_cast(std::forward(arg)); + } + + // Factory that constructs the store in shared memory with the right allocator + template + ShmUniquePtr MakeStore_(StoreArgs&&... storeArgs) + { + // FrameDataStore: expects (ShmAllocator&, size_t cap) + if constexpr (std::is_same_v) { + static_assert(sizeof...(StoreArgs) == 1, + "OwnedStreamedSegment requires a single ring capacity argument"); + return ShmMakeNamedUnique( + name_, + shm_.get_segment_manager(), + *shm_.get_segment_manager(), + GetFrameCapacity_(std::forward(storeArgs)...) + ); + } + // Telemetry stores: expect TelemetryMap::AllocatorType& only + else if constexpr (std::is_same_v || + std::is_same_v) { + static_assert(sizeof...(StoreArgs) == 0, + "OwnedStreamedSegment " + "does not take extra ctor args"); + return ShmMakeNamedUnique( + name_, + shm_.get_segment_manager(), + *shm_.get_segment_manager() + ); + } + } + + ShmSegment shm_; + ShmUniquePtr pData_; + }; +} diff --git a/IntelPresentMon/Interprocess/source/ShmNamer.cpp b/IntelPresentMon/Interprocess/source/ShmNamer.cpp new file mode 100644 index 00000000..8df7a5fa --- /dev/null +++ b/IntelPresentMon/Interprocess/source/ShmNamer.cpp @@ -0,0 +1,37 @@ +#pragma once +#include "ShmNamer.h" +#include +#include + +namespace pmon::ipc +{ + ShmNamer::ShmNamer(std::optional salt, std::optional customPrefix) + : + salt_{ salt.value_or(std::format("{:08x}", std::random_device{}())) }, + prefix_{ customPrefix.value_or(R"(Global\pm2sh)") } + {} + std::string ShmNamer::MakeIntrospectionName() const + { + return std::format("{}_{}_int", prefix_, salt_); + } + std::string ShmNamer::MakeSystemName() const + { + return std::format("{}_{}_sys", prefix_, salt_); + } + std::string ShmNamer::MakeGpuName(uint32_t deviceId) const + { + return std::format("{}_{}_gpu_{}", prefix_, salt_, deviceId); + } + std::string ShmNamer::MakeFrameName(uint32_t pid) const + { + return std::format("{}_{}_tgt_{}", prefix_, salt_, pid); + } + const std::string& ShmNamer::GetSalt() const + { + return salt_; + } + const std::string& ShmNamer::GetPrefix() const + { + return prefix_; + } +} \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/source/ShmNamer.h b/IntelPresentMon/Interprocess/source/ShmNamer.h new file mode 100644 index 00000000..5b04394b --- /dev/null +++ b/IntelPresentMon/Interprocess/source/ShmNamer.h @@ -0,0 +1,23 @@ +#pragma once +#include +#include + + +namespace pmon::ipc +{ + // encodes the conventions used to name shared memory during creation and opening + class ShmNamer + { + public: + ShmNamer(std::optional salt = {}, std::optional customPrefix = {}); + std::string MakeIntrospectionName() const; + std::string MakeSystemName() const; + std::string MakeGpuName(uint32_t deviceId) const; + std::string MakeFrameName(uint32_t pid) const; + const std::string& GetSalt() const; + const std::string& GetPrefix() const; + private: + std::string salt_; + std::string prefix_; + }; +} \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/source/ShmRing.h b/IntelPresentMon/Interprocess/source/ShmRing.h index 8bf5d583..22d1a384 100644 --- a/IntelPresentMon/Interprocess/source/ShmRing.h +++ b/IntelPresentMon/Interprocess/source/ShmRing.h @@ -10,7 +10,7 @@ namespace pmon::ipc class ShmRing { public: - ShmRing(size_t capacity, ShmVector::allocator_type& alloc) + ShmRing(size_t capacity, ShmVector::allocator_type alloc) : data_{ capacity, alloc } { @@ -18,6 +18,28 @@ namespace pmon::ipc throw std::logic_error{ "The capacity of a ShmRing must be at least double its ReadBufferSize" }; } } + + + ShmRing(const ShmRing&) = delete; + ShmRing & operator=(const ShmRing&) = delete; + // we need to enable move for use inside vectors + // not enabled by default because of the atomic member + ShmRing(ShmRing&& other) + : + data_{ std::move(other.data_) }, + nextWriteSerial_{ other.nextWriteSerial_.load() } + {} + ShmRing& operator=(ShmRing&& other) + { + if (this != &other) { + data_ = std::move(other.data_); + nextWriteSerial_ = other.nextWriteSerial_.load(); + } + return *this; + } + ~ShmRing() = default; + + void Push(const T& val) { data_[IndexFromSerial_(nextWriteSerial_)] = val; diff --git a/IntelPresentMon/Interprocess/source/StreamedDataSegment.h b/IntelPresentMon/Interprocess/source/StreamedDataSegment.h deleted file mode 100644 index 4af27972..00000000 --- a/IntelPresentMon/Interprocess/source/StreamedDataSegment.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once -#include "SharedMemoryTypes.h" - -namespace pmon::ipc -{ - // manages shared memory segment and hosts data store T - template - class StreamedDataSegment - { - public: - StreamedDataSegment(const std::string& segmentName, size_t size) - : - shm_{ bip::create_only } - {} - T& GetStore() - { - return *pData_; - } - const T& GetStore() const - { - return *pData_; - } - private: - ShmSegment shm_; - ShmUniquePtr pData_; - }; -} \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/source/TelemetryMap.h b/IntelPresentMon/Interprocess/source/TelemetryMap.h index a10ae61b..1431ec5b 100644 --- a/IntelPresentMon/Interprocess/source/TelemetryMap.h +++ b/IntelPresentMon/Interprocess/source/TelemetryMap.h @@ -1,6 +1,7 @@ #pragma once #include "SharedMemoryTypes.h" #include "HistoryRing.h" +#include "../../PresentMonAPI2/PresentMonAPI.h" #include namespace pmon::ipc @@ -11,15 +12,30 @@ namespace pmon::ipc template using HistoryRingVect = ShmVector>; using MapValueType = std::variant, HistoryRingVect, HistoryRingVect>; - using MapType = ShmMap; + using MapType = ShmMap; public: - TelemetryMap(const MapType::allocator_type& alloc) + using AllocatorType = MapType::allocator_type; + TelemetryMap(AllocatorType alloc) : ringMap_{ alloc } + {} + void AddRing(PM_METRIC id, size_t size, size_t count, PM_DATA_TYPE type) { + switch (type) { + case PM_DATA_TYPE_DOUBLE: + AddRing(id, size, count); + break; + case PM_DATA_TYPE_UINT64: + AddRing(id, size, count); + break; + case PM_DATA_TYPE_BOOL: + AddRing(id, size, count); + break; + default: throw util::Except<>("Unsupported ring type for TelemetryMap"); + } } template - void AddRing(int id, size_t size, size_t count) + void AddRing(PM_METRIC id, size_t size, size_t count) { // extra guard of misuse at compile time static_assert( @@ -29,11 +45,10 @@ namespace pmon::ipc "Unsupported ring type for TelemetryMap" ); - using RingType = HistoryRing; - using RingAlloc = typename MapType::allocator_type::template rebind::other; + using RingAlloc = typename MapType::allocator_type::template rebind>::other; - // Construct an allocator for ShmRing from the map's allocator - HistoryRingVect ringAlloc(ringMap_.get_allocator()); + // Construct an allocator for HistoryRing from the map's allocator + RingAlloc ringAlloc(ringMap_.get_allocator()); // Insert (or get existing) entry for this id, constructing the correct variant alternative auto [it, inserted] = ringMap_.emplace( @@ -58,20 +73,20 @@ namespace pmon::ipc } } template - HistoryRingVect& FindRing(int id) + HistoryRingVect& FindRing(PM_METRIC id) { return std::get>(FindRingVariant(id)); } template - const HistoryRingVect& FindRing(int id) const + const HistoryRingVect& FindRing(PM_METRIC id) const { return std::get>(FindRingVariant(id)); } - MapValueType& FindRingVariant(int id) + MapValueType& FindRingVariant(PM_METRIC id) { return ringMap_.at(id); } - const MapValueType& FindRingVariant(int id) const + const MapValueType& FindRingVariant(PM_METRIC id) const { return ringMap_.at(id); } diff --git a/IntelPresentMon/Interprocess/source/ViewedDataSegment.h b/IntelPresentMon/Interprocess/source/ViewedDataSegment.h new file mode 100644 index 00000000..ca370e17 --- /dev/null +++ b/IntelPresentMon/Interprocess/source/ViewedDataSegment.h @@ -0,0 +1,33 @@ +#pragma once +#include "SharedMemoryTypes.h" + +namespace pmon::ipc +{ + namespace bip = boost::interprocess; + + // Non-owning view over a shared memory segment that hosts a data store T. + // Opens an existing managed segment and finds the named T instance inside. + template + class ViewedDataSegment + { + public: + explicit ViewedDataSegment(const std::string& segmentName) + : + shm_{ bip::open_only, segmentName.c_str() } + { + auto result = shm_.find(name_); + if (!result.first) { + throw std::runtime_error("Failed to find data store in shared memory"); + } + pData_ = result.first; // non-owning + } + + const T& GetStore() const { return *pData_; } + + private: + static constexpr const char* name_ = "seg-dat"; + + ShmSegment shm_; + T* pData_ = nullptr; // non-owning + }; +} diff --git a/IntelPresentMon/Interprocess/source/metadata/MetricList.h b/IntelPresentMon/Interprocess/source/metadata/MetricList.h index e945a12d..4398b588 100644 --- a/IntelPresentMon/Interprocess/source/metadata/MetricList.h +++ b/IntelPresentMon/Interprocess/source/metadata/MetricList.h @@ -9,8 +9,8 @@ X_(PM_METRIC_SWAP_CHAIN_ADDRESS, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_UINT64, PM_DATA_TYPE_UINT64, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_MID_POINT) \ X_(PM_METRIC_GPU_VENDOR, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_DEVICE_VENDOR, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, PM_STAT_NONE) \ X_(PM_METRIC_GPU_NAME, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_STRING, PM_DATA_TYPE_STRING, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, PM_STAT_NONE) \ - X_(PM_METRIC_CPU_VENDOR, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_DEVICE_VENDOR, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ - X_(PM_METRIC_CPU_NAME, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_STRING, PM_DATA_TYPE_STRING, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ + X_(PM_METRIC_CPU_VENDOR, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_DEVICE_VENDOR, PM_DEVICE_TYPE_SYSTEM, PM_STAT_NONE) \ + X_(PM_METRIC_CPU_NAME, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_STRING, PM_DATA_TYPE_STRING, 0, PM_DEVICE_TYPE_SYSTEM, PM_STAT_NONE) \ X_(PM_METRIC_CPU_START_TIME, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ X_(PM_METRIC_CPU_START_QPC, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_QPC, PM_DATA_TYPE_UINT64, PM_DATA_TYPE_UINT64, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ X_(PM_METRIC_CPU_FRAME_TIME, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ @@ -65,12 +65,12 @@ X_(PM_METRIC_GPU_MEM_CURRENT_LIMITED, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_BOOLEAN, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_BOOL, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ X_(PM_METRIC_GPU_MEM_VOLTAGE_LIMITED, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_BOOLEAN, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_BOOL, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ X_(PM_METRIC_GPU_MEM_UTILIZATION_LIMITED, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_BOOLEAN, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_BOOL, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ - X_(PM_METRIC_CPU_UTILIZATION, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_PERCENT, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ - X_(PM_METRIC_CPU_POWER_LIMIT, PM_METRIC_TYPE_STATIC, PM_UNIT_WATTS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, PM_STAT_MID_POINT) \ - X_(PM_METRIC_CPU_POWER, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_WATTS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ - X_(PM_METRIC_CPU_TEMPERATURE, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_CELSIUS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ - X_(PM_METRIC_CPU_FREQUENCY, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MEGAHERTZ, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ - X_(PM_METRIC_CPU_CORE_UTILITY, PM_METRIC_TYPE_DYNAMIC, PM_UNIT_PERCENT, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_VOID, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ + X_(PM_METRIC_CPU_UTILIZATION, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_PERCENT, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, FULL_STATS) \ + X_(PM_METRIC_CPU_POWER_LIMIT, PM_METRIC_TYPE_STATIC, PM_UNIT_WATTS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, PM_STAT_MID_POINT) \ + X_(PM_METRIC_CPU_POWER, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_WATTS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, FULL_STATS) \ + X_(PM_METRIC_CPU_TEMPERATURE, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_CELSIUS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, FULL_STATS) \ + X_(PM_METRIC_CPU_FREQUENCY, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MEGAHERTZ, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, FULL_STATS) \ + X_(PM_METRIC_CPU_CORE_UTILITY, PM_METRIC_TYPE_DYNAMIC, PM_UNIT_PERCENT, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_VOID, 0, PM_DEVICE_TYPE_SYSTEM, FULL_STATS) \ X_(PM_METRIC_ALL_INPUT_TO_PHOTON_LATENCY, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NON_ZERO_AVG, PM_STAT_PERCENTILE_01, PM_STAT_PERCENTILE_05, PM_STAT_PERCENTILE_10, PM_STAT_MAX) \ X_(PM_METRIC_INSTRUMENTED_LATENCY, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_GPU_EFFECTIVE_FREQUENCY, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MEGAHERTZ, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ diff --git a/IntelPresentMon/KernelProcess/winmain.cpp b/IntelPresentMon/KernelProcess/winmain.cpp index 8bb1d5dc..f52b8731 100644 --- a/IntelPresentMon/KernelProcess/winmain.cpp +++ b/IntelPresentMon/KernelProcess/winmain.cpp @@ -213,8 +213,7 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi // compile fixed CLI options auto args = std::vector{ "--control-pipe"s, *opt.controlPipe, - "--nsm-prefix"s, "pm-frame-nsm"s, - "--intro-nsm"s, *opt.shmName, + "--shm-name-prefix"s, *opt.shmNamePrefix, "--etw-session-name"s, *opt.etwSessionName, "--log-level"s, util::log::GetLevelName(util::log::GlobalPolicy::Get().GetLogLevel()), "--log-pipe-name"s, logSvcPipe, diff --git a/IntelPresentMon/PresentMonMiddleware/ActionClient.h b/IntelPresentMon/PresentMonMiddleware/ActionClient.h index 640c97d0..a5198f48 100644 --- a/IntelPresentMon/PresentMonMiddleware/ActionClient.h +++ b/IntelPresentMon/PresentMonMiddleware/ActionClient.h @@ -52,8 +52,21 @@ namespace pmon::mid .pmwatch(res.serviceBuildConfig).pmwatch(bid::BuildIdConfig()); throw Except(PM_STATUS_MIDDLEWARE_SERVICE_MISMATCH); } + shmPrefix_ = res.shmPrefix; + shmSalt_ = res.shmSalt; pmlog_info(std::format("Opened session with server, pid = [{}]", res.servicePid)); EstablishSession_(res.servicePid); } + const std::string& GetShmPrefix() const + { + return shmPrefix_; + } + const std::string& GetShmSalt() const + { + return shmSalt_; + } + private: + std::string shmPrefix_; + std::string shmSalt_; }; } \ No newline at end of file diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index b21e54e4..91f40e00 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -46,7 +46,7 @@ namespace pmon::mid const auto pipeName = pipeNameOverride.transform(&std::string::c_str) .value_or(pmon::gid::defaultControlPipeName); - // Try to open a named pipe; wait for it, if necessary + // Try to open a named pipe to action server; wait for it, if necessary try { if (!pipe::DuplexPipe::WaitForAvailability(pipeName, 500)) { throw std::runtime_error{ "Timeout waiting for service action pipe to become available" }; @@ -60,11 +60,8 @@ namespace pmon::mid clientProcessId = GetCurrentProcessId(); - // discover introspection shm name - auto res = pActionClient->DispatchSync(GetIntrospectionShmName::Params{}); - - // connect to the introspection nsm - pComms = ipc::MakeMiddlewareComms(std::move(res.name)); + // connect to the shm server + pComms = ipc::MakeMiddlewareComms(pActionClient->GetShmPrefix(), pActionClient->GetShmSalt()); // Get the introspection data try { diff --git a/IntelPresentMon/PresentMonService/ActionExecutionContext.h b/IntelPresentMon/PresentMonService/ActionExecutionContext.h index 5f70f595..ff9c1b55 100644 --- a/IntelPresentMon/PresentMonService/ActionExecutionContext.h +++ b/IntelPresentMon/PresentMonService/ActionExecutionContext.h @@ -1,5 +1,6 @@ #pragma once #include "../Interprocess/source/act/SymmetricActionConnector.h" +#include "../Interprocess/source/ShmNamer.h" #include #include #include @@ -39,6 +40,8 @@ namespace pmon::svc::acts Service* pSvc = nullptr; PresentMon* pPmon = nullptr; const std::unordered_map* pSessionMap = nullptr; + std::string shmPrefix; + std::string shmSalt; std::optional responseWriteTimeoutMs; // functions diff --git a/IntelPresentMon/PresentMonService/ActionServer.cpp b/IntelPresentMon/PresentMonService/ActionServer.cpp index 81f44cd1..3ef78b86 100644 --- a/IntelPresentMon/PresentMonService/ActionServer.cpp +++ b/IntelPresentMon/PresentMonService/ActionServer.cpp @@ -13,14 +13,16 @@ namespace pmon::svc using namespace util; using namespace ipc; - ActionServer::ActionServer(Service* pSvc, PresentMon* pPmon, std::optional pipeName) + ActionServer::ActionServer(Service* pSvc, PresentMon* pPmon, std::string shmPrefix, + std::string shmSalt, std::optional pipeName) { // if we have a pipe name override, that indicates we don't need special permissions auto sec = pipe::DuplexPipe::GetSecurityString(pipeName ? pipe::SecurityMode::Child : pipe::SecurityMode::Service); // construct (and start) the server pImpl_ = std::make_shared>( - acts::ActionExecutionContext{ .pSvc = pSvc, .pPmon = pPmon }, + acts::ActionExecutionContext{ .pSvc = pSvc, .pPmon = pPmon, + .shmPrefix = std::move(shmPrefix), .shmSalt = std::move(shmSalt) }, pipeName.value_or(gid::defaultControlPipeName), 2, std::move(sec) ); diff --git a/IntelPresentMon/PresentMonService/ActionServer.h b/IntelPresentMon/PresentMonService/ActionServer.h index 3fe41c43..b54c2568 100644 --- a/IntelPresentMon/PresentMonService/ActionServer.h +++ b/IntelPresentMon/PresentMonService/ActionServer.h @@ -12,7 +12,8 @@ namespace pmon::svc class ActionServer { public: - ActionServer(Service* pSvc, PresentMon* pPmon, std::optional pipeName); + ActionServer(Service* pSvc, PresentMon* pPmon, std::string shmPrefix, + std::string shmSalt, std::optional pipeName); ~ActionServer() = default; ActionServer(const ActionServer&) = delete; ActionServer& operator=(const ActionServer&) = delete; diff --git a/IntelPresentMon/PresentMonService/AllActions.h b/IntelPresentMon/PresentMonService/AllActions.h index b0ddb7db..912a3baa 100644 --- a/IntelPresentMon/PresentMonService/AllActions.h +++ b/IntelPresentMon/PresentMonService/AllActions.h @@ -2,7 +2,6 @@ #pragma once #include "acts/EnumerateAdapters.h" #include "acts/FinishEtlLogging.h" -#include "acts/GetIntrospectionShmName.h" #include "acts/GetStaticCpuMetrics.h" #include "acts/OpenSession.h" #include "acts/SelectAdapter.h" diff --git a/IntelPresentMon/PresentMonService/CliOptions.h b/IntelPresentMon/PresentMonService/CliOptions.h index 9cbd6f83..6c1d4ab9 100644 --- a/IntelPresentMon/PresentMonService/CliOptions.h +++ b/IntelPresentMon/PresentMonService/CliOptions.h @@ -17,8 +17,7 @@ namespace clio private: Group gc_{ this, "Connection", "Control client connection" }; public: Option etwSessionName{ this, "--etw-session-name", "PMService", "Name to use when creating the ETW session" }; Option controlPipe{ this, "--control-pipe", "", "Name of the named pipe to use for the client-service control channel" }; - Option nsmPrefix{ this, "--nsm-prefix", "", "Prefix to use when naming named shared memory segments created for frame data circular buffers" }; - Option introNsm{ this, "--intro-nsm", "", "Name of the NSM used for introspection data" }; + Option shmNamePrefix{ this, "--shm-name-prefix", "", "Prefix to use when naming shared memory segments" }; private: Group gd_{ this, "Debugging", "Aids in debugging this tool" }; public: Flag debug{ this, "--debug,-d", "Stall service by running in a loop after startup waiting for debugger to connect" }; diff --git a/IntelPresentMon/PresentMonService/GlobalIdentifiers.h b/IntelPresentMon/PresentMonService/GlobalIdentifiers.h index 943f3dd1..de192e34 100644 --- a/IntelPresentMon/PresentMonService/GlobalIdentifiers.h +++ b/IntelPresentMon/PresentMonService/GlobalIdentifiers.h @@ -3,7 +3,6 @@ namespace pmon::gid { inline constexpr const char* defaultControlPipeName = R"(\\.\pipe\sharedpresentmonsvcnamedpipe)"; - inline constexpr const char* defaultIntrospectionNsmName = R"(Global\shared_pm2_bip_shm)"; inline constexpr const char* defaultLogPipeBaseName = "shared-pm2-svc-log"; inline constexpr const wchar_t* registryPath = LR"(SOFTWARE\INTEL\PresentMon\Service)"; inline constexpr const char* middlewarePathKey = "sharedMiddlewarePath"; diff --git a/IntelPresentMon/PresentMonService/PMMainThread.cpp b/IntelPresentMon/PresentMonService/PMMainThread.cpp index e3787fde..1beb1920 100644 --- a/IntelPresentMon/PresentMonService/PMMainThread.cpp +++ b/IntelPresentMon/PresentMonService/PMMainThread.cpp @@ -9,6 +9,8 @@ #include "..\PresentMonUtils\StringUtils.h" #include #include "../Interprocess/source/Interprocess.h" +#include "../Interprocess/source/ShmNamer.h" +#include "../Interprocess/source/MetricCapabilitiesShim.h" #include "CliOptions.h" #include "GlobalIdentifiers.h" #include @@ -27,11 +29,6 @@ using namespace util; using v = log::V; -std::string GetIntrospectionShmName() -{ - return clio::Options::Get().introNsm.AsOptional().value_or(gid::defaultIntrospectionNsmName); -} - void EventFlushThreadEntry_(Service* const srv, PresentMon* const pm) { if (srv == nullptr || pm == nullptr) { @@ -109,7 +106,8 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, // sample 2x here as workaround/kludge because Intel provider misreports 1st sample adapter->Sample(); adapter->Sample(); - pComms->RegisterGpuDevice(adapter->GetVendor(), adapter->GetName(), adapter->GetPowerTelemetryCapBits()); + pComms->RegisterGpuDevice(adapter->GetVendor(), adapter->GetName(), + ipc::intro::ConvertBitset(adapter->GetPowerTelemetryCapBits())); } pComms->FinalizeGpuDevices(); pmlog_info(std::format("Finished populating GPU telemetry introspection, {} seconds elapsed", timer.Mark())); @@ -239,12 +237,14 @@ void PresentMonMainThread(Service* const pSvc) PresentMon pm{ !opt.etlTestFile }; PowerTelemetryContainer ptc; + // namer that coordinates naming convention of shared memory segments // create service-side comms object for transmitting introspection data to clients std::unique_ptr pComms; try { - const auto introNsmName = GetIntrospectionShmName(); - LOG(INFO) << "Creating comms with NSM name: " << introNsmName; - pComms = ipc::MakeServiceComms(std::move(introNsmName)); + ipc::ShmNamer shmNamer{ {}, opt.shmNamePrefix.AsOptional() }; + pmlog_info("Creating comms with introspection shm name: ") + .pmwatch(shmNamer.MakeIntrospectionName()); + pComms = ipc::MakeServiceComms(opt.shmNamePrefix.AsOptional()); } catch (const std::exception& e) { LOG(ERROR) << "Failed making service comms> " << e.what() << std::endl; @@ -256,7 +256,9 @@ void PresentMonMainThread(Service* const pSvc) pm.SetPowerTelemetryContainer(&ptc); // Start named pipe action RPC server (active threaded) - auto pActionServer = std::make_unique(pSvc, &pm, opt.controlPipe.AsOptional()); + auto pActionServer = std::make_unique(pSvc, &pm, + pComms->GetNamer().GetPrefix(), pComms->GetNamer().GetSalt(), + opt.controlPipe.AsOptional()); try { gpuTelemetryThread = std::jthread{ PowerTelemetryThreadEntry_, pSvc, &pm, &ptc, pComms.get() }; @@ -298,12 +300,12 @@ void PresentMonMainThread(Service* const pSvc) } }(); // register cpu - pComms->RegisterCpuDevice(vendor, cpu->GetCpuName(), cpu->GetCpuTelemetryCapBits()); + pComms->RegisterCpuDevice(vendor, cpu->GetCpuName(), + ipc::intro::ConvertBitset(cpu->GetCpuTelemetryCapBits())); } else { // We were unable to determine the cpu. - std::bitset(CpuTelemetryCapBits::cpu_telemetry_count)> - cpuTelemetryCapBits_{}; - pComms->RegisterCpuDevice(PM_DEVICE_VENDOR_UNKNOWN, "UNKNOWN_CPU", cpuTelemetryCapBits_); + pComms->RegisterCpuDevice(PM_DEVICE_VENDOR_UNKNOWN, "UNKNOWN_CPU", + ipc::intro::ConvertBitset(CpuTelemetryBitset{})); } // start thread for manual ETW event buffer flushing diff --git a/IntelPresentMon/PresentMonService/PresentMonService.vcxproj b/IntelPresentMon/PresentMonService/PresentMonService.vcxproj index 309c823e..d3ee0ad4 100644 --- a/IntelPresentMon/PresentMonService/PresentMonService.vcxproj +++ b/IntelPresentMon/PresentMonService/PresentMonService.vcxproj @@ -133,7 +133,6 @@ - diff --git a/IntelPresentMon/PresentMonService/PresentMonService.vcxproj.filters b/IntelPresentMon/PresentMonService/PresentMonService.vcxproj.filters index 5b0f4deb..d9936bfc 100644 --- a/IntelPresentMon/PresentMonService/PresentMonService.vcxproj.filters +++ b/IntelPresentMon/PresentMonService/PresentMonService.vcxproj.filters @@ -43,7 +43,6 @@ - diff --git a/IntelPresentMon/PresentMonService/acts/GetIntrospectionShmName.h b/IntelPresentMon/PresentMonService/acts/GetIntrospectionShmName.h deleted file mode 100644 index 8411f754..00000000 --- a/IntelPresentMon/PresentMonService/acts/GetIntrospectionShmName.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once -#include "../../Interprocess/source/act/ActionHelper.h" -#include "../PMMainThread.h" -#include - -#define ACT_NAME GetIntrospectionShmName -#define ACT_EXEC_CTX ActionExecutionContext -#define ACT_NS ::pmon::svc::acts -#define ACT_TYPE AsyncActionBase_ - -namespace pmon::svc::acts -{ - using namespace ipc::act; - - class ACT_NAME : public ACT_TYPE - { - public: - static constexpr const char* Identifier = STRINGIFY(ACT_NAME); - struct Params {}; - struct Response - { - std::string name; - - template void serialize(A& ar) { - ar(name); - } - }; - private: - friend class ACT_TYPE; - static Response Execute_(const ACT_EXEC_CTX& ctx, SessionContext& stx, Params&& in) - { - const Response out{ - .name = ::GetIntrospectionShmName(), - }; - pmlog_dbg(std::format("Reported introspection shm name: {}", out.name)); - return out; - } - }; - -#ifdef PM_ASYNC_ACTION_REGISTRATION_ - ACTION_REG(); -#endif -} - -ACTION_TRAITS_DEF(); - -#undef ACT_NAME -#undef ACT_EXEC_CTX -#undef ACT_NS -#undef ACT_TYPE \ No newline at end of file diff --git a/IntelPresentMon/PresentMonService/acts/OpenSession.h b/IntelPresentMon/PresentMonService/acts/OpenSession.h index bd4e2a86..cd34221e 100644 --- a/IntelPresentMon/PresentMonService/acts/OpenSession.h +++ b/IntelPresentMon/PresentMonService/acts/OpenSession.h @@ -30,9 +30,11 @@ namespace pmon::svc::acts uint32_t servicePid; std::string serviceBuildId; std::string serviceBuildConfig; + std::string shmPrefix; + std::string shmSalt; template void serialize(A& ar) { - ar(serviceBuildId, serviceBuildConfig); + ar(servicePid, serviceBuildId, serviceBuildConfig, shmPrefix, shmSalt); } }; private: @@ -48,7 +50,9 @@ namespace pmon::svc::acts return Response{ .servicePid = GetCurrentProcessId(), .serviceBuildId = bid::BuildIdShortHash(), - .serviceBuildConfig = bid::BuildIdConfig() + .serviceBuildConfig = bid::BuildIdConfig(), + .shmPrefix = ctx.shmPrefix, + .shmSalt = ctx.shmSalt, }; } }; diff --git a/IntelPresentMon/Streamer/Streamer.cpp b/IntelPresentMon/Streamer/Streamer.cpp index bce2846b..10b08063 100644 --- a/IntelPresentMon/Streamer/Streamer.cpp +++ b/IntelPresentMon/Streamer/Streamer.cpp @@ -33,7 +33,7 @@ Streamer::Streamer() mapfileNamePrefix_{ kGlobalPrefix } { if (clio::Options::IsInitialized()) { - mapfileNamePrefix_ = clio::Options::Get().nsmPrefix.AsOptional().value_or(mapfileNamePrefix_); + mapfileNamePrefix_ = clio::Options::Get().shmNamePrefix.AsOptional().value_or(mapfileNamePrefix_); } } From 2ba9c14b6815d4a73a334a1af05f43fac1033beb Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Wed, 3 Dec 2025 10:53:17 -0800 Subject: [PATCH 018/205] Fixed incorrect display latency and readytime tests --- IntelPresentMon/UnitTests/MetricsCore.cpp | 145 ++++++++++++++++------ 1 file changed, 107 insertions(+), 38 deletions(-) diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index 2fe067e8..2f428e5b 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -2549,10 +2549,12 @@ namespace MetricsCoreTests // readyTime = 1'500'000 (single value for the frame) // Display 0: screenTime = 2'000'000 // Display 1: screenTime = 2'100'000 + // Display 2: screenTime = 2'200'000 // QPC 10 MHz // Expected: // Metrics[0].msReadyTimeToDisplayLatency ≈ 0.05 ms (500'000 ticks) // Metrics[1].msReadyTimeToDisplayLatency ≈ 0.06 ms (600'000 ticks) + // Metrics[2].msReadyTimeToDisplayLatency ≈ 0.07 ms (700'000 ticks) QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; @@ -2563,19 +2565,37 @@ namespace MetricsCoreTests frame.readyTime = 1'500'000; frame.setFinalState(PresentResult::Presented); frame.displayed.push_back({ FrameType::Application, 2'000'000 }); - frame.displayed.push_back({ FrameType::Repeated, 2'100'000 }); + frame.displayed.push_back({ FrameType::Intel_XEFG, 2'100'000 }); + frame.displayed.push_back({ FrameType::Intel_XEFG, 2'200'000 }); FrameData next{}; next.setFinalState(PresentResult::Presented); next.displayed.push_back({ FrameType::Application, 2'500'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); - Assert::AreEqual(size_t(1), results.size()); + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + chain.lastAppPresent = priorApp; + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(2), results.size()); // Display 0: readyTime = 1'500'000, screenTime = 2'000'000 → 0.05 ms double expected0 = qpc.DeltaUnsignedMilliSeconds(1'500'000, 2'000'000); Assert::IsTrue(results[0].metrics.msReadyTimeToDisplayLatency.has_value()); Assert::AreEqual(expected0, results[0].metrics.msReadyTimeToDisplayLatency.value(), 0.0001); + + // Display 0: readyTime = 1'500'000, screenTime = 2'000'000 → 0.05 ms + double expected1 = qpc.DeltaUnsignedMilliSeconds(1'500'000, 2'100'000); + Assert::IsTrue(results[1].metrics.msReadyTimeToDisplayLatency.has_value()); + Assert::AreEqual(expected1, results[1].metrics.msReadyTimeToDisplayLatency.value(), 0.0001); + + // Second call with next: process [2] + auto results2 = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results2.size()); + double expected2 = qpc.DeltaUnsignedMilliSeconds(1'500'000, 2'200'000); + Assert::IsTrue(results2[0].metrics.msReadyTimeToDisplayLatency.has_value()); + Assert::AreEqual(expected2, results2[0].metrics.msReadyTimeToDisplayLatency.value(), 0.0001); } }; @@ -2585,12 +2605,13 @@ namespace MetricsCoreTests TEST_METHOD(DisplayLatency_NvCollapsed_AdjustedScreenTime) { // Scenario: NV collapse adjustment modifies screenTime before metric computation. - // Raw screenTime = 3'000'000 - // chain.lastDisplayedScreenTime = 4'000'000 (adjusted upward by NV1 logic) // cpuStart = 1'000'000 + // Display 0: screenTime = 4'000'000 + // Display 1: screenTime = 3'000'000 // QPC 10 MHz - // Assume the unified code applies NV adjustment so effective screenTime = 4'000'000 - // Expected: msDisplayLatency ≈ 0.3 ms (using adjusted screenTime 4'000'000 − 1'000'000) + // Assume the unified code applies NV adjustment + // Expected: msDisplayLatency ≈ 0.295 ms (using adjusted screenTime 4'000'000 − 1'050'000) + // Expected: msFlipDelay ≈ 0.103 ms (original 30'000 + adjustment) QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; @@ -2604,58 +2625,106 @@ namespace MetricsCoreTests // Raw screen time is 4'000'000, greater than next screen time frame.displayed.push_back({ FrameType::Application, 4'000'000 }); - FrameData next{}; - next.presentStartTime = 2'000'000; - next.timeInPresent = 50'000; - next.readyTime = 2'100'000; - next.setFinalState(PresentResult::Presented); - next.displayed.push_back({ FrameType::Application, 3'000'000 }); + FrameData next1{}; + next1.presentStartTime = 2'000'000; + next1.timeInPresent = 50'000; + next1.readyTime = 2'100'000; + next1.flipDelay = 30'000; + next1.setFinalState(PresentResult::Presented); + next1.displayed.push_back({ FrameType::Application, 3'000'000 }); + + FrameData next2{}; + next2.presentStartTime = 3'000'000; + next2.timeInPresent = 50'000; + next2.readyTime = 3'100'000; + next2.setFinalState(PresentResult::Presented); + next2.displayed.push_back({ FrameType::Application, 5'000'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); - Assert::AreEqual(size_t(1), results.size()); - const auto& m = results[0].metrics; + // Set up chain with prior app present to establish cpuStart + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + chain.lastAppPresent = priorApp; - // After NV adjustment: screenTime = 4'000'000 - // msDisplayLatency = 4'000'000 - 1'100'000 = 3'000'000 ticks = 0.3 ms - double expected = qpc.DeltaUnsignedMilliSeconds(1'100'000, 4'000'000); - Assert::AreEqual(expected, m.msDisplayLatency, 0.0001); + auto results1 = ComputeMetricsForPresent(qpc, frame, &next1, chain); + Assert::AreEqual(size_t(1), results1.size()); + + // No adjust of first frame msDisplayLatency = 4'000'000 - 1'000'000 = 3'000'000 ticks = 0.3 ms + double expectedDisplayLatency = qpc.DeltaUnsignedMilliSeconds(1'000'000, 4'000'000); + Assert::AreEqual(expectedDisplayLatency, results1[0].metrics.msDisplayLatency, 0.0001); + double expectedFlipDelay = qpc.DurationMilliSeconds(frame.flipDelay); + Assert::IsTrue(results1[0].metrics.msFlipDelay.has_value()); + Assert::AreEqual(expectedFlipDelay, results1[0].metrics.msFlipDelay.value(), 0.0001); + + auto results2 = ComputeMetricsForPresent(qpc, next1, &next2, chain); + Assert::AreEqual(size_t(1), results1.size()); + + // After NV adjustment: screenTime = 4'000'000 -> set from NV FlipDelay adjustment + // msDisplayLatency = 4'000'000 - 1'050'000 = 2'950'000 ticks = 0.295 ms + // msFlipDelay = original 30'000 + (4'000'000 - 3'000'000) = 1'030'000 ticks = 0.103 ms + double expectedDisplayLatency2 = qpc.DeltaUnsignedMilliSeconds(1'050'000, 4'000'000); + Assert::AreEqual(expectedDisplayLatency2, results2[0].metrics.msDisplayLatency, 0.0001); + double expectedFlipDelay2 = qpc.DurationMilliSeconds(1'030'000); + Assert::IsTrue(results2[0].metrics.msFlipDelay.has_value()); + Assert::AreEqual(expectedFlipDelay2, results2[0].metrics.msFlipDelay.value(), 0.0001); } TEST_METHOD(ReadyTimeToDisplay_NvCollapsed_UsesAdjustedScreenTime) { // Scenario: NV collapse adjustment affects ReadyTimeToDisplay metric. // Adjusted screenTime = 4'000'000 - // readyTime = 1'500'000 + // readyTime = 2'100'000 // QPC 10 MHz - // Expected: msReadyTimeToDisplayLatency ≈ 0.25 ms (4'000'000 − 1'500'000 = 2'500'000 ticks) + // Expected: msReadyTimeToDisplayLatency ≈ 0.19 ms (4'000'000 - 2'100'000 = 1'900'000 ticks) QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - chain.lastDisplayedScreenTime = 4'000'000; - chain.lastDisplayedFlipDelay = 100'000; FrameData frame{}; frame.presentStartTime = 1'000'000; frame.timeInPresent = 50'000; - frame.readyTime = 1'500'000; + frame.readyTime = 1'100'000; frame.flipDelay = 50'000; frame.setFinalState(PresentResult::Presented); - // Raw screen time is 3'000'000, earlier than lastDisplayedScreenTime - frame.displayed.push_back({ FrameType::Application, 3'000'000 }); + // Raw screen time is 4'000'000, greater than next screen time + frame.displayed.push_back({ FrameType::Application, 4'000'000 }); - FrameData next{}; - next.setFinalState(PresentResult::Presented); - next.displayed.push_back({ FrameType::Application, 4'500'000 }); + FrameData next1{}; + next1.presentStartTime = 2'000'000; + next1.timeInPresent = 50'000; + next1.readyTime = 2'100'000; + next1.flipDelay = 30'000; + next1.setFinalState(PresentResult::Presented); + next1.displayed.push_back({ FrameType::Application, 3'000'000 }); + + FrameData next2{}; + next2.presentStartTime = 3'000'000; + next2.timeInPresent = 50'000; + next2.readyTime = 3'100'000; + next2.setFinalState(PresentResult::Presented); + next2.displayed.push_back({ FrameType::Application, 5'000'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); - Assert::AreEqual(size_t(1), results.size()); - const auto& m = results[0].metrics; + // Set up chain with prior app present to establish cpuStart + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + chain.lastAppPresent = priorApp; - // After NV1 adjustment: screenTime = 4'000'000 - // msReadyTimeToDisplayLatency = 4'000'000 - 1'500'000 = 2'500'000 ticks = 0.25 ms - double expected = qpc.DeltaUnsignedMilliSeconds(1'500'000, 4'000'000); - Assert::IsTrue(m.msReadyTimeToDisplayLatency.has_value()); - Assert::AreEqual(expected, m.msReadyTimeToDisplayLatency.value(), 0.0001); + auto results1 = ComputeMetricsForPresent(qpc, frame, &next1, chain); + Assert::AreEqual(size_t(1), results1.size()); + + // No adjust of first frame ready time = 4'000'000 - 1'100'000 = 2'900'000 ticks = 0.29 ms + double expectedReadyTimeLatency = qpc.DeltaUnsignedMilliSeconds(1'100'000, 4'000'000); + Assert::IsTrue(results1[0].metrics.msReadyTimeToDisplayLatency.has_value()); + Assert::AreEqual(expectedReadyTimeLatency, results1[0].metrics.msReadyTimeToDisplayLatency.value(), 0.0001); + + auto results2 = ComputeMetricsForPresent(qpc, next1, &next2, chain); + Assert::AreEqual(size_t(1), results1.size()); + + // After NV adjustment: ready time latency = 4'000'000 - 2'100'000 = 1'900'000 ticks = 0.19 ms + double expectedReadyTimeLatency2 = qpc.DeltaUnsignedMilliSeconds(2'100'000, 4'000'000); + Assert::IsTrue(results2[0].metrics.msReadyTimeToDisplayLatency.has_value()); + Assert::AreEqual(expectedReadyTimeLatency2, results2[0].metrics.msReadyTimeToDisplayLatency.value(), 0.0001); } }; From 8fc3edf3ced042539efbfe085205f8cf6c708d64 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Wed, 3 Dec 2025 15:13:28 -0800 Subject: [PATCH 019/205] Frame based CPU and GPU ULTs added --- IntelPresentMon/UnitTests/MetricsCore.cpp | 1002 +++++++++++++++++++++ 1 file changed, 1002 insertions(+) diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index 2f428e5b..26382bc6 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -2870,4 +2870,1006 @@ namespace MetricsCoreTests Assert::AreEqual(expected, m.msDisplayLatency, 0.0001); } }; + TEST_CLASS(CPUMetricsTests) + { + public: + TEST_METHOD(CPUBusy_BasicCase_StandardPath) + { + // No propagated data in lastAppPresent + // cpuStart = 1'000'000 (prior frame start + timeInPresent) + // presentStartTime = 1'100'000 + // QPC frequency: 10 MHz + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData priorApp = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 800'000, + /*timeInPresent*/ 200'000, + /*readyTime*/ 1'000'000, + /*displayed*/{}); + + chain.lastAppPresent = priorApp; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'100'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'200'000, + /*displayed*/{}); + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // cpuStart = 800'000 + 200'000 = 1'000'000 + // msCPUBusy = 1'100'000 - 1'000'000 = 100'000 ticks = 10 ms + double expected = qpc.DeltaUnsignedMilliSeconds(1'000'000, 1'100'000); + Assert::AreEqual(expected, m.msCPUBusy, 0.0001); + } + + TEST_METHOD(CPUBusy_WithAppPropagatedData) + { + // lastAppPresent has appPropagatedPresentStartTime set + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData priorApp = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, + /*timeInPresent*/ 200'000, + /*readyTime*/ 1'200'000, + /*displayed*/{}, + /*appSimStartTime*/ 0, + /*pclSimStartTime*/ 0, + /*flipDelay*/ 0); + + priorApp.appPropagatedPresentStartTime = 800'000; + priorApp.appPropagatedTimeInPresent = 200'000; + chain.lastAppPresent = priorApp; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'500'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'600'000, + /*displayed*/{}); + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // cpuStart = 800'000 + 200'000 = 1'000'000 (uses appPropagated) + // msCPUBusy = 1'500'000 - 1'000'000 = 500'000 ticks = 50 ms + double expected = qpc.DeltaUnsignedMilliSeconds(1'000'000, 1'500'000); + Assert::AreEqual(expected, m.msCPUBusy, 0.0001); + } + + TEST_METHOD(CPUBusy_FirstFrameNoPriorAppPresent) + { + // No lastAppPresent in chain state + // cpuStart = 0 (default fallback) + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 5'000'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 5'200'000, + /*displayed*/{}); + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // cpuStart = 0 + // msCPUBusy = 5'000'000 - 0 = 5'000'000 ticks = 500 ms + double expected = qpc.DeltaUnsignedMilliSeconds(0, 5'000'000); + Assert::AreEqual(expected, m.msCPUBusy, 0.0001); + } + + TEST_METHOD(CPUBusy_ZeroTimeInPresent) + { + // cpuStart = 1'000'000 + // presentStartTime = 1'000'000 (same as cpuStart) + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData priorApp = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 800'000, + /*timeInPresent*/ 200'000, + /*readyTime*/ 1'000'000, + /*displayed*/{}); + + chain.lastAppPresent = priorApp; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, // Same as cpuStart + /*timeInPresent*/ 0, // Zero present duration + /*readyTime*/ 1'000'000, + /*displayed*/{}); + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + Assert::AreEqual(0.0, m.msCPUBusy, 0.0001); + } + + TEST_METHOD(CPUWait_BasicCase_StandardPath) + { + // timeInPresent = 200'000 ticks + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, + /*timeInPresent*/ 200'000, + /*readyTime*/ 1'200'000, + /*displayed*/{}); + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // msCPUWait = 200'000 ticks = 20 ms + double expected = qpc.DurationMilliSeconds(200'000); + Assert::AreEqual(expected, m.msCPUWait, 0.0001); + } + + TEST_METHOD(CPUWait_WithAppPropagatedTimeInPresent) + { + // appPropagatedTimeInPresent = 150'000 ticks + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, + /*timeInPresent*/ 200'000, // Regular time (not used when propagated available) + /*readyTime*/ 1'200'000, + /*displayed*/{}); + + frame.appPropagatedTimeInPresent = 150'000; + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // When appPropagated is available, use it + double expected = qpc.DurationMilliSeconds(150'000); + Assert::AreEqual(expected, m.msCPUWait, 0.0001); + } + + TEST_METHOD(CPUWait_ZeroDuration) + { + // timeInPresent = 0 + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, + /*timeInPresent*/ 0, + /*readyTime*/ 1'000'000, + /*displayed*/{}); + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + Assert::AreEqual(0.0, m.msCPUWait, 0.0001); + } + }; + + // ============================================================================ + // GROUP B: CORE GPU METRICS (NON-VIDEO) + // ============================================================================ + + TEST_CLASS(GPUMetricsNonVideoTests) + { + public: + TEST_METHOD(GPULatency_BasicCase_StandardPath) + { + // cpuStart = 1'000'000 + // gpuStartTime = 1'050'000 + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData priorApp = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 800'000, + /*timeInPresent*/ 200'000, + /*readyTime*/ 1'000'000, + /*displayed*/{}); + + chain.lastAppPresent = priorApp; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'200'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'300'000, + /*displayed*/{}); + + frame.gpuStartTime = 1'050'000; + frame.gpuDuration = 200'000; + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // cpuStart = 800'000 + 200'000 = 1'000'000 + // msGPULatency = 1'050'000 - 1'000'000 = 50'000 ticks = 5 ms + double expected = qpc.DeltaUnsignedMilliSeconds(1'000'000, 1'050'000); + Assert::AreEqual(expected, m.msGPULatency, 0.0001); + } + + TEST_METHOD(GPULatency_WithAppPropagatedGPUStart) + { + // appPropagatedGPUStartTime = 1'080'000 + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData priorApp = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 800'000, + /*timeInPresent*/ 200'000, + /*readyTime*/ 1'000'000, + /*displayed*/{}); + + chain.lastAppPresent = priorApp; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'200'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'300'000, + /*displayed*/{}); + + frame.gpuStartTime = 1'050'000; // Not used when propagated available + frame.appPropagatedGPUStartTime = 1'080'000; + frame.appPropagatedGPUDuration = 200'000; + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // cpuStart = 1'000'000 + // msGPULatency = 1'080'000 - 1'000'000 = 80'000 ticks = 8 ms + double expected = qpc.DeltaUnsignedMilliSeconds(1'000'000, 1'080'000); + Assert::AreEqual(expected, m.msGPULatency, 0.0001); + } + + TEST_METHOD(GPULatency_GPUStartBeforeCpuStart) + { + // cpuStart = 2'000'000 + // gpuStartTime = 1'900'000 (impossible but defensive) + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData priorApp = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'500'000, + /*timeInPresent*/ 500'000, // cpuStart = 2'000'000 + /*readyTime*/ 2'000'000, + /*displayed*/{}); + + chain.lastAppPresent = priorApp; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 2'200'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 2'300'000, + /*displayed*/{}); + + frame.gpuStartTime = 1'900'000; // Earlier than cpuStart + frame.gpuDuration = 300'000; + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // Result should be 0 or negative (defensive clamping) + Assert::IsTrue(m.msGPULatency <= 0.0 || m.msGPULatency == 0.0); + } + + TEST_METHOD(GPUBusy_BasicCase_StandardPath) + { + // gpuDuration = 500'000 ticks + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'200'000, + /*displayed*/{}); + + frame.gpuStartTime = 1'050'000; + frame.gpuDuration = 500'000; + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // msGPUBusy = 500'000 ticks = 50 ms + double expected = qpc.DurationMilliSeconds(500'000); + Assert::AreEqual(expected, m.msGPUBusy, 0.0001); + } + + TEST_METHOD(GPUBusy_ZeroDuration) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'200'000, + /*displayed*/{}); + + frame.gpuStartTime = 1'050'000; + frame.gpuDuration = 0; // No GPU work + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + Assert::AreEqual(0.0, m.msGPUBusy, 0.0001); + } + + TEST_METHOD(GPUBusy_WithAppPropagatedDuration) + { + // appPropagatedGPUDuration = 450'000 ticks + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'200'000, + /*displayed*/{}); + + frame.gpuStartTime = 1'050'000; + frame.gpuDuration = 500'000; // Not used when propagated available + frame.appPropagatedGPUStartTime = 1'050'000; + frame.appPropagatedGPUDuration = 450'000; + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // Uses appPropagated + double expected = qpc.DurationMilliSeconds(450'000); + Assert::AreEqual(expected, m.msGPUBusy, 0.0001); + } + + TEST_METHOD(GPUWait_BasicCase_BusyLessThanTotal) + { + // gpuStartTime = 1'000'000, readyTime = 1'600'000 ? total = 600'000 + // gpuDuration (busy) = 500'000 + // msGPUWait should be 100'000 ticks = 10 ms + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'600'000, + /*displayed*/{}); + + frame.gpuStartTime = 1'000'000; + frame.gpuDuration = 500'000; + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // Total = 1'600'000 - 1'000'000 = 600'000 + // msGPUWait = 600'000 - 500'000 = 100'000 ticks = 10 ms + double expectedTotal = qpc.DeltaUnsignedMilliSeconds(1'000'000, 1'600'000); + double expectedWait = max(0.0, expectedTotal - m.msGPUBusy); + Assert::AreEqual(expectedWait, m.msGPUWait, 0.0001); + } + + TEST_METHOD(GPUWait_BusyEqualsTotal) + { + // gpuStartTime = 1'000'000, readyTime = 1'600'000 ? total = 600'000 + // gpuDuration = 600'000 (fully busy) + // msGPUWait should be 0 + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'600'000, + /*displayed*/{}); + + frame.gpuStartTime = 1'000'000; + frame.gpuDuration = 600'000; // Equal to total + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + Assert::AreEqual(0.0, m.msGPUWait, 0.0001); + } + + TEST_METHOD(GPUWait_BusyGreaterThanTotal) + { + // gpuStartTime = 1'000'000, readyTime = 1'600'000 ? total = 600'000 + // gpuDuration = 700'000 (impossible, but defensive) + // msGPUWait should clamp to 0 + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'600'000, + /*displayed*/{}); + + frame.gpuStartTime = 1'000'000; + frame.gpuDuration = 700'000; // Greater than total (impossible) + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // Should clamp to 0 + Assert::AreEqual(0.0, m.msGPUWait, 0.0001); + } + + TEST_METHOD(GPUWait_WithAppPropagatedData) + { + // appPropagatedGPUStartTime = 1'000'000, appPropagatedReadyTime = 1'550'000 ? total = 550'000 + // appPropagatedGPUDuration = 450'000 + // msGPUWait should be 100'000 ticks = 10 ms + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'600'000, + /*displayed*/{}); + + frame.gpuStartTime = 1'000'000; + frame.gpuDuration = 600'000; + frame.appPropagatedGPUStartTime = 1'000'000; + frame.appPropagatedReadyTime = 1'550'000; + frame.appPropagatedGPUDuration = 450'000; + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // Total = 1'550'000 - 1'000'000 = 550'000 + // msGPUWait = 550'000 - 450'000 = 100'000 ticks = 10 ms + double expectedTotal = qpc.DeltaUnsignedMilliSeconds(1'000'000, 1'550'000); + double expectedWait = max(0.0, expectedTotal - m.msGPUBusy); + Assert::AreEqual(expectedWait, m.msGPUWait, 0.0001); + } + }; + + // ============================================================================ + // GROUP C: VIDEO METRICS + // ============================================================================ + + TEST_CLASS(GPUMetricsVideoTests) + { + public: + TEST_METHOD(VideoBusy_BasicCase_StandardPath) + { + // gpuVideoDuration = 200'000 ticks + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'200'000, + /*displayed*/{}); + + frame.gpuStartTime = 1'050'000; + frame.gpuDuration = 500'000; + frame.gpuVideoDuration = 200'000; + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // msVideoBusy = 200'000 ticks = 20 ms + double expected = qpc.DurationMilliSeconds(200'000); + Assert::AreEqual(expected, m.msVideoBusy, 0.0001); + } + + TEST_METHOD(VideoBusy_ZeroDuration) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'200'000, + /*displayed*/{}); + + frame.gpuStartTime = 1'050'000; + frame.gpuDuration = 500'000; + frame.gpuVideoDuration = 0; // No video work + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + Assert::AreEqual(0.0, m.msVideoBusy, 0.0001); + } + + TEST_METHOD(VideoBusy_WithAppPropagatedData) + { + // appPropagatedGPUVideoDuration = 180'000 ticks + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'200'000, + /*displayed*/{}); + + frame.gpuStartTime = 1'050'000; + frame.gpuDuration = 500'000; + frame.gpuVideoDuration = 200'000; // Not used when propagated available + frame.appPropagatedGPUStartTime = 1'050'000; + frame.appPropagatedGPUDuration = 450'000; + frame.appPropagatedGPUVideoDuration = 180'000; + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // Uses appPropagated + double expected = qpc.DurationMilliSeconds(180'000); + Assert::AreEqual(expected, m.msVideoBusy, 0.0001); + } + + TEST_METHOD(VideoBusy_OverlapWithGPUBusy) + { + // msGPUBusy = 50 ms (500'000 ticks) + // msVideoBusy = 20 ms (200'000 ticks) + // Verify both are independently computed (no constraint) + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'200'000, + /*displayed*/{}); + + frame.gpuStartTime = 1'050'000; + frame.gpuDuration = 500'000; + frame.gpuVideoDuration = 200'000; + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + double expectedGpuBusy = qpc.DurationMilliSeconds(500'000); + double expectedVideoBusy = qpc.DurationMilliSeconds(200'000); + + Assert::AreEqual(expectedGpuBusy, m.msGPUBusy, 0.0001); + Assert::AreEqual(expectedVideoBusy, m.msVideoBusy, 0.0001); + } + + TEST_METHOD(VideoBusy_LargerThanGPUBusy) + { + // msGPUBusy = 30 ms (computed from gpuDuration) + // msVideoBusy = 50 ms (computed from gpuVideoDuration) + // Verify independent computation (no implicit constraints) + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'200'000, + /*displayed*/{}); + + frame.gpuStartTime = 1'050'000; + frame.gpuDuration = 300'000; // 30 ms + frame.gpuVideoDuration = 500'000; // 50 ms (larger than gpuDuration) + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // Verify both are computed independently + Assert::IsTrue(m.msVideoBusy > m.msGPUBusy); + } + }; + TEST_CLASS(EdgeCasesAndMissingData) + { + public: + TEST_METHOD(AllMetrics_NoGPUData_GPUMetricsZero) + { + // Frame with no GPU data (all GPU fields = 0) + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData priorApp = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 800'000, + /*timeInPresent*/ 200'000, + /*readyTime*/ 1'000'000, + /*displayed*/{}); + + chain.lastAppPresent = priorApp; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'100'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'200'000, + /*displayed*/{}); + + // No GPU data set (all zeros by default) + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // CPU metrics should be non-zero + Assert::IsTrue(m.msCPUBusy > 0); + // GPU metrics should be zero + Assert::AreEqual(0.0, m.msGPULatency, 0.0001); + Assert::AreEqual(0.0, m.msGPUBusy, 0.0001); + Assert::AreEqual(0.0, m.msGPUWait, 0.0001); + Assert::AreEqual(0.0, m.msVideoBusy, 0.0001); + } + + TEST_METHOD(GeneratedFrameMetrics_NotAppFrame_CPUGPUMetricsZero) + { + // Frame with only Repeated display type (not Application) + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData priorApp = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 800'000, + /*timeInPresent*/ 200'000, + /*readyTime*/ 1'000'000, + /*displayed*/{}); + + chain.lastAppPresent = priorApp; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'100'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'200'000, + std::vector>{ + { FrameType::Repeated, 1'300'000 } // Not Application + }); + + frame.gpuStartTime = 1'150'000; + frame.gpuDuration = 200'000; + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 1'500'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // Generated frames have no CPU/GPU work attribution + Assert::AreEqual(0.0, m.msCPUBusy, 0.0001); + Assert::AreEqual(0.0, m.msCPUWait, 0.0001); + Assert::AreEqual(0.0, m.msGPULatency, 0.0001); + Assert::AreEqual(0.0, m.msGPUBusy, 0.0001); + Assert::AreEqual(0.0, m.msGPUWait, 0.0001); + } + + TEST_METHOD(NotDisplayedFrame_AppFrameMetrics_Computed) + { + // Frame is not displayed (Discarded) but has CPU/GPU work + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData priorApp = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 800'000, + /*timeInPresent*/ 200'000, + /*readyTime*/ 1'000'000, + /*displayed*/{}); + + chain.lastAppPresent = priorApp; + + FrameData frame = MakeFrame( + PresentResult::Discarded, // Not displayed + /*presentStartTime*/ 1'100'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'200'000, + /*displayed*/{}); // No displayed entries + + frame.gpuStartTime = 1'150'000; + frame.gpuDuration = 200'000; + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // CPU/GPU metrics should still be computed even for dropped frames + Assert::IsTrue(m.msCPUBusy > 0); + Assert::IsTrue(m.msGPUBusy > 0); + } + }; + TEST_CLASS(StateAndHistory) + { + public: + TEST_METHOD(CPUStart_UsesLastAppPresent_WhenAvailable) + { + // chain.lastAppPresent set; current is app frame + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData lastApp = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 800'000, + /*timeInPresent*/ 200'000, + /*readyTime*/ 1'000'000, + /*displayed*/{}); + + chain.lastAppPresent = lastApp; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'200'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'300'000, + /*displayed*/{}); + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // cpuStart should be 800'000 + 200'000 = 1'000'000 + uint64_t expectedCpuStart = 800'000 + 200'000; + Assert::AreEqual(expectedCpuStart, m.cpuStartQpc); + } + + TEST_METHOD(CPUStart_FallsBackToLastPresent_WhenNoAppPresent) + { + // chain.lastAppPresent is empty; chain.lastPresent is set + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData lastPresent = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 800'000, + /*timeInPresent*/ 200'000, + /*readyTime*/ 1'000'000, + /*displayed*/{}); + + chain.lastPresent = lastPresent; + // lastAppPresent remains unset + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'200'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'300'000, + /*displayed*/{}); + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // cpuStart falls back to lastPresent: 800'000 + 200'000 = 1'000'000 + uint64_t expectedCpuStart = 800'000 + 200'000; + Assert::AreEqual(expectedCpuStart, m.cpuStartQpc); + } + + TEST_METHOD(CPUStart_ReturnsZero_WhenNoChainHistory) + { + // No lastAppPresent; no lastPresent + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; // Empty chain + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 5'000'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 5'200'000, + /*displayed*/{}); + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // cpuStart = 0 (no history) + Assert::AreEqual(uint64_t(0), m.cpuStartQpc); + } + + TEST_METHOD(ChainState_UpdatedAfterPresent_SingleDisplay) + { + // Process a displayed app frame; verify chain state is updated + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 5'000'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 5'200'000, + std::vector>{ + { FrameType::Application, 5'500'000 } + }, + /*appSimStartTime*/ 0, + /*pclSimStartTime*/ 0, + /*flipDelay*/ 777); + + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 6'000'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + + // Verify chain state was updated + Assert::IsTrue(chain.lastPresent.has_value()); + Assert::IsTrue(chain.lastAppPresent.has_value()); + Assert::AreEqual(uint64_t(5'500'000), chain.lastDisplayedScreenTime); + Assert::AreEqual(uint64_t(777), chain.lastDisplayedFlipDelay); + } + }; + TEST_CLASS(NumericAndPrecision) + { + public: + TEST_METHOD(CPUBusy_LargeValues_DoesNotOverflow) + { + // cpuStart = 1'000'000'000 (large QPC value) + // presentStartTime = 1'100'000'000 + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData priorApp = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 900'000'000, + /*timeInPresent*/ 100'000'000, + /*readyTime*/ 1'000'000'000, + /*displayed*/{}); + + chain.lastAppPresent = priorApp; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'100'000'000, + /*timeInPresent*/ 100'000'000, + /*readyTime*/ 1'200'000'000, + /*displayed*/{}); + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // cpuStart = 900'000'000 + 100'000'000 = 1'000'000'000 + // msCPUBusy = 1'100'000'000 - 1'000'000'000 = 100'000'000 ticks = 10'000 ms (10 seconds) + double expected = qpc.DeltaUnsignedMilliSeconds(1'000'000'000, 1'100'000'000); + Assert::AreEqual(expected, m.msCPUBusy, 0.0001); + // Verify large value is reasonable (10 seconds) + Assert::IsTrue(m.msCPUBusy > 9000 && m.msCPUBusy < 11000); + } + + TEST_METHOD(GPULatency_SmallDelta_HighPrecision) + { + // cpuStart = 1'000'000 + // gpuStartTime = 1'000'001 (1 tick delta; tiny latency) + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData priorApp = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 800'000, + /*timeInPresent*/ 200'000, + /*readyTime*/ 1'000'000, + /*displayed*/{}); + + chain.lastAppPresent = priorApp; + + FrameData frame = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'100'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'200'000, + /*displayed*/{}); + + frame.gpuStartTime = 1'000'001; // Only 1 tick later than cpuStart + frame.gpuDuration = 200'000; + + auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + Assert::AreEqual(size_t(1), results.size()); + + const auto& m = results[0].metrics; + // msGPULatency = 1 tick at 10 MHz = 0.0001 ms (very small but non-zero) + double expected = qpc.DeltaUnsignedMilliSeconds(1'000'000, 1'000'001); + Assert::AreEqual(expected, m.msGPULatency, 0.00001); + Assert::IsTrue(m.msGPULatency > 0.0 && m.msGPULatency < 0.001); + } + + TEST_METHOD(VideoBusy_ZeroAndNonzeroInSequence) + { + // Frame A: no video work + // Frame B: with video work + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + // Frame A: zero video + FrameData frameA = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 1'000'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 1'200'000, + std::vector>{ + { FrameType::Application, 1'500'000 } + }); + + frameA.gpuStartTime = 1'050'000; + frameA.gpuDuration = 400'000; + frameA.gpuVideoDuration = 0; // No video + + FrameData nextA{}; + nextA.setFinalState(PresentResult::Presented); + nextA.displayed.push_back({ FrameType::Application, 2'000'000 }); + + auto resultsA = ComputeMetricsForPresent(qpc, frameA, &nextA, chain); + Assert::AreEqual(size_t(1), resultsA.size()); + Assert::AreEqual(0.0, resultsA[0].metrics.msVideoBusy, 0.0001); + + // Frame B: with video + FrameData frameB = MakeFrame( + PresentResult::Presented, + /*presentStartTime*/ 2'100'000, + /*timeInPresent*/ 100'000, + /*readyTime*/ 2'300'000, + std::vector>{ + { FrameType::Application, 2'600'000 } + }); + + frameB.gpuStartTime = 2'150'000; + frameB.gpuDuration = 400'000; + frameB.gpuVideoDuration = 300'000; // 30 ms of video + + FrameData nextB{}; + nextB.setFinalState(PresentResult::Presented); + nextB.displayed.push_back({ FrameType::Application, 3'000'000 }); + + auto resultsB = ComputeMetricsForPresent(qpc, frameB, &nextB, chain); + Assert::AreEqual(size_t(1), resultsB.size()); + double expectedVideoBusy = qpc.DurationMilliSeconds(300'000); + Assert::AreEqual(expectedVideoBusy, resultsB[0].metrics.msVideoBusy, 0.0001); + } + }; + } \ No newline at end of file From 3a71563e93a3bbbf08ec8b21f65903cb0876978c Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 4 Dec 2025 10:10:51 +0900 Subject: [PATCH 020/205] ipm running again --- IntelPresentMon/Core/source/cli/CliOptions.h | 2 +- .../Interprocess/source/Interprocess.cpp | 39 ++++++++++++------- .../Interprocess/source/Interprocess.h | 2 +- .../source/IntrospectionTransfer.h | 4 ++ .../Interprocess/source/ShmNamer.cpp | 6 +-- .../Interprocess/source/ShmNamer.h | 4 +- .../Interprocess/source/TelemetryMap.h | 10 ++++- .../Interprocess/source/metadata/MetricList.h | 4 +- IntelPresentMon/KernelProcess/winmain.cpp | 2 +- .../PresentMonService/CliOptions.h | 2 +- .../PresentMonService/PMMainThread.cpp | 8 ++-- IntelPresentMon/SampleClient/CliOptions.h | 1 - IntelPresentMon/SampleClient/SampleClient.cpp | 12 ++---- 13 files changed, 57 insertions(+), 39 deletions(-) diff --git a/IntelPresentMon/Core/source/cli/CliOptions.h b/IntelPresentMon/Core/source/cli/CliOptions.h index 1a1db1b5..fe49d82c 100644 --- a/IntelPresentMon/Core/source/cli/CliOptions.h +++ b/IntelPresentMon/Core/source/cli/CliOptions.h @@ -21,7 +21,7 @@ namespace p2c::cli private: Group gd_{ this, "Debugging", "Aids in debugging this tool" }; public: Option controlPipe{ this, "--control-pipe", R"(\\.\pipe\pm-ctrl)", "Named pipe to connect to the service with" }; - Option shmNamePrefix{ this, "--shm-name-prefix", "pm-shm", "Shared memory to connect to the service with" }; + Option shmNamePrefix{ this, "--shm-name-prefix", "pm-child-shm", "Shared memory to connect to the service with" }; Option etwSessionName{ this, "--etw-session-name", "pm-child-etw-session", "ETW session name when lauching service as child" }; Flag svcAsChild{ this, "--svc-as-child", "Launch service as child console app" }; Flag traceExceptions{ this, "--trace-exceptions", "Add stack trace to all thrown exceptions (including SEH exceptions)" }; diff --git a/IntelPresentMon/Interprocess/source/Interprocess.cpp b/IntelPresentMon/Interprocess/source/Interprocess.cpp index 659a18d5..dee865e2 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.cpp +++ b/IntelPresentMon/Interprocess/source/Interprocess.cpp @@ -37,7 +37,7 @@ namespace pmon::ipc class ServiceComms_ : public ServiceComms, CommsBase_ { public: - ServiceComms_(std::optional prefix) + ServiceComms_(std::string prefix) : namer_{ std::move(prefix) }, shm_{ bip::create_only, namer_.MakeIntrospectionName().c_str(), @@ -65,7 +65,7 @@ namespace pmon::ipc const auto deviceId = nextDeviceIndex_++; intro::PopulateGpuDevice( shm_.get_segment_manager(), *pRoot_, - nextDeviceIndex_++, vendor, deviceName, caps + deviceId, vendor, deviceName, caps ); // allocate map node and create shm segment auto& gpuShm = gpuShms_.emplace( @@ -74,11 +74,16 @@ namespace pmon::ipc std::forward_as_tuple(namer_.MakeGpuName(deviceId)) ).first->second; // populate rings based on caps - for (auto&& [metric, count] : caps) { - const auto dataType = - pRoot_->FindMetric(metric).GetDataTypeInfo().GetFrameType(); + for (auto&& [m, count] : caps) { + const auto& metric = pRoot_->FindMetric(m); + const auto metricType = metric.GetMetricType(); + // static metrics don't get rings + if (metricType == PM_METRIC_TYPE_STATIC) { + continue; + } + const auto dataType = metric.GetDataTypeInfo().GetFrameType(); gpuShm.GetStore().telemetryData.AddRing( - metric, telemetryRingSize_, count, dataType + m, telemetryRingSize_, count, dataType ); } } @@ -100,11 +105,16 @@ namespace pmon::ipc shm_.get_segment_manager(), *pRoot_, vendor, std::move(deviceName), caps ); // populate rings based on caps - for (auto&& [metric, count] : caps) { - const auto dataType = - pRoot_->FindMetric(metric).GetDataTypeInfo().GetFrameType(); + for (auto&& [m, count] : caps) { + const auto& metric = pRoot_->FindMetric(m); + const auto metricType = metric.GetMetricType(); + // static metrics don't get rings + if (metricType == PM_METRIC_TYPE_STATIC) { + continue; + } + const auto dataType = metric.GetDataTypeInfo().GetFrameType(); systemShm_.GetStore().telemetryData.AddRing( - metric, telemetryRingSize_, count, dataType + m, telemetryRingSize_, count, dataType ); } introCpuComplete_ = true; @@ -233,7 +243,7 @@ namespace pmon::ipc class MiddlewareComms_ : public MiddlewareComms, CommsBase_ { public: - MiddlewareComms_(std::optional prefix, std::string salt) + MiddlewareComms_(std::string prefix, std::string salt) : namer_{ std::move(prefix), std::move(salt) }, shm_{ bip::open_only, namer_.MakeIntrospectionName().c_str() }, @@ -328,7 +338,10 @@ namespace pmon::ipc } std::vector ids; for (auto& p : result.first->GetDevices()) { - ids.push_back(p->GetId()); + // skip the 0 id + if (auto id = p->GetId()) { + ids.push_back(id); + } } return ids; } @@ -374,7 +387,7 @@ namespace pmon::ipc } std::unique_ptr - MakeServiceComms(std::optional prefix) + MakeServiceComms(std::string prefix) { return std::make_unique(std::move(prefix)); } diff --git a/IntelPresentMon/Interprocess/source/Interprocess.h b/IntelPresentMon/Interprocess/source/Interprocess.h index c8984115..1a01e66c 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.h +++ b/IntelPresentMon/Interprocess/source/Interprocess.h @@ -46,6 +46,6 @@ namespace pmon::ipc virtual void CloseFrameDataStore(uint32_t pid) = 0; }; - std::unique_ptr MakeServiceComms(std::optional prefix = {}); + std::unique_ptr MakeServiceComms(std::string prefix); std::unique_ptr MakeMiddlewareComms(std::string prefix, std::string salt); } \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/source/IntrospectionTransfer.h b/IntelPresentMon/Interprocess/source/IntrospectionTransfer.h index 324081a6..f534fbb0 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionTransfer.h +++ b/IntelPresentMon/Interprocess/source/IntrospectionTransfer.h @@ -460,6 +460,10 @@ namespace pmon::ipc::intro { return *pTypeInfo_; } + PM_METRIC_TYPE GetMetricType() const + { + return type_; + } bool operator<(const IntrospectionMetric& rhs) const { return id_ < rhs.id_; diff --git a/IntelPresentMon/Interprocess/source/ShmNamer.cpp b/IntelPresentMon/Interprocess/source/ShmNamer.cpp index 8df7a5fa..b3ce2ba5 100644 --- a/IntelPresentMon/Interprocess/source/ShmNamer.cpp +++ b/IntelPresentMon/Interprocess/source/ShmNamer.cpp @@ -5,10 +5,10 @@ namespace pmon::ipc { - ShmNamer::ShmNamer(std::optional salt, std::optional customPrefix) + ShmNamer::ShmNamer(std::string customPrefix, std::optional salt) : - salt_{ salt.value_or(std::format("{:08x}", std::random_device{}())) }, - prefix_{ customPrefix.value_or(R"(Global\pm2sh)") } + prefix_{ customPrefix }, + salt_{ salt.value_or(std::format("{:08x}", std::random_device{}())) } {} std::string ShmNamer::MakeIntrospectionName() const { diff --git a/IntelPresentMon/Interprocess/source/ShmNamer.h b/IntelPresentMon/Interprocess/source/ShmNamer.h index 5b04394b..1c341632 100644 --- a/IntelPresentMon/Interprocess/source/ShmNamer.h +++ b/IntelPresentMon/Interprocess/source/ShmNamer.h @@ -9,7 +9,7 @@ namespace pmon::ipc class ShmNamer { public: - ShmNamer(std::optional salt = {}, std::optional customPrefix = {}); + ShmNamer(std::string customPrefix, std::optional salt = {}); std::string MakeIntrospectionName() const; std::string MakeSystemName() const; std::string MakeGpuName(uint32_t deviceId) const; @@ -17,7 +17,7 @@ namespace pmon::ipc const std::string& GetSalt() const; const std::string& GetPrefix() const; private: - std::string salt_; std::string prefix_; + std::string salt_; }; } \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/source/TelemetryMap.h b/IntelPresentMon/Interprocess/source/TelemetryMap.h index 1431ec5b..223bb53f 100644 --- a/IntelPresentMon/Interprocess/source/TelemetryMap.h +++ b/IntelPresentMon/Interprocess/source/TelemetryMap.h @@ -11,7 +11,9 @@ namespace pmon::ipc { template using HistoryRingVect = ShmVector>; - using MapValueType = std::variant, HistoryRingVect, HistoryRingVect>; + using MapValueType = std::variant< + HistoryRingVect, HistoryRingVect, + HistoryRingVect, HistoryRingVect>; using MapType = ShmMap; public: using AllocatorType = MapType::allocator_type; @@ -31,6 +33,9 @@ namespace pmon::ipc case PM_DATA_TYPE_BOOL: AddRing(id, size, count); break; + case PM_DATA_TYPE_ENUM: + AddRing(id, size, count); + break; default: throw util::Except<>("Unsupported ring type for TelemetryMap"); } } @@ -41,7 +46,8 @@ namespace pmon::ipc static_assert( std::is_same_v || std::is_same_v || - std::is_same_v, + std::is_same_v || + std::is_same_v, "Unsupported ring type for TelemetryMap" ); diff --git a/IntelPresentMon/Interprocess/source/metadata/MetricList.h b/IntelPresentMon/Interprocess/source/metadata/MetricList.h index 4398b588..e1819b69 100644 --- a/IntelPresentMon/Interprocess/source/metadata/MetricList.h +++ b/IntelPresentMon/Interprocess/source/metadata/MetricList.h @@ -56,7 +56,7 @@ X_(PM_METRIC_GPU_MEM_TEMPERATURE, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_CELSIUS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ X_(PM_METRIC_GPU_MEM_SIZE, PM_METRIC_TYPE_STATIC, PM_UNIT_BYTES, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_UINT64, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, PM_STAT_NONE) \ X_(PM_METRIC_GPU_MEM_USED, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_BYTES, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_UINT64, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ - X_(PM_METRIC_GPU_MEM_UTILIZATION, PM_METRIC_TYPE_DYNAMIC, PM_UNIT_PERCENT, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_VOID, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ + X_(PM_METRIC_GPU_MEM_UTILIZATION, PM_METRIC_TYPE_DYNAMIC, PM_UNIT_PERCENT, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ X_(PM_METRIC_GPU_MEM_MAX_BANDWIDTH, PM_METRIC_TYPE_STATIC, PM_UNIT_BITS_PER_SECOND, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_UINT64, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, PM_STAT_NONE) \ X_(PM_METRIC_GPU_MEM_WRITE_BANDWIDTH, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_BITS_PER_SECOND, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ X_(PM_METRIC_GPU_MEM_READ_BANDWIDTH, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_BITS_PER_SECOND, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ @@ -70,7 +70,7 @@ X_(PM_METRIC_CPU_POWER, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_WATTS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, FULL_STATS) \ X_(PM_METRIC_CPU_TEMPERATURE, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_CELSIUS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, FULL_STATS) \ X_(PM_METRIC_CPU_FREQUENCY, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MEGAHERTZ, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, FULL_STATS) \ - X_(PM_METRIC_CPU_CORE_UTILITY, PM_METRIC_TYPE_DYNAMIC, PM_UNIT_PERCENT, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_VOID, 0, PM_DEVICE_TYPE_SYSTEM, FULL_STATS) \ + X_(PM_METRIC_CPU_CORE_UTILITY, PM_METRIC_TYPE_DYNAMIC, PM_UNIT_PERCENT, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, FULL_STATS) \ X_(PM_METRIC_ALL_INPUT_TO_PHOTON_LATENCY, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NON_ZERO_AVG, PM_STAT_PERCENTILE_01, PM_STAT_PERCENTILE_05, PM_STAT_PERCENTILE_10, PM_STAT_MAX) \ X_(PM_METRIC_INSTRUMENTED_LATENCY, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_GPU_EFFECTIVE_FREQUENCY, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MEGAHERTZ, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ diff --git a/IntelPresentMon/KernelProcess/winmain.cpp b/IntelPresentMon/KernelProcess/winmain.cpp index f52b8731..48909c3a 100644 --- a/IntelPresentMon/KernelProcess/winmain.cpp +++ b/IntelPresentMon/KernelProcess/winmain.cpp @@ -227,7 +227,7 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi // launch service child process svcChild = bp2::windows::default_launcher{}(ioctx, "PresentMonService.exe"s, std::move(args)); // wait for pipe availability of service api - if (!::pmon::util::win::WaitForNamedPipe(*opt.controlPipe + "-in", 1500)) { + if (!::pmon::util::win::WaitForNamedPipe(*opt.controlPipe + "-in", 1500000)) { pmlog_error("timeout waiting for child service control pipe to go online"); return -1; } diff --git a/IntelPresentMon/PresentMonService/CliOptions.h b/IntelPresentMon/PresentMonService/CliOptions.h index 6c1d4ab9..8df0da7a 100644 --- a/IntelPresentMon/PresentMonService/CliOptions.h +++ b/IntelPresentMon/PresentMonService/CliOptions.h @@ -17,7 +17,7 @@ namespace clio private: Group gc_{ this, "Connection", "Control client connection" }; public: Option etwSessionName{ this, "--etw-session-name", "PMService", "Name to use when creating the ETW session" }; Option controlPipe{ this, "--control-pipe", "", "Name of the named pipe to use for the client-service control channel" }; - Option shmNamePrefix{ this, "--shm-name-prefix", "", "Prefix to use when naming shared memory segments" }; + Option shmNamePrefix{ this, "--shm-name-prefix", R"(Global\pm_svc_shm)", "Prefix to use when naming shared memory segments" }; private: Group gd_{ this, "Debugging", "Aids in debugging this tool" }; public: Flag debug{ this, "--debug,-d", "Stall service by running in a loop after startup waiting for debugger to connect" }; diff --git a/IntelPresentMon/PresentMonService/PMMainThread.cpp b/IntelPresentMon/PresentMonService/PMMainThread.cpp index 1beb1920..0476e403 100644 --- a/IntelPresentMon/PresentMonService/PMMainThread.cpp +++ b/IntelPresentMon/PresentMonService/PMMainThread.cpp @@ -241,10 +241,10 @@ void PresentMonMainThread(Service* const pSvc) // create service-side comms object for transmitting introspection data to clients std::unique_ptr pComms; try { - ipc::ShmNamer shmNamer{ {}, opt.shmNamePrefix.AsOptional() }; - pmlog_info("Creating comms with introspection shm name: ") - .pmwatch(shmNamer.MakeIntrospectionName()); - pComms = ipc::MakeServiceComms(opt.shmNamePrefix.AsOptional()); + pmlog_dbg("Creating comms with shm prefix: ").pmwatch(*opt.shmNamePrefix); + pComms = ipc::MakeServiceComms(*opt.shmNamePrefix); + pmlog_info("Created comms with introspection shm name: ") + .pmwatch(pComms->GetNamer().MakeIntrospectionName()); } catch (const std::exception& e) { LOG(ERROR) << "Failed making service comms> " << e.what() << std::endl; diff --git a/IntelPresentMon/SampleClient/CliOptions.h b/IntelPresentMon/SampleClient/CliOptions.h index 10edc478..3764d5ee 100644 --- a/IntelPresentMon/SampleClient/CliOptions.h +++ b/IntelPresentMon/SampleClient/CliOptions.h @@ -36,7 +36,6 @@ namespace clio Option submode{ this, "--submode", 0, "Which submode option to run for the given mode" }; private: Group gc_{ this, "Connection", "Control client connection" }; public: Option controlPipe{ this, "--control-pipe", "", "Name of the named pipe to use for the client-service control channel" }; - Option introNsm{ this, "--intro-nsm", "", "Name of the NSM used for introspection data" }; Option middlewareDllPath{ this, "--middleware-dll-path", "", "Override middleware DLL path discovery with custom path" }; private: Group gs_{ this, "Sampling", "Control sampling / targeting behavior" }; public: Option metricOffset{ this, "--metric-offset", 1064., "Offset from top for frame data. Used in --dynamic-query-sample" }; diff --git a/IntelPresentMon/SampleClient/SampleClient.cpp b/IntelPresentMon/SampleClient/SampleClient.cpp index b8390fd6..f0b5f709 100644 --- a/IntelPresentMon/SampleClient/SampleClient.cpp +++ b/IntelPresentMon/SampleClient/SampleClient.cpp @@ -61,8 +61,7 @@ void RunPlaybackFrameQuery() "PresentMonService.exe"s, // "--timed-stop"s, "10000"s, "--control-pipe"s, pipeName, - "--nsm-prefix"s, "pmon_nsm_tt_"s, - "--intro-nsm"s, "svc-intro-tt"s, + "--shm-name-prefix"s, "pm-tt-shm"s, "--etw-session-name"s, "svc-sesh-tt"s, bp::args(dargs), }; @@ -147,8 +146,7 @@ void RunPlaybackDynamicQuery() "PresentMonService.exe"s, // "--timed-stop"s, "10000"s, "--control-pipe"s, pipeName, - "--nsm-prefix"s, "pmon_nsm_tt_"s, - "--intro-nsm"s, "svc-intro-tt"s, + "--shm-name-prefix"s, "pm-tt-shm"s, "--etw-session-name"s, "svc-sesh-tt"s, bp::args(dargs), }; @@ -222,8 +220,7 @@ void RunPlaybackDynamicQueryN() "PresentMonService.exe"s, // "--timed-stop"s, "10000"s, "--control-pipe"s, pipeName, - "--nsm-prefix"s, "pmon_nsm_tt_"s, - "--intro-nsm"s, "svc-intro-tt"s, + "--shm-name-prefix"s, "pm-tt-shm"s, "--etw-session-name"s, "svc-sesh-tt"s, bp::args(dargs), }; @@ -287,8 +284,7 @@ void IntrospectAllDynamicOptions() bp::child svc{ "PresentMonService.exe"s, "--control-pipe"s, pipeName, - "--nsm-prefix"s, "pmon_nsm_tt_"s, - "--intro-nsm"s, "svc-intro-tt"s, + "--shm-name-prefix"s, "pm-tt-shm"s, "--etw-session-name"s, "svc-sesh-tt"s, }; From 5975f7785dc42e4764b2b13b0145d7bdf8831d5e Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Thu, 4 Dec 2025 13:11:27 -0800 Subject: [PATCH 021/205] Added implementation of frame based CPU and GPU metrics. WIP --- .../CommonUtilities/mc/MetricsCalculator.cpp | 176 +++ .../CommonUtilities/mc/MetricsTypes.h | 14 +- IntelPresentMon/UnitTests/MetricsCore.cpp | 1150 +++++++++++------ 3 files changed, 965 insertions(+), 375 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 26a522da..3320fad4 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -108,6 +108,130 @@ namespace pmon::util::metrics } } + void ComputeMsCpuBusy( + const QpcConverter& qpc, + const SwapChainCoreState& swapChain, + const FrameData& present, + bool isAppPresent, + FrameMetrics& out) + { + //out.msCPUBusy = std::nullopt; + out.msCPUBusy = 0.0; + if (isAppPresent) { + auto cpuStart = CalculateCPUStart(swapChain, present); + if (cpuStart != 0) { + if (present.appPropagatedPresentStartTime != 0) { + out.msCPUBusy = qpc.DeltaUnsignedMilliSeconds(cpuStart, present.appPropagatedPresentStartTime); + } else if (present.presentStartTime != 0) { + out.msCPUBusy = qpc.DeltaUnsignedMilliSeconds(cpuStart, present.presentStartTime); + } + } + } + } + + void ComputeMsCpuWait( + const QpcConverter& qpc, + const FrameData& present, + bool isAppPresent, + FrameMetrics& out) + { + //out.msCPUWait = std::nullopt; + out.msCPUWait = 0.0; + if (isAppPresent) { + if (present.appPropagatedTimeInPresent != 0) { + out.msCPUWait = qpc.DurationMilliSeconds(present.appPropagatedTimeInPresent); + } + else if (present.timeInPresent != 0) { + out.msCPUWait = qpc.DurationMilliSeconds(present.timeInPresent); + } + } + } + + void ComputeMsGpuLatency( + const QpcConverter& qpc, + const SwapChainCoreState& swapChain, + const FrameData& present, + bool isAppPresent, + FrameMetrics& out) + { + //out.msGPULatency = std::nullopt; + out.msGPULatency = 0.0; + if (isAppPresent) { + auto cpuStart = CalculateCPUStart(swapChain, present); + if (cpuStart != 0) { + if (present.appPropagatedGPUStartTime != 0) { + out.msGPULatency = qpc.DeltaUnsignedMilliSeconds(cpuStart, present.appPropagatedGPUStartTime); + } + else if (present.gpuStartTime != 0) { + out.msGPULatency = qpc.DeltaUnsignedMilliSeconds(cpuStart, present.gpuStartTime); + } + } + } + } + + double ComputeMsGpuBusy( + const QpcConverter& qpc, + const FrameData& present, + bool isAppPresent) + { + //out.msGPUBusy = std::nullopt; + double msGPUBusy = 0.0; + if (isAppPresent) { + if (present.appPropagatedGPUDuration != 0) { + msGPUBusy = qpc.DurationMilliSeconds(present.appPropagatedGPUDuration); + } + else if (present.timeInPresent != 0) { + msGPUBusy = qpc.DurationMilliSeconds(present.gpuDuration); + } + } + return msGPUBusy; + } + + void ComputeMsVideoBusy( + const QpcConverter& qpc, + const FrameData& present, + bool isAppPresent, + FrameMetrics& out) + { + //out.msVideoBusy = std::nullopt; + out.msVideoBusy = 0.0; + if (isAppPresent) { + if (present.appPropagatedGPUVideoDuration != 0) { + out.msVideoBusy = qpc.DurationMilliSeconds(present.appPropagatedGPUVideoDuration); + } + else if (present.timeInPresent != 0) { + out.msVideoBusy = qpc.DurationMilliSeconds(present.gpuVideoDuration); + } + } + } + + double ComputeMsGpuDuration ( + const QpcConverter& qpc, + const FrameData& present, + bool isAppPresent) + { + //msGPUDuration = std::nullopt; + double msGPUDuration = 0.0; + if (isAppPresent) { + if (present.appPropagatedGPUStartTime != 0 || present.appPropagatedReadyTime != 0) { + msGPUDuration = qpc.DeltaUnsignedMilliSeconds(present.appPropagatedGPUStartTime, present.appPropagatedReadyTime); + } + else if (present.gpuStartTime != 0 || present.readyTime != 0) { + msGPUDuration = qpc.DeltaUnsignedMilliSeconds(present.gpuStartTime, present.readyTime); + } + } + return msGPUDuration; + } + + void ComputeMsGpuWait( + const QpcConverter& qpc, + const FrameData& present, + bool isAppPresent, + FrameMetrics& out) + { + out.msGPUWait = std::max(0.0, ComputeMsGpuDuration(qpc, present, isAppPresent) - ComputeMsGpuBusy(qpc, present, isAppPresent)); + } + void AdjustScreenTimeForCollapsedPresentNV( const FrameData& present, FrameData* nextDisplayedPresent, @@ -349,6 +473,51 @@ namespace pmon::util::metrics metrics.screenTimeQpc = screenTime; } + void CalculateCpuGpuMetrics( + const QpcConverter& qpc, + const SwapChainCoreState& chainState, + const FrameData& present, + bool isAppFrame, + FrameMetrics& metrics) + { + ComputeMsCpuBusy( + qpc, + chainState, + present, + isAppFrame, + metrics); + + ComputeMsCpuWait( + qpc, + present, + isAppFrame, + metrics); + + ComputeMsGpuLatency( + qpc, + chainState, + present, + isAppFrame, + metrics); + + metrics.msGPUBusy = ComputeMsGpuBusy( + qpc, + present, + isAppFrame); + + ComputeMsVideoBusy( + qpc, + present, + isAppFrame, + metrics); + + ComputeMsGpuWait( + qpc, + present, + isAppFrame, + metrics); + } + ComputedMetrics ComputeFrameMetrics( const QpcConverter& qpc, const FrameData& present, @@ -377,6 +546,13 @@ namespace pmon::util::metrics nextScreenTime, metrics); + CalculateCpuGpuMetrics( + qpc, + chain, + present, + isAppFrame, + metrics); + metrics.cpuStartQpc = CalculateCPUStart(chain, present); return result; diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h index 951dc240..9dff2553 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h @@ -136,6 +136,13 @@ namespace pmon::util::metrics { double msInPresentApi; double msUntilRenderComplete; + // Display Metrics (displayed frames only) + double msDisplayLatency; + double msDisplayedTime; + double msUntilDisplayed; + double msBetweenDisplayChange; + uint64_t screenTimeQpc; + // CPU Metrics (app frames only) uint64_t cpuStartQpc; double msCPUBusy; @@ -147,13 +154,6 @@ namespace pmon::util::metrics { double msVideoBusy; double msGPUWait; - // Display Metrics (displayed frames only) - double msDisplayLatency; - double msDisplayedTime; - double msUntilDisplayed; - double msBetweenDisplayChange; - uint64_t screenTimeQpc; - // Input Latency (optional, app+displayed only) std::optional msClickToPhotonLatency; std::optional msAllInputPhotonLatency; diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index 26382bc6..f4408e8f 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -2876,34 +2876,38 @@ namespace MetricsCoreTests TEST_METHOD(CPUBusy_BasicCase_StandardPath) { // No propagated data in lastAppPresent - // cpuStart = 1'000'000 (prior frame start + timeInPresent) - // presentStartTime = 1'100'000 - // QPC frequency: 10 MHz + // cpuStart = 1'000'000 (prior frame start + timeInPresent) + // presentStartTime = 1'100'000 + // QPC frequency: 10 MHz QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData priorApp = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 800'000, - /*timeInPresent*/ 200'000, - /*readyTime*/ 1'000'000, - /*displayed*/{}); + FrameData priorFrame{}; + priorFrame.presentStartTime = 800'000; + priorFrame.timeInPresent = 200'000; + priorFrame.readyTime = 1'100'000; + priorFrame.setFinalState(PresentResult::Presented); + priorFrame.displayed.push_back({ FrameType::Application, 1'200'000 }); - chain.lastAppPresent = priorApp; + chain.lastAppPresent = priorFrame; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'100'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'200'000, - /*displayed*/{}); + FrameData frame{}; + frame.presentStartTime = 1'100'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'200'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'300'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + FrameData next{}; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 1'400'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; // cpuStart = 800'000 + 200'000 = 1'000'000 - // msCPUBusy = 1'100'000 - 1'000'000 = 100'000 ticks = 10 ms + // msCPUBusy = 1'100'000 - 1'000'000 = 100'000 ticks = 10 ms double expected = qpc.DeltaUnsignedMilliSeconds(1'000'000, 1'100'000); Assert::AreEqual(expected, m.msCPUBusy, 0.0001); } @@ -2911,36 +2915,44 @@ namespace MetricsCoreTests TEST_METHOD(CPUBusy_WithAppPropagatedData) { // lastAppPresent has appPropagatedPresentStartTime set + // We need to ensure the frame is displayed so CPU metrics are computed QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData priorApp = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'000'000, - /*timeInPresent*/ 200'000, - /*readyTime*/ 1'200'000, - /*displayed*/{}, - /*appSimStartTime*/ 0, - /*pclSimStartTime*/ 0, - /*flipDelay*/ 0); - + // Prior app frame with propagated data + FrameData priorApp{}; + priorApp.presentStartTime = 1'000'000; + priorApp.timeInPresent = 200'000; + priorApp.readyTime = 1'200'000; priorApp.appPropagatedPresentStartTime = 800'000; priorApp.appPropagatedTimeInPresent = 200'000; + priorApp.setFinalState(PresentResult::Presented); + priorApp.displayed.push_back({ FrameType::Application, 1'300'000 }); + chain.lastAppPresent = priorApp; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'500'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'600'000, - /*displayed*/{}); + // Current frame (app frame, displayed) + FrameData frame{}; + frame.presentStartTime = 1'500'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'600'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'700'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 2'000'000; + next.timeInPresent = 80'000; + next.readyTime = 2'100'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 2'200'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; - // cpuStart = 800'000 + 200'000 = 1'000'000 (uses appPropagated) - // msCPUBusy = 1'500'000 - 1'000'000 = 500'000 ticks = 50 ms + // cpuStart = 800'000 + 200'000 = 1'000'000 (uses appPropagated from priorApp) + // msCPUBusy = 1'500'000 - 1'000'000 = 500'000 ticks = 50 ms double expected = qpc.DeltaUnsignedMilliSeconds(1'000'000, 1'500'000); Assert::AreEqual(expected, m.msCPUBusy, 0.0001); } @@ -2948,23 +2960,35 @@ namespace MetricsCoreTests TEST_METHOD(CPUBusy_FirstFrameNoPriorAppPresent) { // No lastAppPresent in chain state - // cpuStart = 0 (default fallback) + // cpuStart = 0 (default fallback) + // presentStartTime = 5'000'000 + // Expected msCPUBusy = 5'000'000 ticks = 500 ms QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; + // No prior app present set - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 5'000'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 5'200'000, - /*displayed*/{}); + // Current frame (app frame, displayed) + FrameData frame{}; + frame.presentStartTime = 5'000'000; + frame.timeInPresent = 100'000; + frame.readyTime = 5'200'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 5'500'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 6'000'000; + next.timeInPresent = 80'000; + next.readyTime = 6'100'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 6'300'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; - // cpuStart = 0 - // msCPUBusy = 5'000'000 - 0 = 5'000'000 ticks = 500 ms + // cpuStart = 0 (no prior app present) + // msCPUBusy = 5'000'000 - 0 = 5'000'000 ticks = 500 ms double expected = qpc.DeltaUnsignedMilliSeconds(0, 5'000'000); Assert::AreEqual(expected, m.msCPUBusy, 0.0001); } @@ -2972,47 +2996,82 @@ namespace MetricsCoreTests TEST_METHOD(CPUBusy_ZeroTimeInPresent) { // cpuStart = 1'000'000 - // presentStartTime = 1'000'000 (same as cpuStart) + // presentStartTime = 1'000'000 (same as cpuStart) + // timeInPresent = 0 (zero present duration) + // Expected msCPUBusy = 0 QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData priorApp = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 800'000, - /*timeInPresent*/ 200'000, - /*readyTime*/ 1'000'000, - /*displayed*/{}); + // Prior app frame + FrameData priorFrame{}; + priorFrame.presentStartTime = 800'000; + priorFrame.timeInPresent = 200'000; + priorFrame.readyTime = 1'100'000; + priorFrame.setFinalState(PresentResult::Presented); + priorFrame.displayed.push_back({ FrameType::Application, 1'200'000 }); - chain.lastAppPresent = priorApp; + chain.lastAppPresent = priorFrame; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'000'000, // Same as cpuStart - /*timeInPresent*/ 0, // Zero present duration - /*readyTime*/ 1'000'000, - /*displayed*/{}); + // Current frame with zero timeInPresent + FrameData frame{}; + frame.presentStartTime = 1'000'000; // Same as cpuStart + frame.timeInPresent = 0; // Zero present duration + frame.readyTime = 1'000'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'100'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'500'000; + next.timeInPresent = 50'000; + next.readyTime = 1'600'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 1'700'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; + // cpuStart = 800'000 + 200'000 = 1'000'000 + // presentStartTime = 1'000'000 (same as cpuStart) + // msCPUBusy = 1'000'000 - 1'000'000 = 0 ticks = 0 ms Assert::AreEqual(0.0, m.msCPUBusy, 0.0001); } TEST_METHOD(CPUWait_BasicCase_StandardPath) { // timeInPresent = 200'000 ticks + // Expected msCPUWait = 200'000 / 10'000'000 = 0.02 ms = 20 ms QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'000'000, - /*timeInPresent*/ 200'000, - /*readyTime*/ 1'200'000, - /*displayed*/{}); + // Prior app frame + FrameData priorFrame{}; + priorFrame.presentStartTime = 800'000; + priorFrame.timeInPresent = 100'000; + priorFrame.readyTime = 1'100'000; + priorFrame.setFinalState(PresentResult::Presented); + priorFrame.displayed.push_back({ FrameType::Application, 1'200'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + chain.lastAppPresent = priorFrame; + + // Current frame with timeInPresent = 200'000 + FrameData frame{}; + frame.presentStartTime = 1'100'000; + frame.timeInPresent = 200'000; // 20 ms + frame.readyTime = 1'300'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'400'000 }); + + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'800'000; + next.timeInPresent = 50'000; + next.readyTime = 1'900'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 2'000'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; @@ -3024,23 +3083,43 @@ namespace MetricsCoreTests TEST_METHOD(CPUWait_WithAppPropagatedTimeInPresent) { // appPropagatedTimeInPresent = 150'000 ticks + // Expected msCPUWait = 150'000 / 10'000'000 = 0.015 ms = 15 ms QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'000'000, - /*timeInPresent*/ 200'000, // Regular time (not used when propagated available) - /*readyTime*/ 1'200'000, - /*displayed*/{}); + // Prior app frame + FrameData priorFrame{}; + priorFrame.presentStartTime = 800'000; + priorFrame.timeInPresent = 100'000; + priorFrame.readyTime = 1'100'000; + priorFrame.setFinalState(PresentResult::Presented); + priorFrame.displayed.push_back({ FrameType::Application, 1'200'000 }); - frame.appPropagatedTimeInPresent = 150'000; + chain.lastAppPresent = priorFrame; - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Current frame with appPropagatedTimeInPresent = 150'000 + FrameData frame{}; + frame.presentStartTime = 1'100'000; + frame.timeInPresent = 200'000; // Regular time (not used when propagated available) + frame.readyTime = 1'300'000; + frame.appPropagatedTimeInPresent = 150'000; // 15 ms (propagated value) + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'400'000 }); + + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'800'000; + next.timeInPresent = 50'000; + next.readyTime = 1'900'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 2'000'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; - // When appPropagated is available, use it + // When appPropagated is available, use it instead of regular timeInPresent + // msCPUWait = 150'000 ticks = 15 ms double expected = qpc.DurationMilliSeconds(150'000); Assert::AreEqual(expected, m.msCPUWait, 0.0001); } @@ -3048,20 +3127,41 @@ namespace MetricsCoreTests TEST_METHOD(CPUWait_ZeroDuration) { // timeInPresent = 0 + // Expected msCPUWait = 0 / 10'000'000 = 0 ms QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'000'000, - /*timeInPresent*/ 0, - /*readyTime*/ 1'000'000, - /*displayed*/{}); + // Prior app frame + FrameData priorFrame{}; + priorFrame.presentStartTime = 800'000; + priorFrame.timeInPresent = 100'000; + priorFrame.readyTime = 1'100'000; + priorFrame.setFinalState(PresentResult::Presented); + priorFrame.displayed.push_back({ FrameType::Application, 1'200'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + chain.lastAppPresent = priorFrame; + + // Current frame with zero timeInPresent + FrameData frame{}; + frame.presentStartTime = 1'100'000; + frame.timeInPresent = 0; // Zero CPU wait time + frame.readyTime = 1'100'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'200'000 }); + + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'600'000; + next.timeInPresent = 50'000; + next.readyTime = 1'700'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 1'800'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; + // msCPUWait = 0 ticks = 0 ms Assert::AreEqual(0.0, m.msCPUWait, 0.0001); } }; @@ -3076,35 +3176,43 @@ namespace MetricsCoreTests TEST_METHOD(GPULatency_BasicCase_StandardPath) { // cpuStart = 1'000'000 - // gpuStartTime = 1'050'000 + // gpuStartTime = 1'050'000 QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData priorApp = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 800'000, - /*timeInPresent*/ 200'000, - /*readyTime*/ 1'000'000, - /*displayed*/{}); + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + priorApp.readyTime = 1'000'000; + priorApp.setFinalState(PresentResult::Presented); + priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'200'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'300'000, - /*displayed*/{}); - + // Current frame + FrameData frame{}; + frame.presentStartTime = 1'200'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'300'000; frame.gpuStartTime = 1'050'000; frame.gpuDuration = 200'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'400'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'600'000; + next.timeInPresent = 50'000; + next.readyTime = 1'700'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 1'800'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; // cpuStart = 800'000 + 200'000 = 1'000'000 - // msGPULatency = 1'050'000 - 1'000'000 = 50'000 ticks = 5 ms + // msGPULatency = 1'050'000 - 1'000'000 = 50'000 ticks = 5 ms double expected = qpc.DeltaUnsignedMilliSeconds(1'000'000, 1'050'000); Assert::AreEqual(expected, m.msGPULatency, 0.0001); } @@ -3115,32 +3223,41 @@ namespace MetricsCoreTests QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData priorApp = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 800'000, - /*timeInPresent*/ 200'000, - /*readyTime*/ 1'000'000, - /*displayed*/{}); + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + priorApp.readyTime = 1'000'000; + priorApp.setFinalState(PresentResult::Presented); + priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'200'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'300'000, - /*displayed*/{}); - + // Current frame with app propagated GPU start + FrameData frame{}; + frame.presentStartTime = 1'200'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'300'000; frame.gpuStartTime = 1'050'000; // Not used when propagated available + frame.gpuDuration = 200'000; frame.appPropagatedGPUStartTime = 1'080'000; frame.appPropagatedGPUDuration = 200'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'400'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'600'000; + next.timeInPresent = 50'000; + next.readyTime = 1'700'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 1'800'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; // cpuStart = 1'000'000 - // msGPULatency = 1'080'000 - 1'000'000 = 80'000 ticks = 8 ms + // msGPULatency = 1'080'000 - 1'000'000 = 80'000 ticks = 8 ms double expected = qpc.DeltaUnsignedMilliSeconds(1'000'000, 1'080'000); Assert::AreEqual(expected, m.msGPULatency, 0.0001); } @@ -3148,30 +3265,38 @@ namespace MetricsCoreTests TEST_METHOD(GPULatency_GPUStartBeforeCpuStart) { // cpuStart = 2'000'000 - // gpuStartTime = 1'900'000 (impossible but defensive) + // gpuStartTime = 1'900'000 (impossible but defensive) QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData priorApp = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'500'000, - /*timeInPresent*/ 500'000, // cpuStart = 2'000'000 - /*readyTime*/ 2'000'000, - /*displayed*/{}); + FrameData priorApp{}; + priorApp.presentStartTime = 1'500'000; + priorApp.timeInPresent = 500'000; // cpuStart = 2'000'000 + priorApp.readyTime = 2'000'000; + priorApp.setFinalState(PresentResult::Presented); + priorApp.displayed.push_back({ FrameType::Application, 2'100'000 }); chain.lastAppPresent = priorApp; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 2'200'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 2'300'000, - /*displayed*/{}); - + // Current frame with GPU start before CPU start + FrameData frame{}; + frame.presentStartTime = 2'200'000; + frame.timeInPresent = 100'000; + frame.readyTime = 2'300'000; frame.gpuStartTime = 1'900'000; // Earlier than cpuStart frame.gpuDuration = 300'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 2'400'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 2'600'000; + next.timeInPresent = 50'000; + next.readyTime = 2'700'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 2'800'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; @@ -3182,20 +3307,39 @@ namespace MetricsCoreTests TEST_METHOD(GPUBusy_BasicCase_StandardPath) { // gpuDuration = 500'000 ticks + // Expected msGPUBusy = 500'000 / 10'000'000 = 0.05 ms = 50 ms QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'000'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'200'000, - /*displayed*/{}); + // Prior app frame + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + priorApp.readyTime = 1'000'000; + priorApp.setFinalState(PresentResult::Presented); + priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + chain.lastAppPresent = priorApp; + + // Current frame + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'200'000; frame.gpuStartTime = 1'050'000; frame.gpuDuration = 500'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'300'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'500'000; + next.timeInPresent = 50'000; + next.readyTime = 1'600'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 1'700'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; @@ -3206,191 +3350,327 @@ namespace MetricsCoreTests TEST_METHOD(GPUBusy_ZeroDuration) { + // gpuDuration = 0 + // Expected msGPUBusy = 0 ms QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'000'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'200'000, - /*displayed*/{}); + // Prior app frame + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + priorApp.readyTime = 1'000'000; + priorApp.setFinalState(PresentResult::Presented); + priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + + chain.lastAppPresent = priorApp; + // Current frame with zero GPU duration + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'200'000; frame.gpuStartTime = 1'050'000; frame.gpuDuration = 0; // No GPU work + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'300'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'500'000; + next.timeInPresent = 50'000; + next.readyTime = 1'600'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 1'700'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; + // msGPUBusy = 0 ticks = 0 ms Assert::AreEqual(0.0, m.msGPUBusy, 0.0001); } TEST_METHOD(GPUBusy_WithAppPropagatedDuration) { // appPropagatedGPUDuration = 450'000 ticks + // Expected msGPUBusy = 450'000 / 10'000'000 = 0.045 ms = 45 ms QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'000'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'200'000, - /*displayed*/{}); + // Prior app frame + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + priorApp.readyTime = 1'000'000; + priorApp.setFinalState(PresentResult::Presented); + priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + chain.lastAppPresent = priorApp; + + // Current frame with app propagated GPU duration + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'200'000; frame.gpuStartTime = 1'050'000; frame.gpuDuration = 500'000; // Not used when propagated available frame.appPropagatedGPUStartTime = 1'050'000; frame.appPropagatedGPUDuration = 450'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'300'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'500'000; + next.timeInPresent = 50'000; + next.readyTime = 1'600'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 1'700'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; - // Uses appPropagated + // Uses appPropagated: 450'000 ticks = 45 ms double expected = qpc.DurationMilliSeconds(450'000); Assert::AreEqual(expected, m.msGPUBusy, 0.0001); } TEST_METHOD(GPUWait_BasicCase_BusyLessThanTotal) { - // gpuStartTime = 1'000'000, readyTime = 1'600'000 ? total = 600'000 - // gpuDuration (busy) = 500'000 - // msGPUWait should be 100'000 ticks = 10 ms + // gpuStartTime = 1'000'000, readyTime = 1'600'000 → total = 600'000 + // gpuDuration (busy) = 500'000 + // msGPUWait should be 100'000 ticks = 10 ms QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'000'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'600'000, - /*displayed*/{}); + // Prior app frame + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + priorApp.readyTime = 1'000'000; + priorApp.setFinalState(PresentResult::Presented); + priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + + chain.lastAppPresent = priorApp; + // Current frame + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'600'000; frame.gpuStartTime = 1'000'000; frame.gpuDuration = 500'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'700'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'900'000; + next.timeInPresent = 50'000; + next.readyTime = 2'000'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 2'100'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; // Total = 1'600'000 - 1'000'000 = 600'000 + // msGPUBusy = 500'000 ticks = 50 ms // msGPUWait = 600'000 - 500'000 = 100'000 ticks = 10 ms double expectedTotal = qpc.DeltaUnsignedMilliSeconds(1'000'000, 1'600'000); - double expectedWait = max(0.0, expectedTotal - m.msGPUBusy); + double expectedBusy = qpc.DurationMilliSeconds(500'000); + double expectedWait = max(0.0, expectedTotal - expectedBusy); Assert::AreEqual(expectedWait, m.msGPUWait, 0.0001); } TEST_METHOD(GPUWait_BusyEqualsTotal) { - // gpuStartTime = 1'000'000, readyTime = 1'600'000 ? total = 600'000 - // gpuDuration = 600'000 (fully busy) - // msGPUWait should be 0 + // gpuStartTime = 1'000'000, readyTime = 1'600'000 → total = 600'000 + // gpuDuration = 600'000 (fully busy) + // msGPUWait should be 0 QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'000'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'600'000, - /*displayed*/{}); + // Prior app frame + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + priorApp.readyTime = 1'000'000; + priorApp.setFinalState(PresentResult::Presented); + priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + + chain.lastAppPresent = priorApp; + // Current frame with GPU duration equal to total time + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'600'000; frame.gpuStartTime = 1'000'000; frame.gpuDuration = 600'000; // Equal to total + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'700'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'900'000; + next.timeInPresent = 50'000; + next.readyTime = 2'000'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 2'100'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; + // Total = 1'600'000 - 1'000'000 = 600'000 + // msGPUBusy = 600'000 ticks = 60 ms (equal to total) + // msGPUWait = 600'000 - 600'000 = 0 ms Assert::AreEqual(0.0, m.msGPUWait, 0.0001); } TEST_METHOD(GPUWait_BusyGreaterThanTotal) { - // gpuStartTime = 1'000'000, readyTime = 1'600'000 ? total = 600'000 - // gpuDuration = 700'000 (impossible, but defensive) - // msGPUWait should clamp to 0 + // gpuStartTime = 1'000'000, readyTime = 1'600'000 → total = 600'000 + // gpuDuration = 700'000 (impossible, but defensive) + // msGPUWait should clamp to 0 QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'000'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'600'000, - /*displayed*/{}); + // Prior app frame + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + priorApp.readyTime = 1'000'000; + priorApp.setFinalState(PresentResult::Presented); + priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + + chain.lastAppPresent = priorApp; + // Current frame with GPU duration greater than total (impossible case) + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'600'000; frame.gpuStartTime = 1'000'000; frame.gpuDuration = 700'000; // Greater than total (impossible) + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'700'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'900'000; + next.timeInPresent = 50'000; + next.readyTime = 2'000'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 2'100'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; - // Should clamp to 0 + // Total = 1'600'000 - 1'000'000 = 600'000 + // msGPUBusy = 700'000 ticks = 70 ms (greater than total) + // msGPUWait should clamp to 0 Assert::AreEqual(0.0, m.msGPUWait, 0.0001); } TEST_METHOD(GPUWait_WithAppPropagatedData) { - // appPropagatedGPUStartTime = 1'000'000, appPropagatedReadyTime = 1'550'000 ? total = 550'000 - // appPropagatedGPUDuration = 450'000 - // msGPUWait should be 100'000 ticks = 10 ms + // appPropagatedGPUStartTime = 1'000'000, appPropagatedReadyTime = 1'550'000 → total = 550'000 + // appPropagatedGPUDuration = 450'000 + // msGPUWait should be 100'000 ticks = 10 ms QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'000'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'600'000, - /*displayed*/{}); + // Prior app frame + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + priorApp.readyTime = 1'000'000; + priorApp.setFinalState(PresentResult::Presented); + priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + chain.lastAppPresent = priorApp; + + // Current frame with app propagated GPU data + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'600'000; frame.gpuStartTime = 1'000'000; frame.gpuDuration = 600'000; frame.appPropagatedGPUStartTime = 1'000'000; frame.appPropagatedReadyTime = 1'550'000; frame.appPropagatedGPUDuration = 450'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'700'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'900'000; + next.timeInPresent = 50'000; + next.readyTime = 2'000'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 2'100'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; // Total = 1'550'000 - 1'000'000 = 550'000 - // msGPUWait = 550'000 - 450'000 = 100'000 ticks = 10 ms + // msGPUBusy = 450'000 ticks = 45 ms + // msGPUWait = 550'000 - 450'000 = 100'000 ticks = 10 ms double expectedTotal = qpc.DeltaUnsignedMilliSeconds(1'000'000, 1'550'000); - double expectedWait = max(0.0, expectedTotal - m.msGPUBusy); + double expectedBusy = qpc.DurationMilliSeconds(450'000); + double expectedWait = max(0.0, expectedTotal - expectedBusy); Assert::AreEqual(expectedWait, m.msGPUWait, 0.0001); } }; - // ============================================================================ - // GROUP C: VIDEO METRICS - // ============================================================================ - TEST_CLASS(GPUMetricsVideoTests) { public: TEST_METHOD(VideoBusy_BasicCase_StandardPath) { // gpuVideoDuration = 200'000 ticks + // Expected msVideoBusy = 200'000 / 10'000'000 = 0.02 ms = 20 ms QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'000'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'200'000, - /*displayed*/{}); + // Prior app frame + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + priorApp.readyTime = 1'000'000; + priorApp.setFinalState(PresentResult::Presented); + priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + + chain.lastAppPresent = priorApp; + // Current frame + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'200'000; frame.gpuStartTime = 1'050'000; frame.gpuDuration = 500'000; frame.gpuVideoDuration = 200'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'300'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'500'000; + next.timeInPresent = 50'000; + next.readyTime = 1'600'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 1'700'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; @@ -3401,21 +3681,41 @@ namespace MetricsCoreTests TEST_METHOD(VideoBusy_ZeroDuration) { + // gpuVideoDuration = 0 + // Expected msVideoBusy = 0 ms QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'000'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'200'000, - /*displayed*/{}); + // Prior app frame + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + priorApp.readyTime = 1'000'000; + priorApp.setFinalState(PresentResult::Presented); + priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + chain.lastAppPresent = priorApp; + + // Current frame with zero video duration + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'200'000; frame.gpuStartTime = 1'050'000; frame.gpuDuration = 500'000; frame.gpuVideoDuration = 0; // No video work + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'300'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'500'000; + next.timeInPresent = 50'000; + next.readyTime = 1'600'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 1'700'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; @@ -3425,28 +3725,47 @@ namespace MetricsCoreTests TEST_METHOD(VideoBusy_WithAppPropagatedData) { // appPropagatedGPUVideoDuration = 180'000 ticks + // Expected msVideoBusy = 180'000 / 10'000'000 = 0.018 ms = 18 ms QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'000'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'200'000, - /*displayed*/{}); + // Prior app frame + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + priorApp.readyTime = 1'000'000; + priorApp.setFinalState(PresentResult::Presented); + priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + + chain.lastAppPresent = priorApp; + // Current frame with app propagated GPU video duration + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'200'000; frame.gpuStartTime = 1'050'000; frame.gpuDuration = 500'000; frame.gpuVideoDuration = 200'000; // Not used when propagated available frame.appPropagatedGPUStartTime = 1'050'000; frame.appPropagatedGPUDuration = 450'000; frame.appPropagatedGPUVideoDuration = 180'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'300'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'500'000; + next.timeInPresent = 50'000; + next.readyTime = 1'600'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 1'700'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; - // Uses appPropagated + // Uses appPropagated: 180'000 ticks = 18 ms double expected = qpc.DurationMilliSeconds(180'000); Assert::AreEqual(expected, m.msVideoBusy, 0.0001); } @@ -3454,23 +3773,41 @@ namespace MetricsCoreTests TEST_METHOD(VideoBusy_OverlapWithGPUBusy) { // msGPUBusy = 50 ms (500'000 ticks) - // msVideoBusy = 20 ms (200'000 ticks) - // Verify both are independently computed (no constraint) + // msVideoBusy = 20 ms (200'000 ticks) + // Verify both are independently computed (no constraint) QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'000'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'200'000, - /*displayed*/{}); + // Prior app frame + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + priorApp.readyTime = 1'000'000; + priorApp.setFinalState(PresentResult::Presented); + priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + chain.lastAppPresent = priorApp; + + // Current frame + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'200'000; frame.gpuStartTime = 1'050'000; frame.gpuDuration = 500'000; frame.gpuVideoDuration = 200'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'300'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'500'000; + next.timeInPresent = 50'000; + next.readyTime = 1'600'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 1'700'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; @@ -3484,23 +3821,41 @@ namespace MetricsCoreTests TEST_METHOD(VideoBusy_LargerThanGPUBusy) { // msGPUBusy = 30 ms (computed from gpuDuration) - // msVideoBusy = 50 ms (computed from gpuVideoDuration) - // Verify independent computation (no implicit constraints) + // msVideoBusy = 50 ms (computed from gpuVideoDuration) + // Verify independent computation (no implicit constraints) QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'000'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'200'000, - /*displayed*/{}); + // Prior app frame + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + priorApp.readyTime = 1'000'000; + priorApp.setFinalState(PresentResult::Presented); + priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + + chain.lastAppPresent = priorApp; + // Current frame where video duration > GPU duration + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'200'000; frame.gpuStartTime = 1'050'000; frame.gpuDuration = 300'000; // 30 ms frame.gpuVideoDuration = 500'000; // 50 ms (larger than gpuDuration) + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'300'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'500'000; + next.timeInPresent = 50'000; + next.readyTime = 1'600'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 1'700'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; @@ -3508,6 +3863,7 @@ namespace MetricsCoreTests Assert::IsTrue(m.msVideoBusy > m.msGPUBusy); } }; + TEST_CLASS(EdgeCasesAndMissingData) { public: @@ -3526,16 +3882,23 @@ namespace MetricsCoreTests chain.lastAppPresent = priorApp; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'100'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'200'000, - /*displayed*/{}); + // Current frame with no GPU data + FrameData frame{}; + frame.presentStartTime = 1'100'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'200'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'300'000 }); - // No GPU data set (all zeros by default) + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'500'000; + next.timeInPresent = 50'000; + next.readyTime = 1'600'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 1'700'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; @@ -3563,21 +3926,23 @@ namespace MetricsCoreTests chain.lastAppPresent = priorApp; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'100'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'200'000, - std::vector>{ - { FrameType::Repeated, 1'300'000 } // Not Application - }); - + // Current frame with only Repeated display (not Application) + FrameData frame{}; + frame.presentStartTime = 1'100'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'200'000; frame.gpuStartTime = 1'150'000; frame.gpuDuration = 200'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Repeated, 1'300'000 }); + // Next displayed frame (required to process current frame's display) FrameData next{}; + next.presentStartTime = 1'500'000; + next.timeInPresent = 50'000; + next.readyTime = 1'600'000; next.setFinalState(PresentResult::Presented); - next.displayed.push_back({ FrameType::Application, 1'500'000 }); + next.displayed.push_back({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3597,24 +3962,24 @@ namespace MetricsCoreTests QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData priorApp = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 800'000, - /*timeInPresent*/ 200'000, - /*readyTime*/ 1'000'000, - /*displayed*/{}); + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + priorApp.readyTime = 1'000'000; + priorApp.setFinalState(PresentResult::Presented); + priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; - FrameData frame = MakeFrame( - PresentResult::Discarded, // Not displayed - /*presentStartTime*/ 1'100'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'200'000, - /*displayed*/{}); // No displayed entries - + // Current frame is not displayed (Discarded) + FrameData frame{}; + frame.presentStartTime = 1'100'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'200'000; frame.gpuStartTime = 1'150'000; frame.gpuDuration = 200'000; + frame.setFinalState(PresentResult::Discarded); + // No displayed entries auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3625,6 +3990,7 @@ namespace MetricsCoreTests Assert::IsTrue(m.msGPUBusy > 0); } }; + TEST_CLASS(StateAndHistory) { public: @@ -3634,23 +4000,32 @@ namespace MetricsCoreTests QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData lastApp = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 800'000, - /*timeInPresent*/ 200'000, - /*readyTime*/ 1'000'000, - /*displayed*/{}); + FrameData lastApp{}; + lastApp.presentStartTime = 800'000; + lastApp.timeInPresent = 200'000; + lastApp.readyTime = 1'000'000; + lastApp.setFinalState(PresentResult::Presented); + lastApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = lastApp; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'200'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'300'000, - /*displayed*/{}); + // Current frame + FrameData frame{}; + frame.presentStartTime = 1'200'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'300'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'400'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'600'000; + next.timeInPresent = 50'000; + next.readyTime = 1'700'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 1'800'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; @@ -3665,24 +4040,33 @@ namespace MetricsCoreTests QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData lastPresent = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 800'000, - /*timeInPresent*/ 200'000, - /*readyTime*/ 1'000'000, - /*displayed*/{}); + FrameData lastPresent{}; + lastPresent.presentStartTime = 800'000; + lastPresent.timeInPresent = 200'000; + lastPresent.readyTime = 1'000'000; + lastPresent.setFinalState(PresentResult::Presented); + lastPresent.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastPresent = lastPresent; // lastAppPresent remains unset - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'200'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'300'000, - /*displayed*/{}); + // Current frame + FrameData frame{}; + frame.presentStartTime = 1'200'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'300'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'400'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'600'000; + next.timeInPresent = 50'000; + next.readyTime = 1'700'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 1'800'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; @@ -3697,14 +4081,23 @@ namespace MetricsCoreTests QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; // Empty chain - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 5'000'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 5'200'000, - /*displayed*/{}); + // Current frame + FrameData frame{}; + frame.presentStartTime = 5'000'000; + frame.timeInPresent = 100'000; + frame.readyTime = 5'200'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 5'500'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 6'000'000; + next.timeInPresent = 50'000; + next.readyTime = 6'100'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 6'300'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; @@ -3718,21 +4111,22 @@ namespace MetricsCoreTests QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 5'000'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 5'200'000, - std::vector>{ - { FrameType::Application, 5'500'000 } - }, - /*appSimStartTime*/ 0, - /*pclSimStartTime*/ 0, - /*flipDelay*/ 777); + // Current frame + FrameData frame{}; + frame.presentStartTime = 5'000'000; + frame.timeInPresent = 100'000; + frame.readyTime = 5'200'000; + frame.flipDelay = 777; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 5'500'000 }); + // Next displayed frame (required to process current frame's display) FrameData next{}; + next.presentStartTime = 6'000'000; + next.timeInPresent = 50'000; + next.readyTime = 6'100'000; next.setFinalState(PresentResult::Presented); - next.displayed.push_back({ FrameType::Application, 6'000'000 }); + next.displayed.push_back({ FrameType::Application, 6'300'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3744,38 +4138,50 @@ namespace MetricsCoreTests Assert::AreEqual(uint64_t(777), chain.lastDisplayedFlipDelay); } }; + TEST_CLASS(NumericAndPrecision) { public: TEST_METHOD(CPUBusy_LargeValues_DoesNotOverflow) { // cpuStart = 1'000'000'000 (large QPC value) - // presentStartTime = 1'100'000'000 + // presentStartTime = 1'100'000'000 + // Expected msCPUBusy = 100'000'000 ticks = 10'000 ms (10 seconds) QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData priorApp = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 900'000'000, - /*timeInPresent*/ 100'000'000, - /*readyTime*/ 1'000'000'000, - /*displayed*/{}); + // Prior app frame with large values + FrameData priorApp{}; + priorApp.presentStartTime = 900'000'000; + priorApp.timeInPresent = 100'000'000; + priorApp.readyTime = 1'000'000'000; + priorApp.setFinalState(PresentResult::Presented); + priorApp.displayed.push_back({ FrameType::Application, 1'100'000'000 }); chain.lastAppPresent = priorApp; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'100'000'000, - /*timeInPresent*/ 100'000'000, - /*readyTime*/ 1'200'000'000, - /*displayed*/{}); + // Current frame with large values + FrameData frame{}; + frame.presentStartTime = 1'100'000'000; + frame.timeInPresent = 100'000'000; + frame.readyTime = 1'200'000'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'300'000'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'600'000'000; + next.timeInPresent = 50'000'000; + next.readyTime = 1'700'000'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 1'800'000'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; // cpuStart = 900'000'000 + 100'000'000 = 1'000'000'000 - // msCPUBusy = 1'100'000'000 - 1'000'000'000 = 100'000'000 ticks = 10'000 ms (10 seconds) + // msCPUBusy = 1'100'000'000 - 1'000'000'000 = 100'000'000 ticks = 10'000 ms (10 seconds) double expected = qpc.DeltaUnsignedMilliSeconds(1'000'000'000, 1'100'000'000); Assert::AreEqual(expected, m.msCPUBusy, 0.0001); // Verify large value is reasonable (10 seconds) @@ -3785,30 +4191,38 @@ namespace MetricsCoreTests TEST_METHOD(GPULatency_SmallDelta_HighPrecision) { // cpuStart = 1'000'000 - // gpuStartTime = 1'000'001 (1 tick delta; tiny latency) + // gpuStartTime = 1'000'001 (1 tick delta; tiny latency) QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - FrameData priorApp = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 800'000, - /*timeInPresent*/ 200'000, - /*readyTime*/ 1'000'000, - /*displayed*/{}); + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + priorApp.readyTime = 1'000'000; + priorApp.setFinalState(PresentResult::Presented); + priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; - FrameData frame = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'100'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'200'000, - /*displayed*/{}); - + // Current frame with small GPU latency + FrameData frame{}; + frame.presentStartTime = 1'100'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'200'000; frame.gpuStartTime = 1'000'001; // Only 1 tick later than cpuStart frame.gpuDuration = 200'000; + frame.setFinalState(PresentResult::Presented); + frame.displayed.push_back({ FrameType::Application, 1'300'000 }); - auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); + // Next displayed frame (required to process current frame's display) + FrameData next{}; + next.presentStartTime = 1'500'000; + next.timeInPresent = 50'000; + next.readyTime = 1'600'000; + next.setFinalState(PresentResult::Presented); + next.displayed.push_back({ FrameType::Application, 1'700'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; @@ -3821,49 +4235,49 @@ namespace MetricsCoreTests TEST_METHOD(VideoBusy_ZeroAndNonzeroInSequence) { // Frame A: no video work - // Frame B: with video work + // Frame B: with video work QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; // Frame A: zero video - FrameData frameA = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 1'000'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 1'200'000, - std::vector>{ - { FrameType::Application, 1'500'000 } - }); - + FrameData frameA{}; + frameA.presentStartTime = 1'000'000; + frameA.timeInPresent = 100'000; + frameA.readyTime = 1'200'000; frameA.gpuStartTime = 1'050'000; frameA.gpuDuration = 400'000; frameA.gpuVideoDuration = 0; // No video + frameA.setFinalState(PresentResult::Presented); + frameA.displayed.push_back({ FrameType::Application, 1'500'000 }); FrameData nextA{}; + nextA.presentStartTime = 2'000'000; + nextA.timeInPresent = 50'000; + nextA.readyTime = 2'100'000; nextA.setFinalState(PresentResult::Presented); - nextA.displayed.push_back({ FrameType::Application, 2'000'000 }); + nextA.displayed.push_back({ FrameType::Application, 2'200'000 }); auto resultsA = ComputeMetricsForPresent(qpc, frameA, &nextA, chain); Assert::AreEqual(size_t(1), resultsA.size()); Assert::AreEqual(0.0, resultsA[0].metrics.msVideoBusy, 0.0001); // Frame B: with video - FrameData frameB = MakeFrame( - PresentResult::Presented, - /*presentStartTime*/ 2'100'000, - /*timeInPresent*/ 100'000, - /*readyTime*/ 2'300'000, - std::vector>{ - { FrameType::Application, 2'600'000 } - }); - + FrameData frameB{}; + frameB.presentStartTime = 2'100'000; + frameB.timeInPresent = 100'000; + frameB.readyTime = 2'300'000; frameB.gpuStartTime = 2'150'000; frameB.gpuDuration = 400'000; frameB.gpuVideoDuration = 300'000; // 30 ms of video + frameB.setFinalState(PresentResult::Presented); + frameB.displayed.push_back({ FrameType::Application, 2'600'000 }); FrameData nextB{}; + nextB.presentStartTime = 3'000'000; + nextB.timeInPresent = 50'000; + nextB.readyTime = 3'100'000; nextB.setFinalState(PresentResult::Presented); - nextB.displayed.push_back({ FrameType::Application, 3'000'000 }); + nextB.displayed.push_back({ FrameType::Application, 3'200'000 }); auto resultsB = ComputeMetricsForPresent(qpc, frameB, &nextB, chain); Assert::AreEqual(size_t(1), resultsB.size()); From 841270a81f6f67dc81d2c3b93eed573dca72883a Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 5 Dec 2025 14:35:44 +0900 Subject: [PATCH 022/205] populating cpu rings --- IntelPresentMon/ControlLib/CpuTelemetry.h | 1 + IntelPresentMon/ControlLib/WmiCpu.cpp | 6 ++ IntelPresentMon/ControlLib/WmiCpu.h | 1 + .../Interprocess/source/TelemetryMap.h | 10 +- .../PresentMonService/PMMainThread.cpp | 102 ++++++++++++------ 5 files changed, 87 insertions(+), 33 deletions(-) diff --git a/IntelPresentMon/ControlLib/CpuTelemetry.h b/IntelPresentMon/ControlLib/CpuTelemetry.h index 4da83714..40682b6b 100644 --- a/IntelPresentMon/ControlLib/CpuTelemetry.h +++ b/IntelPresentMon/ControlLib/CpuTelemetry.h @@ -17,6 +17,7 @@ class CpuTelemetry { public: virtual ~CpuTelemetry() = default; virtual bool Sample() noexcept = 0; + virtual const CpuTelemetryInfo& GetNewest() const noexcept = 0; virtual std::optional GetClosest( uint64_t qpc) const noexcept = 0; void SetTelemetryCapBit(CpuTelemetryCapBits telemetryCapBit) noexcept diff --git a/IntelPresentMon/ControlLib/WmiCpu.cpp b/IntelPresentMon/ControlLib/WmiCpu.cpp index df44cc90..d783b9fb 100644 --- a/IntelPresentMon/ControlLib/WmiCpu.cpp +++ b/IntelPresentMon/ControlLib/WmiCpu.cpp @@ -72,6 +72,12 @@ WmiCpu::WmiCpu() { // next_sample_qpc_.QuadPart += frequency_.QuadPart; } + +const CpuTelemetryInfo& WmiCpu::GetNewest() const noexcept +{ + return *history_.begin(); +} + bool WmiCpu::Sample() noexcept { DWORD counter_type; diff --git a/IntelPresentMon/ControlLib/WmiCpu.h b/IntelPresentMon/ControlLib/WmiCpu.h index 91d5ee9d..6d8370c8 100644 --- a/IntelPresentMon/ControlLib/WmiCpu.h +++ b/IntelPresentMon/ControlLib/WmiCpu.h @@ -19,6 +19,7 @@ class WmiCpu : public CpuTelemetry { public: WmiCpu(); bool Sample() noexcept override; + const CpuTelemetryInfo& GetNewest() const noexcept override; std::optional GetClosest( uint64_t qpc) const noexcept override; // types diff --git a/IntelPresentMon/Interprocess/source/TelemetryMap.h b/IntelPresentMon/Interprocess/source/TelemetryMap.h index 223bb53f..5443b761 100644 --- a/IntelPresentMon/Interprocess/source/TelemetryMap.h +++ b/IntelPresentMon/Interprocess/source/TelemetryMap.h @@ -9,13 +9,13 @@ namespace pmon::ipc // container for multiple history rings organized by PM_METRIC x index class TelemetryMap { + public: template using HistoryRingVect = ShmVector>; using MapValueType = std::variant< HistoryRingVect, HistoryRingVect, HistoryRingVect, HistoryRingVect>; using MapType = ShmMap; - public: using AllocatorType = MapType::allocator_type; TelemetryMap(AllocatorType alloc) : @@ -96,6 +96,14 @@ namespace pmon::ipc { return ringMap_.at(id); } + auto Rings() + { + return std::ranges::subrange{ ringMap_.begin(), ringMap_.end() }; + } + auto Rings() const + { + return std::ranges::subrange{ ringMap_.begin(), ringMap_.end() }; + } private: MapType ringMap_; }; diff --git a/IntelPresentMon/PresentMonService/PMMainThread.cpp b/IntelPresentMon/PresentMonService/PMMainThread.cpp index 0476e403..5e7aaa20 100644 --- a/IntelPresentMon/PresentMonService/PMMainThread.cpp +++ b/IntelPresentMon/PresentMonService/PMMainThread.cpp @@ -152,39 +152,77 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, } } -void CpuTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, - pwr::cpu::CpuTelemetry* const cpu) +void CpuTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, ipc::ServiceComms* pComms, + pwr::cpu::CpuTelemetry* const cpu) noexcept { - IntervalWaiter waiter{ 0.016 }; - if (srv == nullptr || pm == nullptr) { - // TODO: log error on this condition - return; - } + // we don't expect any exceptions in this system during normal operation + // (maybe during initialization at most, not during polling) + // but if they do happen, it is a halting condition for the system telemetry + // don't let this thread crash the process, just exit with an error for later + // diagnosis + try { + IntervalWaiter waiter{ 0.016 }; + if (srv == nullptr || pm == nullptr) { + // TODO: log error on this condition + return; + } - const HANDLE events[] { - pm->GetStreamingStartHandle(), - srv->GetServiceStopHandle(), - }; - - while (1) { - auto waitResult = WaitForMultipleObjects((DWORD)std::size(events), events, FALSE, INFINITE); - auto i = waitResult - WAIT_OBJECT_0; - if (i == 1) { - return; - } - while (WaitForSingleObject(srv->GetServiceStopHandle(), 0) != WAIT_OBJECT_0) { - cpu->Sample(); - // Convert from the ms to seconds as GetTelemetryPeriod returns back - // ms and SetInterval expects seconds. - waiter.SetInterval(pm->GetGpuTelemetryPeriod() / 1000.); - waiter.Wait(); - // Get the number of currently active streams - auto num_active_streams = pm->GetActiveStreams(); - if (num_active_streams == 0) { - break; - } - } - } + const HANDLE events[]{ + pm->GetStreamingStartHandle(), + srv->GetServiceStopHandle(), + }; + + while (1) { + auto waitResult = WaitForMultipleObjects((DWORD)std::size(events), events, FALSE, INFINITE); + auto i = waitResult - WAIT_OBJECT_0; + if (i == 1) { + return; + } + while (WaitForSingleObject(srv->GetServiceStopHandle(), 0) != WAIT_OBJECT_0) { + // TODO:streamer replace this flow with a call that populates rings of a store + cpu->Sample(); + // placeholder routine shim to translate cpu tranfer struct into rings + // replace with a direct mapping on PM_METRIC that obviates the switch + auto& store = pComms->GetSystemDataStore(); + auto& sample = cpu->GetNewest(); + for (auto&& [metric, ringVariant] : store.telemetryData.Rings()) { + // all cpu telemetry is double + auto& ringVect = std::get>(ringVariant); + switch (metric) { + case PM_METRIC_CPU_FREQUENCY: + ringVect[0].Push(sample.cpu_frequency, sample.qpc); + break; + case PM_METRIC_CPU_UTILIZATION: + ringVect[0].Push(sample.cpu_utilization, sample.qpc); + break; + case PM_METRIC_CPU_POWER: + ringVect[0].Push(sample.cpu_power_w, sample.qpc); + break; + case PM_METRIC_CPU_POWER_LIMIT: + ringVect[0].Push(sample.cpu_power_limit_w, sample.qpc); + break; + case PM_METRIC_CPU_TEMPERATURE: + ringVect[0].Push(sample.cpu_temperature, sample.qpc); + break; + default: + break; + } + } + // Convert from the ms to seconds as GetTelemetryPeriod returns back + // ms and SetInterval expects seconds. + waiter.SetInterval(pm->GetGpuTelemetryPeriod() / 1000.); + waiter.Wait(); + // Get the number of currently active streams + auto num_active_streams = pm->GetActiveStreams(); + if (num_active_streams == 0) { + break; + } + } + } + } + catch (...) { + pmlog_error(util::ReportException("Failure in telemetry loop")); + } } @@ -281,7 +319,7 @@ void PresentMonMainThread(Service* const pSvc) } if (cpu) { - cpuTelemetryThread = std::jthread{ CpuTelemetryThreadEntry_, pSvc, &pm, cpu.get() }; + cpuTelemetryThread = std::jthread{ CpuTelemetryThreadEntry_, pSvc, &pm, pComms.get(), cpu.get()}; pm.SetCpu(cpu); // sample once to populate the cap bits cpu->Sample(); From 81ad28390740fd5bc6298bb1d416f14834746ef6 Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 5 Dec 2025 16:50:16 +0900 Subject: [PATCH 023/205] populating system (cpu) statics --- IntelPresentMon/PresentMonService/PMMainThread.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/IntelPresentMon/PresentMonService/PMMainThread.cpp b/IntelPresentMon/PresentMonService/PMMainThread.cpp index 5e7aaa20..1d4b25a0 100644 --- a/IntelPresentMon/PresentMonService/PMMainThread.cpp +++ b/IntelPresentMon/PresentMonService/PMMainThread.cpp @@ -324,6 +324,8 @@ void PresentMonMainThread(Service* const pSvc) // sample once to populate the cap bits cpu->Sample(); // determine vendor based on device name + // TODO: move this logic either into system (CPU) provider or + // into the ipc components const auto vendor = [&] { const auto lowerNameRn = cpu->GetCpuName() | vi::transform(tolower); const std::string lowerName{ lowerNameRn.begin(), lowerNameRn.end() }; @@ -340,6 +342,13 @@ void PresentMonMainThread(Service* const pSvc) // register cpu pComms->RegisterCpuDevice(vendor, cpu->GetCpuName(), ipc::intro::ConvertBitset(cpu->GetCpuTelemetryCapBits())); + // after registering, we know that at lest the store is available even + // if the introspection itself is not complete + auto& systemStore = pComms->GetSystemDataStore(); + // TODO: replace this placeholder routine for populating statics + systemStore.statics.cpuName = cpu->GetCpuName().c_str(); + systemStore.statics.cpuPowerLimit = cpu->GetCpuPowerLimit(); + systemStore.statics.cpuVendor = vendor; } else { // We were unable to determine the cpu. pComms->RegisterCpuDevice(PM_DEVICE_VENDOR_UNKNOWN, "UNKNOWN_CPU", From f58c3ee88bbe2a8c1bd96c77ca60cc4101f52f05 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Fri, 5 Dec 2025 11:32:01 -0800 Subject: [PATCH 024/205] Added in AnimationError and Animation Time code and tests. WIP --- .../CommonUtilities/mc/MetricsCalculator.cpp | 104 +- .../CommonUtilities/mc/MetricsTypes.cpp | 2 +- .../CommonUtilities/mc/MetricsTypes.h | 1 + .../CommonUtilities/mc/SwapChainState.cpp | 18 +- IntelPresentMon/UnitTests/MetricsCore.cpp | 1865 +++++++++++++++++ 5 files changed, 1969 insertions(+), 21 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 3320fad4..0d9ec1ca 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -180,7 +180,7 @@ namespace pmon::util::metrics if (present.appPropagatedGPUDuration != 0) { msGPUBusy = qpc.DurationMilliSeconds(present.appPropagatedGPUDuration); } - else if (present.timeInPresent != 0) { + else if (present.gpuDuration != 0) { msGPUBusy = qpc.DurationMilliSeconds(present.gpuDuration); } } @@ -199,7 +199,7 @@ namespace pmon::util::metrics if (present.appPropagatedGPUVideoDuration != 0) { out.msVideoBusy = qpc.DurationMilliSeconds(present.appPropagatedGPUVideoDuration); } - else if (present.timeInPresent != 0) { + else if (present.gpuVideoDuration != 0) { out.msVideoBusy = qpc.DurationMilliSeconds(present.gpuVideoDuration); } } @@ -232,6 +232,59 @@ namespace pmon::util::metrics out.msGPUWait = std::max(0.0, ComputeMsGpuDuration(qpc, present, isAppPresent) - ComputeMsGpuBusy(qpc, present, isAppPresent)); } + void ComputeAnimationError( + const QpcConverter& qpc, + const SwapChainCoreState& chain, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + uint64_t screenTime, + FrameMetrics& out) + { + if (!isDisplayed || !isAppFrame) { + out.msAnimationError = std::nullopt; + return; + } + + uint64_t currentSimStart = CalculateSimStartTime(chain, present, chain.animationErrorSource); + + if (currentSimStart == 0 || + chain.lastDisplayedSimStartTime == 0 || + currentSimStart <= chain.lastDisplayedSimStartTime || + chain.lastDisplayedAppScreenTime == 0) { + out.msAnimationError = std::nullopt; + return; + } + + double simElapsed = qpc.DeltaUnsignedMilliSeconds(chain.lastDisplayedSimStartTime, currentSimStart); + double displayElapsed = qpc.DeltaUnsignedMilliSeconds(chain.lastDisplayedAppScreenTime, screenTime); + + out.msAnimationError = simElapsed - displayElapsed; + } + + void ComputeAnimationTime( + const QpcConverter& qpc, + const SwapChainCoreState& chain, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + FrameMetrics& out) + { + if (!isDisplayed || !isAppFrame || chain.firstAppSimStartTime == 0) { + out.msAnimationTime = std::nullopt; + return; + } + + uint64_t currentSimStart = CalculateSimStartTime(chain, present, chain.animationErrorSource); + + if (currentSimStart == 0) { + out.msAnimationTime = std::nullopt; + return; + } + + out.msAnimationTime = CalculateAnimationTime(qpc, chain.firstAppSimStartTime, currentSimStart); + } + void AdjustScreenTimeForCollapsedPresentNV( const FrameData& present, FrameData* nextDisplayedPresent, @@ -322,7 +375,7 @@ namespace pmon::util::metrics const uint64_t screenTime = 0; const uint64_t nextScreenTime = 0; const bool isDisplayed = false; - const bool isAppFrame = false; + const bool isAppFrame = true; auto metrics = ComputeFrameMetrics( qpc, @@ -518,6 +571,34 @@ namespace pmon::util::metrics metrics); } + + void CalculateAnimationMetrics( + const QpcConverter& qpc, + const SwapChainCoreState& swapChain, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + uint64_t screenTime, + FrameMetrics& metrics) + { + ComputeAnimationError( + qpc, + swapChain, + present, + isDisplayed, + isAppFrame, + screenTime, + metrics); + + ComputeAnimationTime( + qpc, + swapChain, + present, + isDisplayed, + isAppFrame, + metrics); + } + ComputedMetrics ComputeFrameMetrics( const QpcConverter& qpc, const FrameData& present, @@ -553,6 +634,15 @@ namespace pmon::util::metrics isAppFrame, metrics); + CalculateAnimationMetrics( + qpc, + chain, + present, + isDisplayed, + isAppFrame, + screenTime, + metrics); + metrics.cpuStartQpc = CalculateCPUStart(chain, present); return result; @@ -594,17 +684,9 @@ namespace pmon::util::metrics } else if (source == AnimationErrorSource::AppProvider) { simStartTime = present.getAppSimStartTime(); - if (simStartTime == 0) { - // Fallback to CPU start - simStartTime = CalculateCPUStart(chainState, present); - } } else if (source == AnimationErrorSource::PCLatency) { simStartTime = present.getPclSimStartTime(); - if (simStartTime == 0) { - // Fallback to CPU start - simStartTime = CalculateCPUStart(chainState, present); - } } return simStartTime; } diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp index e8243243..78db837b 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp @@ -27,7 +27,7 @@ namespace pmon::util::metrics { frame.appSleepStartTime = p.AppSleepStartTime; frame.appSleepEndTime = p.AppSleepEndTime; frame.appSimStartTime = p.AppSimStartTime; - frame.appSleepEndTime = p.AppSleepEndTime; + frame.appSimEndTime = p.AppSimEndTime; frame.appRenderSubmitStartTime = p.AppRenderSubmitStartTime; frame.appRenderSubmitEndTime = p.AppRenderSubmitEndTime; frame.appPresentStartTime = p.AppPresentStartTime; diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h index 9dff2553..efd3c65d 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h @@ -42,6 +42,7 @@ namespace pmon::util::metrics { // Instrumented Timestamps uint64_t appSimStartTime = 0; + uint64_t appSimEndTime = 0; uint64_t appSleepStartTime = 0; uint64_t appSleepEndTime = 0; uint64_t appRenderSubmitStartTime = 0; diff --git a/IntelPresentMon/CommonUtilities/mc/SwapChainState.cpp b/IntelPresentMon/CommonUtilities/mc/SwapChainState.cpp index c28faf81..e87e6c74 100644 --- a/IntelPresentMon/CommonUtilities/mc/SwapChainState.cpp +++ b/IntelPresentMon/CommonUtilities/mc/SwapChainState.cpp @@ -40,20 +40,20 @@ namespace pmon::util::metrics { } } else { // AnimationErrorSource::CpuStart - // Check for app or PCL sim start and possibly change source. - if (present.getAppSimStartTime() != 0) { - animationErrorSource = AnimationErrorSource::AppProvider; - lastDisplayedSimStartTime = present.getAppSimStartTime(); + // Check for PCL or App sim start and possibly change source. + if (present.getPclSimStartTime() != 0) { + animationErrorSource = AnimationErrorSource::PCLatency; + lastDisplayedSimStartTime = present.getPclSimStartTime(); if (firstAppSimStartTime == 0) { - firstAppSimStartTime = present.getAppSimStartTime(); + firstAppSimStartTime = present.getPclSimStartTime(); } lastDisplayedAppScreenTime = lastScreenTime; } - else if (present.getPclSimStartTime() != 0) { - animationErrorSource = AnimationErrorSource::PCLatency; - lastDisplayedSimStartTime = present.getPclSimStartTime(); + else if (present.getAppSimStartTime() != 0) { + animationErrorSource = AnimationErrorSource::AppProvider; + lastDisplayedSimStartTime = present.getAppSimStartTime(); if (firstAppSimStartTime == 0) { - firstAppSimStartTime = present.getPclSimStartTime(); + firstAppSimStartTime = present.getAppSimStartTime(); } lastDisplayedAppScreenTime = lastScreenTime; } diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index f4408e8f..8e590e5d 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -4286,4 +4286,1869 @@ namespace MetricsCoreTests } }; + TEST_CLASS(AnimationTime) + { + public: + // ======================================================================== + // A1: AnimationTime_AppProvider_FirstFrame_ZeroWithoutAppSimStartTime + // ======================================================================== + TEST_METHOD(AnimationTime_AppProvider_FirstFrame_ZeroWithoutAppSimStartTime) + { + // Scenario: + // - SwapChainCoreState starts with CpuStart (default) + // - Current frame: displayed, displayIndex == appIndex, but appSimStartTime == 0 + // - No app data means source stays CpuStart + // + // Expected Outcome: + // - msAnimationTime = std::nullopt (no valid sim start time, no history) + // - firstAppSimStartTime remains 0 in state + // - Source remains CpuStart + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + // state.animationErrorSource defaults to CpuStart + + // Verify initial state + Assert::AreEqual(uint64_t(0), state.firstAppSimStartTime); + Assert::AreEqual(uint64_t(0), state.lastDisplayedSimStartTime); + + // Create a displayed app frame WITHOUT appSimStartTime + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 500; + frame.readyTime = 1'500'000; + frame.appSimStartTime = 0; // No app instrumentation yet + frame.pclSimStartTime = 0; + frame.finalState = PresentResult::Presented; + frame.displayed.push_back({ FrameType::Application, 1'000'000 }); + + // Verify frame setup + Assert::AreEqual(uint64_t(0), frame.appSimStartTime); + Assert::AreEqual(size_t(1), frame.displayed.size()); + + // Create nextDisplayed to allow processing + FrameData next{}; + next.presentStartTime = 2'000'000; + next.timeInPresent = 400; + next.readyTime = 2'500'000; + next.finalState = PresentResult::Presented; + next.displayed.push_back({ FrameType::Application, 2'000'000 }); + + // Action: Compute metrics for this frame + auto metricsVector = ComputeMetricsForPresent(qpc, frame, &next, state); + + // Assert: Should have one computed metric + Assert::AreEqual(size_t(1), metricsVector.size()); + + const ComputedMetrics& result = metricsVector[0]; + + // Assert: msAnimationTime should be nullopt + Assert::IsFalse(result.metrics.msAnimationTime.has_value(), + L"msAnimationTime should be nullopt when appSimStartTime is 0 on first frame"); + + // Assert: firstAppSimStartTime in state should remain 0 + Assert::AreEqual(uint64_t(0), state.firstAppSimStartTime, + L"State: firstAppSimStartTime should remain 0 (no valid app sim start time detected)"); + + // Assert: lastDisplayedSimStartTime should remain 0 + Assert::AreEqual(uint64_t(0), state.lastDisplayedSimStartTime, + L"State: lastDisplayedSimStartTime should remain 0 (no valid app sim start time detected)"); + } + + // ======================================================================== + // A2: AnimationTime_AppProvider_TransitionFrame_FirstValidAppSimStart + // ======================================================================== + TEST_METHOD(AnimationTime_AppProvider_TransitionFrame_FirstValidAppSimStart) + { + // Scenario: + // - Start with CpuStart source (default) + // - Frame 1: App data arrives, triggers source switch to AppProvider + // - Expected: msAnimationTime = 0 (first frame with valid app sim start) + // + // Expected Outcome: + // - msAnimationTime = 0 (first frame with app instrumentation) + // - firstAppSimStartTime is set to qpc(100) + // - Source switches to AppProvider after UpdateChain + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + // state.animationErrorSource defaults to CpuStart + + // Create a displayed app frame WITH appSimStartTime for the first time + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 500; + frame.readyTime = 1'500'000; + frame.appSimStartTime = 100; // First valid app sim start + frame.pclSimStartTime = 0; + frame.finalState = PresentResult::Presented; + frame.displayed.push_back({ FrameType::Application, 1'000'000 }); + + // Create nextDisplayed + FrameData next{}; + next.presentStartTime = 2'000'000; + next.timeInPresent = 400; + next.readyTime = 2'500'000; + next.finalState = PresentResult::Presented; + next.displayed.push_back({ FrameType::Application, 2'000'000 }); + + // Action: Compute metrics + auto metricsVector = ComputeMetricsForPresent(qpc, frame, &next, state); + + // Assert + Assert::AreEqual(size_t(1), metricsVector.size()); + const ComputedMetrics& result = metricsVector[0]; + + // Assert: msAnimationTime should be 0 (first transition frame) + Assert::IsTrue(result.metrics.msAnimationTime.has_value(), + L"msAnimationTime should have a value on first valid app sim start"); + Assert::AreEqual(0.0, result.metrics.msAnimationTime.value(), 0.0001, + L"msAnimationTime should be 0 on first transition frame"); + + // Assert: State should be updated + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, + L"State: firstAppSimStartTime should be set to first valid app sim start"); + Assert::AreEqual(uint64_t(100), state.lastDisplayedSimStartTime, + L"State: lastDisplayedSimStartTime should be set to current frame's app sim start"); + } + + // ======================================================================== + // A3: AnimationTime_AppProvider_SecondFrame_IncrementsCorrectly + // ======================================================================== + TEST_METHOD(AnimationTime_AppProvider_SecondFrame_IncrementsCorrectly) + { + // Scenario: + // - Frame 1: First app data, establishes firstAppSimStartTime = 100, switches to AppProvider + // - Frame 2: appSimStartTime = 150 (50 QPC ticks later) + // - QPC frequency = 10 MHz → 50 ticks = 5 µs = 0.005 ms + // + // Expected Outcome: + // - msAnimationTime ≈ 0.005 ms (elapsed sim time from first to current) + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + // Start with CpuStart, will switch to AppProvider after frame1 + + // Frame 1: First app data + FrameData frame1{}; + frame1.presentStartTime = 500'000; + frame1.timeInPresent = 300; + frame1.appSimStartTime = 100; + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 900'000 }); + + FrameData next1{}; + next1.presentStartTime = 1'500'000; + next1.finalState = PresentResult::Presented; + next1.displayed.push_back({ FrameType::Application, 1'500'000 }); + + ComputeMetricsForPresent(qpc, frame1, &next1, state); + // After this, source is AppProvider, firstAppSimStartTime = 100 + + // Frame 2: Second app frame with incremented sim start + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 500; + frame.readyTime = 1'500'000; + frame.appSimStartTime = 150; // 50 ticks later + frame.pclSimStartTime = 0; + frame.finalState = PresentResult::Presented; + frame.displayed.push_back({ FrameType::Application, 1'000'000 }); + + // Create nextDisplayed + FrameData next{}; + next.presentStartTime = 2'000'000; + next.timeInPresent = 400; + next.readyTime = 2'500'000; + next.finalState = PresentResult::Presented; + next.displayed.push_back({ FrameType::Application, 2'000'000 }); + + // Action: Compute metrics + auto metricsVector = ComputeMetricsForPresent(qpc, frame, &next, state); + + Assert::AreEqual(size_t(1), metricsVector.size()); + const ComputedMetrics& result = metricsVector[0]; + + // Assert: msAnimationTime should be 0.005 ms + Assert::IsTrue(result.metrics.msAnimationTime.has_value()); + double expectedMs = qpc.DeltaUnsignedMilliSeconds(100, 150); + Assert::AreEqual(expectedMs, result.metrics.msAnimationTime.value(), 0.0001, + L"msAnimationTime should reflect elapsed time from first app sim start"); + + // Assert: firstAppSimStartTime unchanged + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, + L"firstAppSimStartTime should remain at first value"); + + // Assert: lastDisplayedSimStartTime updated + Assert::AreEqual(uint64_t(150), state.lastDisplayedSimStartTime, + L"lastDisplayedSimStartTime should be updated to current frame's app sim start"); + } + // ======================================================================== + // A4: AnimationTime_AppProvider_ThreeFrames_CumulativeElapsedTime + // ======================================================================== + TEST_METHOD(AnimationTime_AppProvider_ThreeFrames_CumulativeElapsedTime) + { + // Scenario: + // Start with CpuStart, source switches when first app data arrives + // Frame 1: appSimStartTime = qpc(100) → msAnimationTime = 0, sets firstAppSimStartTime = 100 + // Frame 2: appSimStartTime = qpc(150) → msAnimationTime = 0.005 ms + // Frame 3: appSimStartTime = qpc(250) → expected msAnimationTime = 0.015 ms + // QPC frequency = 10 MHz + // All frames are app-displayed + // + // Expected Outcome: + // Each frame's msAnimationTime is computed relative to the SAME firstAppSimStartTime (100) + // Frame 3's msAnimationTime should be (250 - 100) / 10e6 = 0.015 ms + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + // Start with CpuStart, will switch to AppProvider after frame1 + + // Frame 1: first valid app sim start + FrameData frame1{}; + frame1.presentStartTime = 1'000'000; + frame1.timeInPresent = 500; + frame1.readyTime = 1'500'000; + frame1.appSimStartTime = 100; + frame1.pclSimStartTime = 0; + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 1'000'000 }); + + FrameData next1{}; + next1.presentStartTime = 2'000'000; + next1.timeInPresent = 400; + next1.readyTime = 2'500'000; + next1.finalState = PresentResult::Presented; + next1.displayed.push_back({ FrameType::Application, 2'000'000 }); + + auto metrics1 = ComputeMetricsForPresent(qpc, frame1, &next1, state); + Assert::AreEqual(size_t(1), metrics1.size()); + Assert::IsTrue(metrics1[0].metrics.msAnimationTime.has_value()); + Assert::AreEqual(0.0, metrics1[0].metrics.msAnimationTime.value(), 0.0001); + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime); + + // Frame 2: incremented sim start + FrameData frame2{}; + frame2.presentStartTime = 3'000'000; + frame2.timeInPresent = 500; + frame2.readyTime = 3'500'000; + frame2.appSimStartTime = 150; + frame2.pclSimStartTime = 0; + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 3'000'000 }); + + FrameData next2{}; + next2.presentStartTime = 4'000'000; + next2.timeInPresent = 400; + next2.readyTime = 4'500'000; + next2.finalState = PresentResult::Presented; + next2.displayed.push_back({ FrameType::Application, 4'000'000 }); + + auto metrics2 = ComputeMetricsForPresent(qpc, frame2, &next2, state); + Assert::AreEqual(size_t(1), metrics2.size()); + Assert::IsTrue(metrics2[0].metrics.msAnimationTime.has_value()); + double expected2 = qpc.DeltaUnsignedMilliSeconds(100, 150); + Assert::AreEqual(expected2, metrics2[0].metrics.msAnimationTime.value(), 0.0001); + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, L"Should not change"); + + // Frame 3: further incremented sim start + FrameData frame3{}; + frame3.presentStartTime = 5'000'000; + frame3.timeInPresent = 500; + frame3.readyTime = 5'500'000; + frame3.appSimStartTime = 250; + frame3.pclSimStartTime = 0; + frame3.finalState = PresentResult::Presented; + frame3.displayed.push_back({ FrameType::Application, 5'000'000 }); + + FrameData next3{}; + next3.presentStartTime = 6'000'000; + next3.timeInPresent = 400; + next3.readyTime = 6'500'000; + next3.finalState = PresentResult::Presented; + next3.displayed.push_back({ FrameType::Application, 6'000'000 }); + + auto metrics3 = ComputeMetricsForPresent(qpc, frame3, &next3, state); + Assert::AreEqual(size_t(1), metrics3.size()); + Assert::IsTrue(metrics3[0].metrics.msAnimationTime.has_value()); + double expected3 = qpc.DeltaUnsignedMilliSeconds(100, 250); + Assert::AreEqual(expected3, metrics3[0].metrics.msAnimationTime.value(), 0.0001, + L"Frame 3's msAnimationTime should be relative to original firstAppSimStartTime"); + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, L"Should remain at first value"); + } + + // ======================================================================== + // A5: AnimationTime_AppProvider_SkippedFrame_StaysConsistent + // ======================================================================== + TEST_METHOD(AnimationTime_AppProvider_SkippedFrame_StaysConsistent) + { + // Scenario: + // Start with CpuStart + // Baseline: Establish CPU frames before app data arrives + // Frame 1 (displayed): appSimStartTime = qpc(100) → msAnimationTime = 0, switches to AppProvider + // Frame 2 (not displayed): skipped from animation + // Frame 3 (displayed): appSimStartTime = qpc(200) → msAnimationTime = 0.01 ms + // + // Expected Outcome: + // Animation time stays consistent even when frames are skipped + // Frame 3's msAnimationTime = (200 - 100) / 10e6 = 0.01 ms + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + // state.animationErrorSource defaults to CpuStart + + // Frame 1: CPU-only frame (Presented) + FrameData frame1{}; + frame1.presentStartTime = 100'000; + frame1.timeInPresent = 50'000; + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 200'000 }); + + // ReportMetrics: frame1 arrives (Presented) + // - First frame: just UpdateChain and return + // - Pending: empty (no processing happens) + state.UpdateAfterPresent(frame1); + + // Frame 2: CPU-only frame (Presented) + FrameData frame2{}; + frame2.presentStartTime = 500'000; + frame2.timeInPresent = 50'000; + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 600'000 }); + + // ReportMetrics: frame2 arrives (Presented) + // - Pending is empty, so: + // - Process nothing from pending + // - Process frame2 with nullptr (postpone last display) + // - Add frame2 to pending + // Pending: [frame2] + ComputeMetricsForPresent(qpc, frame2, nullptr, state); + + // Frame 3: first valid app sim start (Presented) + FrameData frame3{}; + frame3.presentStartTime = 1'000'000; + frame3.timeInPresent = 500; + frame3.readyTime = 1'500'000; + frame3.appSimStartTime = 100; + frame3.pclSimStartTime = 0; + frame3.finalState = PresentResult::Presented; + frame3.displayed.push_back({ FrameType::Application, 1'000'000 }); + + // ReportMetrics: frame3 arrives (Presented) + // - Process pending [frame2] with frame3 as next + // - Process frame3 with nullptr (postpone) + // - Clear pending, add frame3 + // Pending: [frame3] + ComputeMetricsForPresent(qpc, frame2, &frame3, state); + + // Frame 4: discarded frame (Discarded) + FrameData frame4{}; + frame4.presentStartTime = 2'500'000; + frame4.timeInPresent = 400; + frame4.readyTime = 2'900'000; + frame4.appSimStartTime = 150; + frame4.pclSimStartTime = 0; + frame4.finalState = PresentResult::Discarded; + + // ReportMetrics: frame4 arrives (Discarded) + // - Pending is not empty + // - Add frame4 to pending (no processing) + // Pending: [frame3, frame4] + + // Frame 5: displayed again (Presented) + FrameData frame5{}; + frame5.presentStartTime = 3'000'000; + frame5.timeInPresent = 500; + frame5.readyTime = 3'500'000; + frame5.appSimStartTime = 200; + frame5.pclSimStartTime = 0; + frame5.finalState = PresentResult::Presented; + frame5.displayed.push_back({ FrameType::Application, 3'000'000 }); + + // ReportMetrics: frame5 arrives (Presented) + // - Process pending [frame3, frame4] with frame5 as next + ComputeMetricsForPresent(qpc, frame3, &frame5, state); + + auto metrics3 = ComputeMetricsForPresent(qpc, frame3, &frame5, state); + Assert::AreEqual(size_t(1), metrics3.size()); + Assert::IsTrue(metrics3[0].metrics.msAnimationTime.has_value()); + Assert::AreEqual(0.0, metrics3[0].metrics.msAnimationTime.value(), 0.0001); + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime); + Assert::AreEqual(uint64_t(100), state.lastDisplayedSimStartTime); + Assert::IsTrue(state.animationErrorSource == AnimationErrorSource::AppProvider, + L"Source should switch to AppProvider after first app data"); + + auto metrics4 = ComputeMetricsForPresent(qpc, frame4, &frame5, state); + Assert::AreEqual(size_t(1), metrics4.size()); + Assert::IsFalse(metrics4[0].metrics.msAnimationTime.has_value(), + L"Discarded frame should have no animation time"); + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime); + Assert::AreEqual(uint64_t(100), state.lastDisplayedSimStartTime); + + // - Process frame5 with nullptr (postpone) + // - Clear pending, add frame5 + // Pending: [frame5] + ComputeMetricsForPresent(qpc, frame5, nullptr, state); + + // Frame 6: next presented frame + FrameData frame6{}; + frame6.presentStartTime = 4'000'000; + frame6.timeInPresent = 400; + frame6.readyTime = 4'500'000; + frame6.finalState = PresentResult::Presented; + frame6.displayed.push_back({ FrameType::Application, 4'000'000 }); + + // ReportMetrics: frame6 arrives (Presented) + // - Process pending [frame5] with frame6 as next + auto metrics5 = ComputeMetricsForPresent(qpc, frame5, &frame6, state); + Assert::AreEqual(size_t(1), metrics5.size()); + Assert::IsTrue(metrics5[0].metrics.msAnimationTime.has_value()); + double expected5 = qpc.DeltaUnsignedMilliSeconds(100, 200); + Assert::AreEqual(expected5, metrics5[0].metrics.msAnimationTime.value(), 0.0001); + + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime); + Assert::AreEqual(uint64_t(200), state.lastDisplayedSimStartTime); + } + // ======================================================================== + // B1: AnimationTime_PCLatency_FirstFrame_ZeroWithoutPclSimStartTime + // ======================================================================== + TEST_METHOD(AnimationTime_PCLatency_FirstFrame_ZeroWithoutPclSimStartTime) + { + // Scenario: + // - SwapChainCoreState starts with CpuStart (default) + // - Current frame: displayed, displayIndex == appIndex, but pclSimStartTime == 0 + // - No PCL data means source stays CpuStart + // + // Expected Outcome: + // - msAnimationTime = std::nullopt (no valid sim start time, no history) + // - firstAppSimStartTime remains 0 in state + // - Source remains CpuStart + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + // state.animationErrorSource defaults to CpuStart + + // Verify initial state + Assert::AreEqual(uint64_t(0), state.firstAppSimStartTime); + Assert::AreEqual(uint64_t(0), state.lastDisplayedSimStartTime); + + // Create a displayed app frame WITHOUT pclSimStartTime + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 500; + frame.readyTime = 1'500'000; + frame.appSimStartTime = 0; + frame.pclSimStartTime = 0; // No PC latency instrumentation yet + frame.finalState = PresentResult::Presented; + frame.displayed.push_back({ FrameType::Application, 1'000'000 }); + + // Verify frame setup + Assert::AreEqual(uint64_t(0), frame.pclSimStartTime); + Assert::AreEqual(size_t(1), frame.displayed.size()); + + // Create nextDisplayed to allow processing + FrameData next{}; + next.presentStartTime = 2'000'000; + next.timeInPresent = 400; + next.readyTime = 2'500'000; + next.finalState = PresentResult::Presented; + next.displayed.push_back({ FrameType::Application, 2'000'000 }); + + // Action: Compute metrics for this frame + auto metricsVector = ComputeMetricsForPresent(qpc, frame, &next, state); + + // Assert: Should have one computed metric + Assert::AreEqual(size_t(1), metricsVector.size()); + + const ComputedMetrics& result = metricsVector[0]; + + // Assert: msAnimationTime should be nullopt + Assert::IsFalse(result.metrics.msAnimationTime.has_value(), + L"msAnimationTime should be nullopt when pclSimStartTime is 0 on first frame"); + + // Assert: firstAppSimStartTime in state should remain 0 + Assert::AreEqual(uint64_t(0), state.firstAppSimStartTime, + L"State: firstAppSimStartTime should remain 0 (no valid pcl sim start time detected)"); + + // Assert: lastDisplayedSimStartTime should remain 0 + Assert::AreEqual(uint64_t(0), state.lastDisplayedSimStartTime, + L"State: lastDisplayedSimStartTime should remain 0 (no valid pcl sim start time detected)"); + } + + // ======================================================================== + // B2: AnimationTime_PCLatency_TransitionFrame_FirstValidPclSimStart + // ======================================================================== + TEST_METHOD(AnimationTime_PCLatency_TransitionFrame_FirstValidPclSimStart) + { + // Scenario: + // - Start with CpuStart source (default) + // - Frame 1: PCL data arrives, triggers source switch to PCLatency + // - Expected: msAnimationTime = 0 (first frame with valid pcl sim start) + // + // Expected Outcome: + // - msAnimationTime = 0 (first frame with PCL instrumentation) + // - firstAppSimStartTime is set to qpc(100) + // - Source switches to PCLatency after UpdateChain + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + // state.animationErrorSource defaults to CpuStart + + // Create a displayed app frame WITH pclSimStartTime for the first time + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 500; + frame.readyTime = 1'500'000; + frame.appSimStartTime = 0; + frame.pclSimStartTime = 100; // First valid pcl sim start + frame.finalState = PresentResult::Presented; + frame.displayed.push_back({ FrameType::Application, 1'000'000 }); + + // Create nextDisplayed + FrameData next{}; + next.presentStartTime = 2'000'000; + next.timeInPresent = 400; + next.readyTime = 2'500'000; + next.finalState = PresentResult::Presented; + next.displayed.push_back({ FrameType::Application, 2'000'000 }); + + // Action: Compute metrics + auto metricsVector = ComputeMetricsForPresent(qpc, frame, &next, state); + + // Assert + Assert::AreEqual(size_t(1), metricsVector.size()); + const ComputedMetrics& result = metricsVector[0]; + + // Assert: msAnimationTime should be 0 (first transition frame) + Assert::IsTrue(result.metrics.msAnimationTime.has_value(), + L"msAnimationTime should have a value on first valid pcl sim start"); + Assert::AreEqual(0.0, result.metrics.msAnimationTime.value(), 0.0001, + L"msAnimationTime should be 0 on first transition frame"); + + // Assert: State should be updated + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, + L"State: firstAppSimStartTime should be set to first valid pcl sim start"); + Assert::AreEqual(uint64_t(100), state.lastDisplayedSimStartTime, + L"State: lastDisplayedSimStartTime should be set to current frame's pcl sim start"); + } + + // ======================================================================== + // B3: AnimationTime_PCLatency_SecondFrame_IncrementsCorrectly + // ======================================================================== + TEST_METHOD(AnimationTime_PCLatency_SecondFrame_IncrementsCorrectly) + { + // Scenario: + // - Frame 2: pclSimStartTime = 200 (100 QPC ticks later) + // - QPC frequency = 10 MHz → 100 ticks = 10 µs = 0.01 ms + // + // Expected Outcome: + // - msAnimationTime ≈ 0.01 ms (elapsed sim time from first to current) + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + // Start with CpuStart, will switch to PCLatency after frame1 + + // Frame 1: First PCL data + FrameData frame1{}; + frame1.presentStartTime = 500'000; + frame1.timeInPresent = 300; + frame1.pclSimStartTime = 100; + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 900'000 }); + + FrameData next1{}; + next1.presentStartTime = 1'500'000; + next1.finalState = PresentResult::Presented; + next1.displayed.push_back({ FrameType::Application, 1'500'000 }); + + ComputeMetricsForPresent(qpc, frame1, &next1, state); + // After this, source is PCLatency, firstAppSimStartTime = 100 + + // Frame 2: Second PCL frame with incremented sim start + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 500; + frame.readyTime = 1'500'000; + frame.appSimStartTime = 0; + frame.pclSimStartTime = 200; // 100 ticks later + frame.finalState = PresentResult::Presented; + frame.displayed.push_back({ FrameType::Application, 1'000'000 }); + + // Create nextDisplayed + FrameData next{}; + next.presentStartTime = 2'000'000; + next.timeInPresent = 400; + next.readyTime = 2'500'000; + next.finalState = PresentResult::Presented; + next.displayed.push_back({ FrameType::Application, 2'000'000 }); + + // Action: Compute metrics + auto metricsVector = ComputeMetricsForPresent(qpc, frame, &next, state); + + Assert::AreEqual(size_t(1), metricsVector.size()); + const ComputedMetrics& result = metricsVector[0]; + + // Assert: msAnimationTime should be 0.01 ms + Assert::IsTrue(result.metrics.msAnimationTime.has_value()); + double expectedMs = qpc.DeltaUnsignedMilliSeconds(100, 200); + Assert::AreEqual(expectedMs, result.metrics.msAnimationTime.value(), 0.0001, + L"msAnimationTime should reflect elapsed time from first pcl sim start"); + + // Assert: firstAppSimStartTime unchanged + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, + L"firstAppSimStartTime should remain at first value"); + + // Assert: lastDisplayedSimStartTime updated + Assert::AreEqual(uint64_t(200), state.lastDisplayedSimStartTime, + L"lastDisplayedSimStartTime should be updated to current frame's pcl sim start"); + } + + // ======================================================================== + // B4: AnimationTime_PCLatency_ThreeFrames_CumulativeElapsedTime + // ======================================================================== + TEST_METHOD(AnimationTime_PCLatency_ThreeFrames_CumulativeElapsedTime) + { + // Scenario: + // Start with CpuStart source + // Frame 1: pclSimStartTime = qpc(100) → msAnimationTime = 0, switches to PCLatency + // Frame 2: pclSimStartTime = qpc(250) → msAnimationTime = 0.015 ms + // Frame 3: pclSimStartTime = qpc(400) → expected msAnimationTime = 0.03 ms + // QPC frequency = 10 MHz + // All frames are app-displayed + // + // Expected Outcome: + // Each frame's msAnimationTime is computed relative to the SAME firstAppSimStartTime (100) + // Frame 3's msAnimationTime should be (400 - 100) / 10e6 = 0.03 ms + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + // state.animationErrorSource defaults to CpuStart + + // Frame 1: first valid pcl sim start + FrameData frame1{}; + frame1.presentStartTime = 1'000'000; + frame1.timeInPresent = 500; + frame1.readyTime = 1'500'000; + frame1.appSimStartTime = 0; + frame1.pclSimStartTime = 100; + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 1'000'000 }); + + FrameData next1{}; + next1.presentStartTime = 2'000'000; + next1.timeInPresent = 400; + next1.readyTime = 2'500'000; + next1.finalState = PresentResult::Presented; + next1.displayed.push_back({ FrameType::Application, 2'000'000 }); + + auto metrics1 = ComputeMetricsForPresent(qpc, frame1, &next1, state); + Assert::AreEqual(size_t(1), metrics1.size()); + Assert::IsTrue(metrics1[0].metrics.msAnimationTime.has_value()); + Assert::AreEqual(0.0, metrics1[0].metrics.msAnimationTime.value(), 0.0001); + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime); + + // Frame 2: incremented pcl sim start + FrameData frame2{}; + frame2.presentStartTime = 3'000'000; + frame2.timeInPresent = 500; + frame2.readyTime = 3'500'000; + frame2.appSimStartTime = 0; + frame2.pclSimStartTime = 250; + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 3'000'000 }); + + FrameData next2{}; + next2.presentStartTime = 4'000'000; + next2.timeInPresent = 400; + next2.readyTime = 4'500'000; + next2.finalState = PresentResult::Presented; + next2.displayed.push_back({ FrameType::Application, 4'000'000 }); + + auto metrics2 = ComputeMetricsForPresent(qpc, frame2, &next2, state); + Assert::AreEqual(size_t(1), metrics2.size()); + Assert::IsTrue(metrics2[0].metrics.msAnimationTime.has_value()); + double expected2 = qpc.DeltaUnsignedMilliSeconds(100, 250); + Assert::AreEqual(expected2, metrics2[0].metrics.msAnimationTime.value(), 0.0001); + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, L"Should not change"); + + // Frame 3: further incremented pcl sim start + FrameData frame3{}; + frame3.presentStartTime = 5'000'000; + frame3.timeInPresent = 500; + frame3.readyTime = 5'500'000; + frame3.appSimStartTime = 0; + frame3.pclSimStartTime = 400; + frame3.finalState = PresentResult::Presented; + frame3.displayed.push_back({ FrameType::Application, 5'000'000 }); + + FrameData next3{}; + next3.presentStartTime = 6'000'000; + next3.timeInPresent = 400; + next3.readyTime = 6'500'000; + next3.finalState = PresentResult::Presented; + next3.displayed.push_back({ FrameType::Application, 6'000'000 }); + + auto metrics3 = ComputeMetricsForPresent(qpc, frame3, &next3, state); + Assert::AreEqual(size_t(1), metrics3.size()); + Assert::IsTrue(metrics3[0].metrics.msAnimationTime.has_value()); + double expected3 = qpc.DeltaUnsignedMilliSeconds(100, 400); + Assert::AreEqual(expected3, metrics3[0].metrics.msAnimationTime.value(), 0.0001, + L"Frame 3's msAnimationTime should be relative to original firstAppSimStartTime"); + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, L"Should remain at first value"); + } + + // ======================================================================== + // B5: AnimationTime_PCLatency_SkippedFrame_StaysConsistent + // ======================================================================== + TEST_METHOD(AnimationTime_PCLatency_SkippedFrame_StaysConsistent) + { + // Scenario: + // Frame 1 (displayed): pclSimStartTime = qpc(100) → msAnimationTime = 0, sets firstAppSimStartTime = 100 + // Frame 2 (not displayed): skipped from animation + // Frame 3 (displayed): pclSimStartTime = qpc(300) → msAnimationTime should still be relative to frame 1's sim start + // + // Expected Outcome: + // Animation time stays consistent even when frames in between are skipped + // Frame 3's msAnimationTime = (300 - 100) / 10e6 = 0.02 ms + // firstAppSimStartTime unchanged + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + // state.animationErrorSource defaults to CpuStart + + // Frame 1: first valid pcl sim start (displayed) + FrameData frame1{}; + frame1.presentStartTime = 1'000'000; + frame1.timeInPresent = 500; + frame1.readyTime = 1'500'000; + frame1.appSimStartTime = 0; + frame1.pclSimStartTime = 100; + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 1'000'000 }); + + FrameData next1{}; + next1.presentStartTime = 2'000'000; + next1.timeInPresent = 400; + next1.readyTime = 2'500'000; + next1.finalState = PresentResult::Presented; + next1.displayed.push_back({ FrameType::Application, 2'000'000 }); + + auto metrics1 = ComputeMetricsForPresent(qpc, frame1, &next1, state); + Assert::AreEqual(size_t(1), metrics1.size()); + Assert::IsTrue(metrics1[0].metrics.msAnimationTime.has_value()); + Assert::AreEqual(0.0, metrics1[0].metrics.msAnimationTime.value(), 0.0001); + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime); + Assert::AreEqual(uint64_t(100), state.lastDisplayedSimStartTime); + + // Frame 2: not displayed (dropped frame) + FrameData frame2{}; + frame2.presentStartTime = 2'500'000; + frame2.timeInPresent = 400; + frame2.readyTime = 2'900'000; + frame2.appSimStartTime = 0; + frame2.pclSimStartTime = 200; // Has pcl sim start but not displayed + frame2.finalState = PresentResult::Discarded; // Not presented, so not displayed + + auto metrics2 = ComputeMetricsForPresent(qpc, frame2, nullptr, state); + Assert::AreEqual(size_t(1), metrics2.size()); + // Not displayed, so animation metrics should be nullopt + Assert::IsFalse(metrics2[0].metrics.msAnimationTime.has_value()); + // State should not update animation sim start for non-displayed frames + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, + L"Should remain unchanged after skipped frame"); + Assert::AreEqual(uint64_t(100), state.lastDisplayedSimStartTime, + L"Should remain unchanged after skipped frame"); + + // Frame 3: displayed again (after skipped frame) + FrameData frame3{}; + frame3.presentStartTime = 3'000'000; + frame3.timeInPresent = 500; + frame3.readyTime = 3'500'000; + frame3.appSimStartTime = 0; + frame3.pclSimStartTime = 300; + frame3.finalState = PresentResult::Presented; + frame3.displayed.push_back({ FrameType::Application, 3'000'000 }); + + FrameData next3{}; + next3.presentStartTime = 4'000'000; + next3.timeInPresent = 400; + next3.readyTime = 4'500'000; + next3.finalState = PresentResult::Presented; + next3.displayed.push_back({ FrameType::Application, 4'000'000 }); + + auto metrics3 = ComputeMetricsForPresent(qpc, frame3, &next3, state); + Assert::AreEqual(size_t(1), metrics3.size()); + Assert::IsTrue(metrics3[0].metrics.msAnimationTime.has_value()); + // Frame 3 should compute relative to original Frame 1's pcl sim start + double expected3 = qpc.DeltaUnsignedMilliSeconds(100, 300); + Assert::AreEqual(expected3, metrics3[0].metrics.msAnimationTime.value(), 0.0001, + L"Frame 3's msAnimationTime should be relative to Frame 1's pcl sim start, skipping Frame 2"); + + // State should be updated to Frame 3's pcl sim start + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, + L"firstAppSimStartTime should remain at Frame 1's value"); + Assert::AreEqual(uint64_t(300), state.lastDisplayedSimStartTime, + L"lastDisplayedSimStartTime should update to Frame 3's pcl sim start"); + } + + // ======================================================================== + // B6: AnimationTime_PCLatency_FallsBackToCpuStart_WhenPclSimStartTimeZero + // ======================================================================== + TEST_METHOD(AnimationTime_PCLatency_FallsBackToCpuStart_WhenPclSimStartTimeZero) + { + // Scenario: + // - Start with CpuStart source + // - Frame 1: PCL data establishes PCLatency source + // - Frame 2: pclSimStartTime = 0 (PCL data disappears) + // - Source should stay PCLatency (no fallback), msAnimationTime = nullopt + // + // Expected Outcome: + // - msAnimationTime = nullopt (PCL data missing) + // - animationErrorSource remains PCLatency (no fallback) + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + // state.animationErrorSource defaults to CpuStart + + // Frame 1: First PCL data + FrameData frame1{}; + frame1.presentStartTime = 500'000; + frame1.timeInPresent = 300; + frame1.pclSimStartTime = 100; + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 900'000 }); + + FrameData next1{}; + next1.presentStartTime = 1'500'000; + next1.finalState = PresentResult::Presented; + next1.displayed.push_back({ FrameType::Application, 1'500'000 }); + + auto metrics1 = ComputeMetricsForPresent(qpc, frame1, &next1, state); + Assert::AreEqual(size_t(1), metrics1.size()); + Assert::IsTrue(metrics1[0].metrics.msAnimationTime.has_value()); + Assert::AreEqual(0.0, metrics1[0].metrics.msAnimationTime.value(), 0.0001); + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime); + Assert::IsTrue(state.animationErrorSource == AnimationErrorSource::PCLatency); + + // Frame 2: PCL data missing (pclSimStartTime = 0) + FrameData frame{}; + frame.presentStartTime = 1'200'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'300'000; + frame.appSimStartTime = 0; + frame.pclSimStartTime = 0; // PCL data disappeared + frame.finalState = PresentResult::Presented; + frame.displayed.push_back({ FrameType::Application, 1'400'000 }); + + // Create nextDisplayed + FrameData next{}; + next.presentStartTime = 1'600'000; + next.timeInPresent = 50'000; + next.readyTime = 1'700'000; + next.finalState = PresentResult::Presented; + next.displayed.push_back({ FrameType::Application, 1'800'000 }); + + // Action: Compute metrics + auto metricsVector = ComputeMetricsForPresent(qpc, frame, &next, state); + + // Assert: Should have one computed metric + Assert::AreEqual(size_t(1), metricsVector.size()); + + const ComputedMetrics& result = metricsVector[0]; + + // Assert: msAnimationTime should be nullopt (no PCL data, no fallback) + Assert::IsFalse(result.metrics.msAnimationTime.has_value(), + L"msAnimationTime should be nullopt when pclSimStartTime is 0"); + + // Assert: State should remain PCLatency + Assert::IsTrue(state.animationErrorSource == AnimationErrorSource::PCLatency, + L"animationErrorSource should remain PCLatency (no fallback)"); + + // Assert: firstAppSimStartTime unchanged + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, + L"firstAppSimStartTime should remain unchanged"); + } + // ======================================================================== + // D1: AnimationTime_CpuStart_FirstFrame_ZeroWithoutHistory + // ======================================================================== + TEST_METHOD(AnimationTime_CpuStart_FirstFrame_ZeroWithoutHistory) + { + // Scenario: + // - SwapChainCoreState is in initial state (no prior frames) + // - Current frame: displayed, displayIndex == appIndex + // - animationErrorSource = CpuStart + // - No lastAppPresent or lastPresent in chain + // + // Expected Outcome: + // - msAnimationTime = std::nullopt (cpuStart = 0, cannot initialize firstAppSimStartTime) + // - firstAppSimStartTime remains 0 in state + // - lastDisplayedSimStartTime remains 0 in state + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + // state.animationErrorSource defaults to CpuStart + + // Verify initial state + Assert::AreEqual(uint64_t(0), state.firstAppSimStartTime); + Assert::AreEqual(uint64_t(0), state.lastDisplayedSimStartTime); + + // Create a displayed app frame with CpuStart source + FrameData frame{}; + frame.presentStartTime = 1'000'000; + frame.timeInPresent = 500; + frame.readyTime = 1'500'000; + frame.appSimStartTime = 0; + frame.pclSimStartTime = 0; + frame.finalState = PresentResult::Presented; + frame.displayed.push_back({ FrameType::Application, 1'000'000 }); + + // Verify frame setup + Assert::AreEqual(size_t(1), frame.displayed.size()); + + // Create nextDisplayed to allow processing + FrameData next{}; + next.presentStartTime = 2'000'000; + next.timeInPresent = 400; + next.readyTime = 2'500'000; + next.finalState = PresentResult::Presented; + next.displayed.push_back({ FrameType::Application, 2'000'000 }); + + // Action: Compute metrics for this frame + auto metricsVector = ComputeMetricsForPresent(qpc, frame, &next, state); + + // Assert: Should have one computed metric + Assert::AreEqual(size_t(1), metricsVector.size()); + + const ComputedMetrics& result = metricsVector[0]; + + // Assert: msAnimationTime should be nullopt (cpuStart = 0) + Assert::IsFalse(result.metrics.msAnimationTime.has_value(), + L"msAnimationTime should be nullopt when no prior frame history exists"); + + // Assert: State should not be updated + Assert::AreEqual(uint64_t(0), state.firstAppSimStartTime, + L"State: firstAppSimStartTime should remain 0 (no valid CPU start available)"); + + // Assert: lastDisplayedSimStartTime should remain 0 + Assert::AreEqual(uint64_t(0), state.lastDisplayedSimStartTime, + L"State: lastDisplayedSimStartTime should remain 0"); + } + + // ======================================================================== + // D2: AnimationTime_CpuStart_TransitionFrame_FirstValidCpuStart + // ======================================================================== + TEST_METHOD(AnimationTime_CpuStart_TransitionFrame_FirstValidCpuStart) + { + // Scenario: + // - Prior state: firstAppSimStartTime == 0 (no prior CPU start) + // - Chain has lastAppPresent with valid timing data + // - Current frame: displayed, displayIndex == appIndex + // - cpuStart = lastAppPresent.presentStartTime + lastAppPresent.timeInPresent = 1'000'000 + // - animationErrorSource = CpuStart + // + // Expected Outcome: + // - msAnimationTime = 0 (first frame with valid CPU start) + // - firstAppSimStartTime is set to cpuStart (1'000'000) for subsequent frames + // - lastDisplayedSimStartTime is updated to cpuStart (1'000'000) + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + // state.animationErrorSource defaults to CpuStart + + // Set up prior app present for CPU start calculation + FrameData priorApp{}; + priorApp.presentStartTime = 800'000; + priorApp.timeInPresent = 200'000; + priorApp.readyTime = 1'000'000; + priorApp.setFinalState(PresentResult::Presented); + priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + + state.lastAppPresent = priorApp; + + // Create a displayed app frame with CpuStart source + FrameData frame{}; + frame.presentStartTime = 1'200'000; + frame.timeInPresent = 100'000; + frame.readyTime = 1'300'000; + frame.appSimStartTime = 0; + frame.pclSimStartTime = 0; + frame.finalState = PresentResult::Presented; + frame.displayed.push_back({ FrameType::Application, 1'400'000 }); + + // Create nextDisplayed + FrameData next{}; + next.presentStartTime = 1'600'000; + next.timeInPresent = 50'000; + next.readyTime = 1'700'000; + next.finalState = PresentResult::Presented; + next.displayed.push_back({ FrameType::Application, 1'800'000 }); + + // Action: Compute metrics + auto metricsVector = ComputeMetricsForPresent(qpc, frame, &next, state); + + // Assert + Assert::AreEqual(size_t(1), metricsVector.size()); + const ComputedMetrics& result = metricsVector[0]; + + // Assert: msAnimationTime should be 0 (first transition frame) + Assert::IsTrue(result.metrics.msAnimationTime.has_value(), + L"msAnimationTime should have a value on first valid CPU start"); + Assert::AreEqual(0.0, result.metrics.msAnimationTime.value(), 0.0001, + L"msAnimationTime should be 0 on first transition frame"); + + // Assert: State should be updated with CPU start + // cpuStart = 800'000 + 200'000 = 1'000'000 + uint64_t expectedCpuStart = 800'000 + 200'000; + Assert::AreEqual(expectedCpuStart, state.firstAppSimStartTime, + L"State: firstAppSimStartTime should be set to CPU start value"); + Assert::AreEqual(expectedCpuStart, state.lastDisplayedSimStartTime, + L"State: lastDisplayedSimStartTime should be set to CPU start value"); + } + + // ======================================================================== + // D3: AnimationTime_CpuStart_SecondFrame_IncrementsCorrectly + // ======================================================================== + TEST_METHOD(AnimationTime_CpuStart_SecondFrame_IncrementsCorrectly) + { + // Scenario: + // - Prior state: firstAppSimStartTime = cpuStart1 = 1'000'000, lastDisplayedSimStartTime = 1'000'000 + // - Chain.lastAppPresent points to first frame + // - Second frame: displayed, new lastAppPresent + // - cpuStart2 = 1'100'000 + 100'000 = 1'200'000 (new prior frame's end time) + // - QPC frequency = 10 MHz → delta = 200'000 ticks = 0.02 ms + // - animationErrorSource = CpuStart + // + // Expected Outcome: + // - msAnimationTime ≈ 0.02 ms (elapsed CPU start time from first to current) + // - firstAppSimStartTime unchanged (still 1'000'000) + // - lastDisplayedSimStartTime updated to 1'200'000 + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + // state.animationErrorSource defaults to CpuStart + + // First frame setup + FrameData frame1{}; + frame1.presentStartTime = 800'000; + frame1.timeInPresent = 200'000; + frame1.readyTime = 1'000'000; + frame1.setFinalState(PresentResult::Presented); + frame1.displayed.push_back({ FrameType::Application, 1'100'000 }); + + state.lastAppPresent = frame1; + state.firstAppSimStartTime = 1'000'000; + state.lastDisplayedSimStartTime = 1'000'000; + + // Second frame with new timing + FrameData frame2{}; + frame2.presentStartTime = 1'100'000; + frame2.timeInPresent = 100'000; + frame2.readyTime = 1'200'000; + frame2.appSimStartTime = 0; + frame2.pclSimStartTime = 0; + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 1'300'000 }); + + // Create nextDisplayed + FrameData next{}; + next.presentStartTime = 1'500'000; + next.timeInPresent = 50'000; + next.readyTime = 1'600'000; + next.finalState = PresentResult::Presented; + next.displayed.push_back({ FrameType::Application, 1'700'000 }); + + // Action: Compute metrics + auto metricsVector = ComputeMetricsForPresent(qpc, frame2, &next, state); + + Assert::AreEqual(size_t(1), metricsVector.size()); + const ComputedMetrics& result = metricsVector[0]; + + // Assert: msAnimationTime should be 0.02 ms + // cpuStart2 = 1'100'000 + 100'000 = 1'200'000 + // delta = 1'200'000 - 1'000'000 = 200'000 ticks = 0.02 ms + Assert::IsTrue(result.metrics.msAnimationTime.has_value()); + double expectedMs = qpc.DeltaUnsignedMilliSeconds(1'000'000, 1'200'000); + Assert::AreEqual(expectedMs, result.metrics.msAnimationTime.value(), 0.0001, + L"msAnimationTime should reflect elapsed CPU start time"); + + // Assert: firstAppSimStartTime unchanged + Assert::AreEqual(uint64_t(1'000'000), state.firstAppSimStartTime, + L"firstAppSimStartTime should remain at first value"); + + // Assert: lastDisplayedSimStartTime updated + uint64_t expectedNewCpuStart = 1'100'000 + 100'000; + Assert::AreEqual(expectedNewCpuStart, state.lastDisplayedSimStartTime, + L"lastDisplayedSimStartTime should be updated to new CPU start value"); + } + }; + + // ============================================================================ + // SECTION: Animation Error Tests + // ============================================================================ + + TEST_CLASS(AnimationErrorTests) + { + public: + // Section B: Animation Error – AppProvider Source + + TEST_METHOD(AnimationError_AppProvider_NoLastDisplayedFrame_Nullopt) + { + // Scenario: First app-displayed frame with appSimStartTime + // Expected: msAnimationError = std::nullopt (no prior frame data) + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + state.animationErrorSource = AnimationErrorSource::AppProvider; + + FrameData present{}; + present.presentStartTime = 1000; + present.timeInPresent = 100; + present.appSimStartTime = 150; + present.finalState = PresentResult::Presented; + present.displayed.push_back({ FrameType::Application, 200 }); + + FrameData nextPresent{}; + nextPresent.presentStartTime = 2000; + nextPresent.finalState = PresentResult::Presented; + nextPresent.displayed.push_back({ FrameType::Application, 2100 }); + + auto results = ComputeMetricsForPresent(qpc, present, &nextPresent, state); + + Assert::AreEqual(size_t(1), results.size()); + Assert::IsFalse(results[0].metrics.msAnimationError.has_value(), + L"msAnimationError should be nullopt without prior displayed frame"); + } + + TEST_METHOD(AnimationError_AppProvider_TwoFrames_PositiveError) + { + // Scenario: Two frames with sim elapsed = 50, display elapsed = 50 + // Expected: msAnimationError = 0 ms (cadences match) + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + state.animationErrorSource = AnimationErrorSource::AppProvider; + + // Frame 1 setup + FrameData frame1{}; + frame1.presentStartTime = 1000; + frame1.timeInPresent = 100; + frame1.appSimStartTime = 100; + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 1000 }); + + FrameData frame2{}; + frame2.presentStartTime = 2000; + frame2.timeInPresent = 100; + frame2.appSimStartTime = 150; // sim elapsed = 50 + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 1050 }); // display elapsed = 50 + + // Process frame 1 + FrameData dummyNext{}; + dummyNext.finalState = PresentResult::Presented; + dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + auto results1 = ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); + + // Process frame 2 + FrameData frame3{}; + frame3.finalState = PresentResult::Presented; + frame3.displayed.push_back({ FrameType::Application, 3000 }); + auto results2 = ComputeMetricsForPresent(qpc, frame2, &frame3, state); + + Assert::AreEqual(size_t(1), results2.size()); + Assert::IsTrue(results2[0].metrics.msAnimationError.has_value()); + Assert::AreEqual(0.0, results2[0].metrics.msAnimationError.value(), 0.0001, + L"msAnimationError should be 0 when sim and display cadences match"); + } + + TEST_METHOD(AnimationError_AppProvider_TwoFrames_SimSlowerThanDisplay) + { + // Scenario: sim elapsed = 40 ticks, display elapsed = 50 ticks + // Expected: msAnimationError = -0.001 ms (sim slower) + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + state.animationErrorSource = AnimationErrorSource::AppProvider; + + FrameData frame1{}; + frame1.presentStartTime = 1000; + frame1.timeInPresent = 100; + frame1.appSimStartTime = 100; + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 1000 }); + + FrameData frame2{}; + frame2.presentStartTime = 2000; + frame2.timeInPresent = 100; + frame2.appSimStartTime = 140; // sim elapsed = 40 + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 1050 }); // display elapsed = 50 + + FrameData dummyNext{}; + dummyNext.finalState = PresentResult::Presented; + dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); + + FrameData frame3{}; + frame3.finalState = PresentResult::Presented; + frame3.displayed.push_back({ FrameType::Application, 3000 }); + auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); + + Assert::IsTrue(results[0].metrics.msAnimationError.has_value()); + double simElapsed = qpc.DeltaUnsignedMilliSeconds(100, 140); // 0.004 ms + double displayElapsed = qpc.DeltaUnsignedMilliSeconds(1000, 1050); // 0.005 ms + double expected = simElapsed - displayElapsed; // -0.001 ms + Assert::AreEqual(expected, results[0].metrics.msAnimationError.value(), 0.0001); + } + + TEST_METHOD(AnimationError_AppProvider_TwoFrames_SimFasterThanDisplay) + { + // Scenario: sim elapsed = 60 ticks, display elapsed = 50 ticks + // Expected: msAnimationError = +0.001 ms (sim faster) + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + state.animationErrorSource = AnimationErrorSource::AppProvider; + + FrameData frame1{}; + frame1.presentStartTime = 1000; + frame1.timeInPresent = 100; + frame1.appSimStartTime = 100; + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 1000 }); + + FrameData frame2{}; + frame2.presentStartTime = 2000; + frame2.timeInPresent = 100; + frame2.appSimStartTime = 160; // sim elapsed = 60 + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 1050 }); // display elapsed = 50 + + FrameData dummyNext{}; + dummyNext.finalState = PresentResult::Presented; + dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); + + FrameData frame3{}; + frame3.finalState = PresentResult::Presented; + frame3.displayed.push_back({ FrameType::Application, 3000 }); + auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); + + Assert::IsTrue(results[0].metrics.msAnimationError.has_value()); + double simElapsed = qpc.DeltaUnsignedMilliSeconds(100, 160); // 0.006 ms + double displayElapsed = qpc.DeltaUnsignedMilliSeconds(1000, 1050); // 0.005 ms + double expected = simElapsed - displayElapsed; // +0.001 ms + Assert::AreEqual(expected, results[0].metrics.msAnimationError.value(), 0.0001); + } + + TEST_METHOD(AnimationError_AppProvider_BackwardsSimStartTime_Nullopt) + { + // Scenario: Current sim start goes backward in time + // Expected: msAnimationError = std::nullopt + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + state.animationErrorSource = AnimationErrorSource::AppProvider; + + FrameData frame1{}; + frame1.presentStartTime = 1000; + frame1.timeInPresent = 100; + frame1.appSimStartTime = 150; + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 1050 }); + + FrameData frame2{}; + frame2.presentStartTime = 2000; + frame2.timeInPresent = 100; + frame2.appSimStartTime = 140; // backwards! + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 1100 }); + + FrameData dummyNext{}; + dummyNext.finalState = PresentResult::Presented; + dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); + + FrameData frame3{}; + frame3.finalState = PresentResult::Presented; + frame3.displayed.push_back({ FrameType::Application, 3000 }); + auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); + + Assert::IsFalse(results[0].metrics.msAnimationError.has_value(), + L"msAnimationError should be nullopt when sim start goes backward"); + } + + TEST_METHOD(AnimationError_AppProvider_CurrentSimStartTimeZero_Nullopt) + { + // Scenario: Current frame has no app instrumentation (appSimStartTime = 0) + // Expected: msAnimationError = std::nullopt + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState swapChain{}; + swapChain.animationErrorSource = AnimationErrorSource::AppProvider; + + FrameData frame1{}; + frame1.presentStartTime = 1000; + frame1.timeInPresent = 100; + frame1.appSimStartTime = 100; + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 1000 }); + + FrameData frame2{}; + frame2.presentStartTime = 2000; + frame2.timeInPresent = 100; + frame2.appSimStartTime = 0; // no instrumentation + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 1050 }); + + FrameData dummyNext{}; + dummyNext.finalState = PresentResult::Presented; + dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + ComputeMetricsForPresent(qpc, frame1, &dummyNext, swapChain); + + FrameData frame3{}; + frame3.finalState = PresentResult::Presented; + frame3.displayed.push_back({ FrameType::Application, 3000 }); + auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, swapChain); + + Assert::IsFalse(results[0].metrics.msAnimationError.has_value(), + L"msAnimationError should be nullopt without valid sim start time"); + } + + TEST_METHOD(AnimationError_AppProvider_ZeroDisplayDelta_ErrorIsSimElapsed) + { + // Scenario: Display elapsed = 0 (same screen time) + // Expected: msAnimationError = simElapsed - 0 + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + state.animationErrorSource = AnimationErrorSource::AppProvider; + + FrameData frame1{}; + frame1.presentStartTime = 1000; + frame1.timeInPresent = 100; + frame1.appSimStartTime = 100; + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 1000 }); + + FrameData frame2{}; + frame2.presentStartTime = 2000; + frame2.timeInPresent = 100; + frame2.appSimStartTime = 150; // sim elapsed = 50 + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 1000 }); // same screen time! + + FrameData dummyNext{}; + dummyNext.finalState = PresentResult::Presented; + dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); + + FrameData frame3{}; + frame3.finalState = PresentResult::Presented; + frame3.displayed.push_back({ FrameType::Application, 3000 }); + auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); + + Assert::IsTrue(results[0].metrics.msAnimationError.has_value()); + double simElapsed = qpc.DeltaUnsignedMilliSeconds(100, 150); // 0.005 ms + Assert::AreEqual(simElapsed, results[0].metrics.msAnimationError.value(), 0.0001, + L"msAnimationError should equal simElapsed when display delta is zero"); + } + + // Section C: Animation Error – PCLatency Source + + TEST_METHOD(AnimationError_PCLatency_TwoFrames_ValidPclSimStart) + { + // Scenario: PCL source, sim elapsed = 40, display elapsed = 50 + // Expected: msAnimationError = -0.001 ms + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + state.animationErrorSource = AnimationErrorSource::PCLatency; + + FrameData frame1{}; + frame1.presentStartTime = 1000; + frame1.timeInPresent = 100; + frame1.pclSimStartTime = 100; + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 1000 }); + + FrameData frame2{}; + frame2.presentStartTime = 2000; + frame2.timeInPresent = 100; + frame2.pclSimStartTime = 140; // PCL sim elapsed = 40 + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 1050 }); // display elapsed = 50 + + FrameData dummyNext{}; + dummyNext.finalState = PresentResult::Presented; + dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); + + FrameData frame3{}; + frame3.finalState = PresentResult::Presented; + frame3.displayed.push_back({ FrameType::Application, 3000 }); + auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); + + Assert::IsTrue(results[0].metrics.msAnimationError.has_value()); + double simElapsed = qpc.DeltaUnsignedMilliSeconds(100, 140); // 0.004 ms + double displayElapsed = qpc.DeltaUnsignedMilliSeconds(1000, 1050); // 0.005 ms + double expected = simElapsed - displayElapsed; // -0.001 ms + Assert::AreEqual(expected, results[0].metrics.msAnimationError.value(), 0.0001); + } + + TEST_METHOD(AnimationError_PCLatency_CurrentPclSimStartZero_Nullopt) + { + // Scenario: PCL source, but current frame has pclSimStartTime = 0 + // Expected: msAnimationError = std::nullopt (no fallback to app in PCL mode) + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + state.animationErrorSource = AnimationErrorSource::PCLatency; + + FrameData frame1{}; + frame1.presentStartTime = 1000; + frame1.timeInPresent = 100; + frame1.pclSimStartTime = 100; + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 1000 }); + + FrameData frame2{}; + frame2.presentStartTime = 2000; + frame2.timeInPresent = 100; + frame2.pclSimStartTime = 0; // PCL unavailable + frame2.appSimStartTime = 150; // app available, but should not be used + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 1050 }); + + FrameData dummyNext{}; + dummyNext.finalState = PresentResult::Presented; + dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); + + FrameData frame3{}; + frame3.finalState = PresentResult::Presented; + frame3.displayed.push_back({ FrameType::Application, 3000 }); + auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); + + Assert::IsFalse(results[0].metrics.msAnimationError.has_value(), + L"msAnimationError should be nullopt when PCL source unavailable"); + } + + TEST_METHOD(AnimationError_PCLatency_TransitionFromZero_FirstValidPclSimStart) + { + // Scenario: First frame with valid PCL sim start + // Expected: msAnimationError = std::nullopt (no prior PCL frame) + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + state.animationErrorSource = AnimationErrorSource::PCLatency; + + FrameData present{}; + present.presentStartTime = 1000; + present.timeInPresent = 100; + present.pclSimStartTime = 100; // first valid PCL + present.finalState = PresentResult::Presented; + present.displayed.push_back({ FrameType::Application, 1000 }); + + FrameData nextPresent{}; + nextPresent.finalState = PresentResult::Presented; + nextPresent.displayed.push_back({ FrameType::Application, 2000 }); + + auto results = ComputeMetricsForPresent(qpc, present, &nextPresent, state); + + Assert::IsFalse(results[0].metrics.msAnimationError.has_value(), + L"msAnimationError should be nullopt on first valid PCL frame"); + } + + TEST_METHOD(AnimationError_PCLatency_TransitionFromAppToPcl_SourceSwitches) + { + // Scenario: Switching from CpuStart source to PCL + // Expected: Source auto-switches when PCL data arrives + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + state.animationErrorSource = AnimationErrorSource::CpuStart; + + // Frame 1: Establish baseline + FrameData frame1{}; + frame1.presentStartTime = 800; + frame1.timeInPresent = 100; // CPU end = 900 + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 1800 }); + + // Frame 2: Continue with CPU source + FrameData frame2{}; + frame2.presentStartTime = 1000; + frame2.timeInPresent = 100; // CPU end = 1100 + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 2000 }); + + // Frame 3: PCL becomes available, triggers source switch + FrameData frame3{}; + frame3.presentStartTime = 1200; + frame3.timeInPresent = 100; + frame3.pclSimStartTime = 150; // PCL data present + frame3.appSimStartTime = 150; + frame3.finalState = PresentResult::Presented; + frame3.displayed.push_back({ FrameType::Application, 2100 }); + + // Frame 4: Next frame for completion + FrameData frame4{}; + frame4.presentStartTime = 1400; + frame4.timeInPresent = 100; + frame4.finalState = PresentResult::Presented; + frame4.displayed.push_back({ FrameType::Application, 2200 }); + + ComputeMetricsForPresent(qpc, frame1, &frame2, state); + ComputeMetricsForPresent(qpc, frame2, &frame3, state); + + // Frame 3 processes with CpuStart source, then UpdateChain switches to PCLatency + auto results = ComputeMetricsForPresent(qpc, frame3, &frame4, state); + + // Animation error computed using CPU start (source still CpuStart during calculation) + Assert::IsTrue(results[0].metrics.msAnimationError.has_value(), + L"Animation error should be computed with CPU start before source switch"); + + // After UpdateChain, source should have switched to PCLatency + Assert::IsTrue(state.animationErrorSource == AnimationErrorSource::PCLatency, + L"Source should auto-switch to PCLatency after UpdateChain"); + } + + TEST_METHOD(AnimationError_PCLatency_SourcePriority_PclWinsOverApp) + { + // Scenario: Both PCL and App sim start present + // Expected: Use PCL (source priority) + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + state.animationErrorSource = AnimationErrorSource::PCLatency; + + FrameData frame1{}; + frame1.presentStartTime = 1000; + frame1.timeInPresent = 100; + frame1.pclSimStartTime = 100; + frame1.appSimStartTime = 200; // different value + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 1000 }); + + FrameData frame2{}; + frame2.presentStartTime = 2000; + frame2.timeInPresent = 100; + frame2.pclSimStartTime = 150; // PCL elapsed = 50 + frame2.appSimStartTime = 300; // app elapsed = 100 (should be ignored) + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 1050 }); // display elapsed = 50 + + FrameData dummyNext{}; + dummyNext.finalState = PresentResult::Presented; + dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); + + FrameData frame3{}; + frame3.finalState = PresentResult::Presented; + frame3.displayed.push_back({ FrameType::Application, 3000 }); + auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); + + Assert::IsTrue(results[0].metrics.msAnimationError.has_value()); + // Should use PCL: sim=50, display=50, error=0 + double simElapsed = qpc.DeltaUnsignedMilliSeconds(100, 150); // PCL: 0.005 ms + double displayElapsed = qpc.DeltaUnsignedMilliSeconds(1000, 1050); + double expected = simElapsed - displayElapsed; // 0.0 ms + Assert::AreEqual(expected, results[0].metrics.msAnimationError.value(), 0.0001, + L"Should use PCL source, not app source"); + } + + // Section D: Animation Error – CpuStart Source + + TEST_METHOD(AnimationError_CpuStart_ComputedFromCpuPresent) + { + // Scenario: CpuStart source, CPU-derived sim times + // Expected: Error computed from CPU present end times + // Note: Need baseline frame to establish lastDisplayedSimStartTime + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + state.animationErrorSource = AnimationErrorSource::CpuStart; + + // Baseline frame to establish initial state + FrameData frame1{}; + frame1.presentStartTime = 800; + frame1.timeInPresent = 100; // CPU sim start for next frame = 900 + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 1900 }); + + FrameData frame2{}; + frame2.presentStartTime = 1000; + frame2.timeInPresent = 100; // CPU sim start for next frame = 1100 + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 2000 }); + + FrameData frame3{}; + frame3.presentStartTime = 1200; + frame3.timeInPresent = 100; // CPU sim start = 1300, elapsed from 1100 = 200 + frame3.finalState = PresentResult::Presented; + frame3.displayed.push_back({ FrameType::Application, 2050 }); // display elapsed from 2000 = 50 + + FrameData dummyNext1{}; + dummyNext1.finalState = PresentResult::Presented; + dummyNext1.displayed.push_back({ FrameType::Application, 2500 }); + ComputeMetricsForPresent(qpc, frame1, &dummyNext1, state); + + FrameData dummyNext2{}; + dummyNext2.finalState = PresentResult::Presented; + dummyNext2.displayed.push_back({ FrameType::Application, 3000 }); + ComputeMetricsForPresent(qpc, frame2, &dummyNext2, state); + + FrameData frame4{}; + frame4.finalState = PresentResult::Presented; + frame4.displayed.push_back({ FrameType::Application, 4000 }); + auto results = ComputeMetricsForPresent(qpc, frame3, &frame4, state); + + Assert::IsTrue(results[0].metrics.msAnimationError.has_value()); + double simElapsed = qpc.DeltaUnsignedMilliSeconds(1100, 1300); // 0.020 ms + double displayElapsed = qpc.DeltaUnsignedMilliSeconds(2000, 2050); // 0.005 ms + double expected = simElapsed - displayElapsed; // 0.015 ms + Assert::AreEqual(expected, results[0].metrics.msAnimationError.value(), 0.0001); + } + + TEST_METHOD(AnimationError_CpuStart_TransitionToAppProvider_Nullopt) + { + // Scenario: Source switches from CpuStart to AppProvider mid-stream + // Expected: msAnimationError = std::nullopt (first frame of new source) + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + state.animationErrorSource = AnimationErrorSource::CpuStart; + + FrameData frame1{}; + frame1.presentStartTime = 1000; + frame1.timeInPresent = 100; + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 2000 }); + + FrameData frame2{}; + frame2.presentStartTime = 2000; + frame2.timeInPresent = 100; + frame2.appSimStartTime = 100; // app instrumentation appears + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 2050 }); + + FrameData dummyNext{}; + dummyNext.finalState = PresentResult::Presented; + dummyNext.displayed.push_back({ FrameType::Application, 3000 }); + ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); + + FrameData frame3{}; + frame3.finalState = PresentResult::Presented; + frame3.displayed.push_back({ FrameType::Application, 4000 }); + auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); + + Assert::IsFalse(results[0].metrics.msAnimationError.has_value(), + L"msAnimationError should be nullopt on source transition"); + Assert::IsTrue(state.animationErrorSource == AnimationErrorSource::AppProvider, + L"Source should auto-switch to AppProvider"); + } + + // Section E: Disabled or Edge Cases + + TEST_METHOD(AnimationError_NotAppDisplayed_BothNullopt) + { + // Scenario: Frame not app-displayed (wrong displayIndex) + // Expected: Both animation metrics = std::nullopt + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + state.animationErrorSource = AnimationErrorSource::AppProvider; + state.firstAppSimStartTime = 100; + state.lastDisplayedSimStartTime = 100; + + FrameData present{}; + present.presentStartTime = 1000; + present.timeInPresent = 100; + present.appSimStartTime = 150; + present.finalState = PresentResult::Presented; + present.displayed.push_back({ FrameType::Repeated, 2000 }); // Not Application! + + FrameData nextPresent{}; + nextPresent.finalState = PresentResult::Presented; + nextPresent.displayed.push_back({ FrameType::Application, 3000 }); + + auto results = ComputeMetricsForPresent(qpc, present, &nextPresent, state); + + Assert::AreEqual(size_t(1), results.size()); + Assert::IsFalse(results[0].metrics.msAnimationError.has_value(), + L"msAnimationError should be nullopt for non-app frames"); + Assert::IsFalse(results[0].metrics.msAnimationTime.has_value(), + L"msAnimationTime should be nullopt for non-app frames"); + } + + TEST_METHOD(AnimationError_FirstFrameEver_BothNullopt) + { + // Scenario: Very first frame, no prior state + // Expected: Both animation metrics = std::nullopt + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; // all zeros + state.animationErrorSource = AnimationErrorSource::AppProvider; + + FrameData present{}; + present.presentStartTime = 1000; + present.timeInPresent = 100; + present.appSimStartTime = 0; // no instrumentation + present.pclSimStartTime = 0; + present.finalState = PresentResult::Presented; + present.displayed.push_back({ FrameType::Application, 2000 }); + + FrameData nextPresent{}; + nextPresent.finalState = PresentResult::Presented; + nextPresent.displayed.push_back({ FrameType::Application, 3000 }); + + auto results = ComputeMetricsForPresent(qpc, present, &nextPresent, state); + + Assert::IsFalse(results[0].metrics.msAnimationError.has_value()); + Assert::IsFalse(results[0].metrics.msAnimationTime.has_value()); + } + + TEST_METHOD(AnimationError_BackwardsScreenTime_ErrorStillComputed) + { + // Scenario: Screen time goes backward (unusual but allowed) + // Expected: Error still computed (reflects raw cadence mismatch) + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + state.animationErrorSource = AnimationErrorSource::AppProvider; + + FrameData frame1{}; + frame1.presentStartTime = 1000; + frame1.timeInPresent = 100; + frame1.appSimStartTime = 100; + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 1100 }); + + FrameData frame2{}; + frame2.presentStartTime = 2000; + frame2.timeInPresent = 100; + frame2.appSimStartTime = 150; // sim elapsed = 50 + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 1050 }); // screen time backward! + + FrameData dummyNext{}; + dummyNext.finalState = PresentResult::Presented; + dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); + + FrameData frame3{}; + frame3.finalState = PresentResult::Presented; + frame3.displayed.push_back({ FrameType::Application, 3000 }); + auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); + + Assert::IsTrue(results[0].metrics.msAnimationError.has_value(), + L"Error should still be computed even with backwards screen time"); + double simElapsed = qpc.DeltaUnsignedMilliSeconds(100, 150); // 0.005 ms + double displayElapsed = qpc.DeltaUnsignedMilliSeconds(1100, 1050); // -0.005 ms (negative!) + double expected = simElapsed - displayElapsed; // 0.010 ms + Assert::AreEqual(expected, results[0].metrics.msAnimationError.value(), 0.0001); + } + + TEST_METHOD(AnimationError_VeryLargeCadenceMismatch_LargeError) + { + // Scenario: Sim running much faster than display + // Expected: Large positive error + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + state.animationErrorSource = AnimationErrorSource::AppProvider; + + FrameData frame1{}; + frame1.presentStartTime = 1000; + frame1.timeInPresent = 100; + frame1.appSimStartTime = 100; + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 1000 }); + + FrameData frame2{}; + frame2.presentStartTime = 2000; + frame2.timeInPresent = 100; + frame2.appSimStartTime = 500; // sim elapsed = 400 ticks + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 1010 }); // display elapsed = 10 ticks + + FrameData dummyNext{}; + dummyNext.finalState = PresentResult::Presented; + dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); + + FrameData frame3{}; + frame3.finalState = PresentResult::Presented; + frame3.displayed.push_back({ FrameType::Application, 3000 }); + auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); + + Assert::IsTrue(results[0].metrics.msAnimationError.has_value()); + double simElapsed = qpc.DeltaUnsignedMilliSeconds(100, 500); // 0.040 ms + double displayElapsed = qpc.DeltaUnsignedMilliSeconds(1000, 1010); // 0.001 ms + double expected = simElapsed - displayElapsed; // 0.039 ms + Assert::AreEqual(expected, results[0].metrics.msAnimationError.value(), 0.0001, + L"Large cadence mismatch should produce large positive error"); + } + + TEST_METHOD(AnimationError_RepeatedFrameType_BothNullopt) + { + // Scenario: Frame displayed but type is Repeated, not Application + // Expected: Animation metrics should be nullopt + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + state.animationErrorSource = AnimationErrorSource::AppProvider; + state.firstAppSimStartTime = 100; + state.lastDisplayedSimStartTime = 100; + + FrameData present{}; + present.presentStartTime = 1000; + present.timeInPresent = 100; + present.appSimStartTime = 150; + present.finalState = PresentResult::Presented; + present.displayed.push_back({ FrameType::Repeated, 2000 }); + + FrameData nextPresent{}; + nextPresent.finalState = PresentResult::Presented; + nextPresent.displayed.push_back({ FrameType::Application, 3000 }); + + auto results = ComputeMetricsForPresent(qpc, present, &nextPresent, state); + + Assert::IsFalse(results[0].metrics.msAnimationError.has_value(), + L"msAnimationError should be nullopt for Repeated frame type"); + Assert::IsFalse(results[0].metrics.msAnimationTime.has_value(), + L"msAnimationTime should be nullopt for Repeated frame type"); + } + + TEST_METHOD(AnimationError_MultipleDisplayInstances_OnlyLastAppIndex) + { + // Scenario: Present has 3 display instances, only middle one is Application + // Expected: Animation computed only for Application display instance + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + state.animationErrorSource = AnimationErrorSource::AppProvider; + + // Setup prior frame + FrameData frame1{}; + frame1.presentStartTime = 1000; + frame1.timeInPresent = 100; + frame1.appSimStartTime = 100; + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 1000 }); + + // Frame with multiple display instances + FrameData frame2{}; + frame2.presentStartTime = 2000; + frame2.timeInPresent = 100; + frame2.appSimStartTime = 150; + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Repeated, 2000 }); // [0] + frame2.displayed.push_back({ FrameType::Application, 2050 }); // [1] - appIndex + frame2.displayed.push_back({ FrameType::Repeated, 2100 }); // [2] + + FrameData dummyNext{}; + dummyNext.finalState = PresentResult::Presented; + dummyNext.displayed.push_back({ FrameType::Application, 3000 }); + ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); + + // Process frame2 without next (should process [0] and [1]) + auto resultsPartial = ComputeMetricsForPresent(qpc, frame2, nullptr, state); + Assert::AreEqual(size_t(2), resultsPartial.size()); + + // First display instance (Repeated) - no animation metrics + Assert::IsFalse(resultsPartial[0].metrics.msAnimationError.has_value(), + L"Display [0] (Repeated) should not have animation error"); + + // Second display instance (Application) - has animation metrics + Assert::IsTrue(resultsPartial[1].metrics.msAnimationError.has_value(), + L"Display [1] (Application) should have animation error"); + + double simElapsed = qpc.DeltaUnsignedMilliSeconds(100, 150); + double displayElapsed = qpc.DeltaUnsignedMilliSeconds(1000, 2050); + double expected = simElapsed - displayElapsed; + Assert::AreEqual(expected, resultsPartial[1].metrics.msAnimationError.value(), 0.0001); + } + }; } \ No newline at end of file From 4aabe23b4b5db07eb7e1fc5d2a10d8e99df22d6f Mon Sep 17 00:00:00 2001 From: Chili Date: Sun, 7 Dec 2025 07:49:17 +0900 Subject: [PATCH 025/205] fix test child cli args --- IntelPresentMon/PresentMonAPI2Tests/EtlLoggerTests.cpp | 3 +-- IntelPresentMon/PresentMonAPI2Tests/MultiClientTests.cpp | 3 +-- IntelPresentMon/PresentMonAPI2Tests/PacedPollingTests.cpp | 3 +-- IntelPresentMon/PresentMonAPI2Tests/TestProcess.h | 7 ++----- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/IntelPresentMon/PresentMonAPI2Tests/EtlLoggerTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/EtlLoggerTests.cpp index 86f090ed..3629324f 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/EtlLoggerTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/EtlLoggerTests.cpp @@ -25,8 +25,7 @@ namespace EtlLoggerTests { static CommonProcessArgs args{ .ctrlPipe = R"(\\.\pipe\pm-etllog-test-ctrl)", - .introNsm = "pm_etllog_test_intro", - .frameNsm = "pm_etllog_test_nsm", + .shmNamePrefix = "pm_etllog_test_intro", .logLevel = "debug", .logFolder = logFolder_, .sampleClientMode = "EtlLogger", diff --git a/IntelPresentMon/PresentMonAPI2Tests/MultiClientTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/MultiClientTests.cpp index fc737fe4..e0819c7d 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/MultiClientTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/MultiClientTests.cpp @@ -24,8 +24,7 @@ namespace MultiClientTests { static CommonProcessArgs args{ .ctrlPipe = R"(\\.\pipe\pm-multi-test-ctrl)", - .introNsm = "pm_multi_test_intro", - .frameNsm = "pm_multi_test_nsm", + .shmNamePrefix = "pm_multi_test_intro", .logLevel = "debug", .logFolder = logFolder_, .sampleClientMode = "MultiClient", diff --git a/IntelPresentMon/PresentMonAPI2Tests/PacedPollingTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/PacedPollingTests.cpp index 34e51c94..6e67ee47 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/PacedPollingTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/PacedPollingTests.cpp @@ -34,8 +34,7 @@ namespace PacedPolling { static CommonProcessArgs args{ .ctrlPipe = R"(\\.\pipe\pm-paced-polling-test-ctrl)", - .introNsm = "pm_paced_polling_test_intro", - .frameNsm = "pm_paced_polling_test_nsm", + .shmNamePrefix = "pm_paced_polling_test_intro", .logLevel = "debug", .logFolder = logFolder_, .sampleClientMode = "PacedPlayback", diff --git a/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h b/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h index d2c6d02f..8cc23f12 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h +++ b/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h @@ -25,8 +25,7 @@ using namespace pmon; struct CommonProcessArgs { std::string ctrlPipe; - std::string introNsm; - std::string frameNsm; + std::string shmNamePrefix; std::string logLevel; std::string logFolder; std::string sampleClientMode; @@ -163,8 +162,7 @@ class ServiceProcess : public ConnectedTestProcess { std::vector allArgs{ "--control-pipe"s, common.ctrlPipe, - "--nsm-prefix"s, common.frameNsm, - "--intro-nsm"s, common.introNsm, + "--shm-name-prefix"s, common.shmNamePrefix, "--enable-test-control"s, "--log-dir"s, common.logFolder, "--log-name-pid"s, @@ -199,7 +197,6 @@ class ClientProcess : public ConnectedTestProcess { std::vector allArgs{ "--control-pipe"s, common.ctrlPipe, - "--intro-nsm"s, common.introNsm, "--middleware-dll-path"s, "PresentMonAPI2.dll"s, "--log-folder"s, common.logFolder, "--log-name-pid"s, From 2030d8249ba25bbfd5ccc3e3680e8775c3471407 Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 8 Dec 2025 13:30:23 +0900 Subject: [PATCH 026/205] fixing bug in secure subdir and basic action connect test --- .../file/SecureSubdirectory.cpp | 1 + IntelPresentMon/PresentMonAPI2Tests/Folders.h | 6 ++ .../InterimBroadcasterTests.cpp | 84 +++++++++++++++++++ .../PresentMonAPI2Tests.vcxproj | 1 + .../PresentMonAPI2Tests.vcxproj.filters | 3 + 5 files changed, 95 insertions(+) create mode 100644 IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp diff --git a/IntelPresentMon/CommonUtilities/file/SecureSubdirectory.cpp b/IntelPresentMon/CommonUtilities/file/SecureSubdirectory.cpp index fa2745b4..d35be5c2 100644 --- a/IntelPresentMon/CommonUtilities/file/SecureSubdirectory.cpp +++ b/IntelPresentMon/CommonUtilities/file/SecureSubdirectory.cpp @@ -253,6 +253,7 @@ namespace pmon::util::file { if (!Empty()) { Clear(); + hDirectory_.Clear(); fs::remove(path_); path_.clear(); } diff --git a/IntelPresentMon/PresentMonAPI2Tests/Folders.h b/IntelPresentMon/PresentMonAPI2Tests/Folders.h index 8b7b6a9d..38f38d4c 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/Folders.h +++ b/IntelPresentMon/PresentMonAPI2Tests/Folders.h @@ -15,4 +15,10 @@ namespace PacedPolling { static constexpr const char* logFolder_ = "TestLogs\\PacedPolling"; static constexpr const char* outFolder_ = "TestOutput\\PacedPolling"; +} + +namespace InterimBroadcasterTests +{ + static constexpr const char* logFolder_ = "TestLogs\\InterimBroadcaster"; + static constexpr const char* outFolder_ = "TestOutput\\InterimBroadcaster"; } \ No newline at end of file diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp new file mode 100644 index 00000000..61cf2113 --- /dev/null +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -0,0 +1,84 @@ +// Copyright (C) 2022-2023 Intel Corporation +// SPDX-License-Identifier: MIT +#include "../CommonUtilities/win/WinAPI.h" +#include "CppUnitTest.h" +#include "StatusComparison.h" +#include "TestProcess.h" +#include +#include +#include "Folders.h" +#include "JobManager.h" + +#include "../PresentMonMiddleware/ActionClient.h" + + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +namespace vi = std::views; +using namespace std::literals; +using namespace pmon; + +namespace InterimBroadcasterTests +{ + class TestFixture : public CommonTestFixture + { + public: + const CommonProcessArgs& GetCommonArgs() const override + { + static CommonProcessArgs args{ + .ctrlPipe = R"(\\.\pipe\pm-intbroad-test-ctrl)", + .shmNamePrefix = "pm_intborad_test_intro", + .logLevel = "debug", + .logFolder = logFolder_, + .sampleClientMode = "NONE", + }; + return args; + } + }; + + TEST_CLASS(CommonFixtureTests) + { + TestFixture fixture_; + + public: + TEST_METHOD_INITIALIZE(Setup) + { + fixture_.Setup(); + } + TEST_METHOD_CLEANUP(Cleanup) + { + fixture_.Cleanup(); + } + // verify service lifetime and status command functionality + TEST_METHOD(ServiceStatusTest) + { + // verify initial status + const auto status = fixture_.service->QueryStatus(); + Assert::AreEqual(0ull, status.nsmStreamedPids.size()); + Assert::AreEqual(16u, status.telemetryPeriodMs); + Assert::IsTrue((bool)status.etwFlushPeriodMs); + Assert::AreEqual(1000u, *status.etwFlushPeriodMs); + } + // verify action system can connect + TEST_METHOD(ActionConnect) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + Assert::IsFalse(client.GetShmPrefix().empty()); + // there is a bit of a race condition on creating a service, immediately connecting + // and then immediately terminating it via the test control module + // not a concern for normal operation and is entirely synthetic; don't waste + // effort on trying to rework this, just add a little wait for odd tests like this + std::this_thread::sleep_for(150ms); + } + // verify comms work with introspection + TEST_METHOD(IntrospectionConnect) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + Assert::IsFalse(client.GetShmPrefix().empty()); + // there is a bit of a race condition on creating a service, immediately connecting + // and then immediately terminating it via the test control module + // not a concern for normal operation and is entirely synthetic; don't waste + // effort on trying to rework this, just add a little wait for odd tests like this + std::this_thread::sleep_for(150ms); + } + }; +} \ No newline at end of file diff --git a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj index 5ce3f9ec..29e8b70b 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj +++ b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj @@ -97,6 +97,7 @@ + diff --git a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj.filters b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj.filters index a8ea4a3d..b9a1614c 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj.filters +++ b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj.filters @@ -39,6 +39,9 @@ Source Files + + Source Files + From 31ce7abbcbcb9963203b1de471059b38ab5838f0 Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 8 Dec 2025 14:37:23 +0900 Subject: [PATCH 027/205] interim test basics --- .../InterimBroadcasterTests.cpp | 14 +++++++------- .../PresentMonAPI2Tests.vcxproj | 3 +++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 61cf2113..357c339f 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -10,6 +10,7 @@ #include "JobManager.h" #include "../PresentMonMiddleware/ActionClient.h" +#include "../Interprocess/source/Interprocess.h" using namespace Microsoft::VisualStudio::CppUnitTestFramework; @@ -69,16 +70,15 @@ namespace InterimBroadcasterTests // effort on trying to rework this, just add a little wait for odd tests like this std::this_thread::sleep_for(150ms); } - // verify comms work with introspection + // verify comms work with introspection (no wrapper) TEST_METHOD(IntrospectionConnect) { mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; - Assert::IsFalse(client.GetShmPrefix().empty()); - // there is a bit of a race condition on creating a service, immediately connecting - // and then immediately terminating it via the test control module - // not a concern for normal operation and is entirely synthetic; don't waste - // effort on trying to rework this, just add a little wait for odd tests like this - std::this_thread::sleep_for(150ms); + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + auto pIntro = pComms->GetIntrospectionRoot(); + Assert::AreEqual(2ull, pIntro->pDevices->size); + auto pDevice = static_cast(pIntro->pDevices->pData[1]); + Assert::AreEqual("NVIDIA GeForce RTX 2080 Ti", pDevice->pName->pData); } }; } \ No newline at end of file diff --git a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj index 29e8b70b..e2c61ae5 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj +++ b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj @@ -110,6 +110,9 @@ {08a704d8-ca1c-45e9-8ede-542a1a43b53e} + + {ca23d648-daef-4f06-81d5-fe619bd31f0b} + {8f86d067-2437-46fc-8f82-4d7155ceced7} From cf80d50e8c51a1e22f2aac0edf37fc2f2e6c8f86 Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 8 Dec 2025 16:00:45 +0900 Subject: [PATCH 028/205] fixes to cpu vendor detection and static cpu ipc test --- .../InterimBroadcasterTests.cpp | 35 +++++++++++++++++++ .../PresentMonService/PMMainThread.cpp | 5 +-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 357c339f..faa397fd 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -15,6 +15,7 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace vi = std::views; +namespace rn = std::ranges; using namespace std::literals; using namespace pmon; @@ -81,4 +82,38 @@ namespace InterimBroadcasterTests Assert::AreEqual("NVIDIA GeForce RTX 2080 Ti", pDevice->pName->pData); } }; + + TEST_CLASS(CpuStoreTests) + { + TestFixture fixture_; + public: + TEST_METHOD_INITIALIZE(Setup) + { + fixture_.Setup(); + } + TEST_METHOD_CLEANUP(Cleanup) + { + fixture_.Cleanup(); + } + // static store + TEST_METHOD(StaticData) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + std::this_thread::sleep_for(100ms); + auto& sys = pComms->GetSystemDataStore(); + Assert::AreEqual((int)PM_DEVICE_VENDOR_AMD, (int)sys.statics.cpuVendor); + Assert::AreEqual("AMD Ryzen 7 5800X 8-Core Processor", sys.statics.cpuName.c_str()); + Assert::AreEqual(0., sys.statics.cpuPowerLimit); + } + // polled store + TEST_METHOD(PolledData) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + std::this_thread::sleep_for(100ms); + auto& sys = pComms->GetSystemDataStore(); + Assert::AreEqual(1ull, (size_t)rn::distance(sys.telemetryData.Rings())); + } + }; } \ No newline at end of file diff --git a/IntelPresentMon/PresentMonService/PMMainThread.cpp b/IntelPresentMon/PresentMonService/PMMainThread.cpp index 1d4b25a0..2f22fdec 100644 --- a/IntelPresentMon/PresentMonService/PMMainThread.cpp +++ b/IntelPresentMon/PresentMonService/PMMainThread.cpp @@ -329,10 +329,10 @@ void PresentMonMainThread(Service* const pSvc) const auto vendor = [&] { const auto lowerNameRn = cpu->GetCpuName() | vi::transform(tolower); const std::string lowerName{ lowerNameRn.begin(), lowerNameRn.end() }; - if (rn::search(lowerName, "intel")) { + if (lowerName.contains("intel")) { return PM_DEVICE_VENDOR_INTEL; } - else if (rn::search(lowerName, "amd")) { + else if (lowerName.contains("amd")) { return PM_DEVICE_VENDOR_AMD; } else { @@ -346,6 +346,7 @@ void PresentMonMainThread(Service* const pSvc) // if the introspection itself is not complete auto& systemStore = pComms->GetSystemDataStore(); // TODO: replace this placeholder routine for populating statics + systemStore.statics.cpuName = cpu->GetCpuName().c_str(); systemStore.statics.cpuPowerLimit = cpu->GetCpuPowerLimit(); systemStore.statics.cpuVendor = vendor; From 7c2647f3799317cb079180275000c083d3b6ee77 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Mon, 8 Dec 2025 08:00:19 -0800 Subject: [PATCH 029/205] Fixing Animation Error and Time tests. Still WIP. --- IntelPresentMon/UnitTests/MetricsCore.cpp | 602 ++++++++++++---------- 1 file changed, 334 insertions(+), 268 deletions(-) diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index 8e590e5d..68eb899f 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -4385,31 +4385,32 @@ namespace MetricsCoreTests frame.displayed.push_back({ FrameType::Application, 1'000'000 }); // Create nextDisplayed - FrameData next{}; - next.presentStartTime = 2'000'000; - next.timeInPresent = 400; - next.readyTime = 2'500'000; - next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'000'000 }); + FrameData frame2{}; + frame2.presentStartTime = 2'000'000; + frame2.timeInPresent = 400; + frame2.readyTime = 2'500'000; + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 2'000'000 }); // Action: Compute metrics - auto metricsVector = ComputeMetricsForPresent(qpc, frame, &next, state); + auto metricsVector = ComputeMetricsForPresent(qpc, frame, &frame2, state); // Assert Assert::AreEqual(size_t(1), metricsVector.size()); const ComputedMetrics& result = metricsVector[0]; - // Assert: msAnimationTime should be 0 (first transition frame) - Assert::IsTrue(result.metrics.msAnimationTime.has_value(), - L"msAnimationTime should have a value on first valid app sim start"); - Assert::AreEqual(0.0, result.metrics.msAnimationTime.value(), 0.0001, - L"msAnimationTime should be 0 on first transition frame"); + // Assert: msAnimationTime shouldn't have a value (first frame with valid app sim start) + Assert::IsFalse(result.metrics.msAnimationTime.has_value(), + L"msAnimationTime should not have a value on first valid app sim start"); // Assert: State should be updated Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, L"State: firstAppSimStartTime should be set to first valid app sim start"); Assert::AreEqual(uint64_t(100), state.lastDisplayedSimStartTime, L"State: lastDisplayedSimStartTime should be set to current frame's app sim start"); + Assert::IsTrue( + state.animationErrorSource == AnimationErrorSource::AppProvider, + L"Animation source should transition to AppProvider after first appSimStartTime."); } // ======================================================================== @@ -4489,225 +4490,228 @@ namespace MetricsCoreTests TEST_METHOD(AnimationTime_AppProvider_ThreeFrames_CumulativeElapsedTime) { // Scenario: - // Start with CpuStart, source switches when first app data arrives - // Frame 1: appSimStartTime = qpc(100) → msAnimationTime = 0, sets firstAppSimStartTime = 100 - // Frame 2: appSimStartTime = qpc(150) → msAnimationTime = 0.005 ms - // Frame 3: appSimStartTime = qpc(250) → expected msAnimationTime = 0.015 ms - // QPC frequency = 10 MHz - // All frames are app-displayed + // - Start with CpuStart as the animation source. + // - Frame 1: first displayed app frame with appSimStartTime = 100. + // This SEEDS animation state (firstAppSimStartTime), but + // does not yet emit msAnimationTime. + // - Frame 2: displayed app frame with appSimStartTime = 150. + // - Frame 3: displayed app frame with appSimStartTime = 250. // - // Expected Outcome: - // Each frame's msAnimationTime is computed relative to the SAME firstAppSimStartTime (100) - // Frame 3's msAnimationTime should be (250 - 100) / 10e6 = 0.015 ms + // QPC frequency = 10 MHz. + // + // Expected outcome: + // - firstAppSimStartTime is latched to 100 and never changes. + // - Frame 1: msAnimationTime == nullopt (just seeds state). + // - Frame 2: msAnimationTime = (150 - 100) / 10e6. + // - Frame 3: msAnimationTime = (250 - 100) / 10e6. QpcConverter qpc(10'000'000, 0); SwapChainCoreState state{}; - // Start with CpuStart, will switch to AppProvider after frame1 + // animationErrorSource defaults to CpuStart; will transition to AppProvider + // when the first displayed frame with appSimStartTime arrives. - // Frame 1: first valid app sim start + // -------------------------------------------------------------------- + // Frame 1: first valid app sim start, displayed + // -------------------------------------------------------------------- FrameData frame1{}; frame1.presentStartTime = 1'000'000; frame1.timeInPresent = 500; frame1.readyTime = 1'500'000; frame1.appSimStartTime = 100; frame1.pclSimStartTime = 0; - frame1.finalState = PresentResult::Presented; + frame1.setFinalState(PresentResult::Presented); frame1.displayed.push_back({ FrameType::Application, 1'000'000 }); FrameData next1{}; next1.presentStartTime = 2'000'000; next1.timeInPresent = 400; next1.readyTime = 2'500'000; - next1.finalState = PresentResult::Presented; + next1.setFinalState(PresentResult::Presented); next1.displayed.push_back({ FrameType::Application, 2'000'000 }); auto metrics1 = ComputeMetricsForPresent(qpc, frame1, &next1, state); Assert::AreEqual(size_t(1), metrics1.size()); - Assert::IsTrue(metrics1[0].metrics.msAnimationTime.has_value()); - Assert::AreEqual(0.0, metrics1[0].metrics.msAnimationTime.value(), 0.0001); + + // First displayed app frame seeds state; animation time is not reported yet. + Assert::IsFalse( + metrics1[0].metrics.msAnimationTime.has_value(), + L"First AppProvider frame should seed firstAppSimStartTime but not report msAnimationTime yet."); + + // After processing frame1, the chain should have latched sim start and + // switched to AppProvider. Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime); + Assert::AreEqual(uint64_t(100), state.lastDisplayedSimStartTime); + Assert::IsTrue( + state.animationErrorSource == AnimationErrorSource::AppProvider, + L"Animation source should transition to AppProvider after first appSimStartTime."); - // Frame 2: incremented sim start + // -------------------------------------------------------------------- + // Frame 2: second displayed app frame + // -------------------------------------------------------------------- FrameData frame2{}; frame2.presentStartTime = 3'000'000; frame2.timeInPresent = 500; frame2.readyTime = 3'500'000; frame2.appSimStartTime = 150; frame2.pclSimStartTime = 0; - frame2.finalState = PresentResult::Presented; + frame2.setFinalState(PresentResult::Presented); frame2.displayed.push_back({ FrameType::Application, 3'000'000 }); FrameData next2{}; next2.presentStartTime = 4'000'000; next2.timeInPresent = 400; next2.readyTime = 4'500'000; - next2.finalState = PresentResult::Presented; + next2.setFinalState(PresentResult::Presented); next2.displayed.push_back({ FrameType::Application, 4'000'000 }); auto metrics2 = ComputeMetricsForPresent(qpc, frame2, &next2, state); Assert::AreEqual(size_t(1), metrics2.size()); - Assert::IsTrue(metrics2[0].metrics.msAnimationTime.has_value()); + Assert::IsTrue( + metrics2[0].metrics.msAnimationTime.has_value(), + L"Second displayed app frame should report msAnimationTime."); + double expected2 = qpc.DeltaUnsignedMilliSeconds(100, 150); - Assert::AreEqual(expected2, metrics2[0].metrics.msAnimationTime.value(), 0.0001); - Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, L"Should not change"); + Assert::AreEqual( + expected2, + metrics2[0].metrics.msAnimationTime.value(), + 0.0001, + L"Frame 2's msAnimationTime should be relative to firstAppSimStartTime (100 → 150)."); + + // firstAppSimStartTime should stay anchored at 100 + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, L"firstAppSimStartTime should not change."); + // lastDisplayedSimStartTime should now reflect frame2 + Assert::AreEqual(uint64_t(150), state.lastDisplayedSimStartTime); - // Frame 3: further incremented sim start + // -------------------------------------------------------------------- + // Frame 3: third displayed app frame + // -------------------------------------------------------------------- FrameData frame3{}; frame3.presentStartTime = 5'000'000; frame3.timeInPresent = 500; frame3.readyTime = 5'500'000; frame3.appSimStartTime = 250; frame3.pclSimStartTime = 0; - frame3.finalState = PresentResult::Presented; + frame3.setFinalState(PresentResult::Presented); frame3.displayed.push_back({ FrameType::Application, 5'000'000 }); FrameData next3{}; next3.presentStartTime = 6'000'000; next3.timeInPresent = 400; next3.readyTime = 6'500'000; - next3.finalState = PresentResult::Presented; + next3.setFinalState(PresentResult::Presented); next3.displayed.push_back({ FrameType::Application, 6'000'000 }); auto metrics3 = ComputeMetricsForPresent(qpc, frame3, &next3, state); Assert::AreEqual(size_t(1), metrics3.size()); - Assert::IsTrue(metrics3[0].metrics.msAnimationTime.has_value()); + Assert::IsTrue( + metrics3[0].metrics.msAnimationTime.has_value(), + L"Third displayed app frame should report msAnimationTime."); + double expected3 = qpc.DeltaUnsignedMilliSeconds(100, 250); - Assert::AreEqual(expected3, metrics3[0].metrics.msAnimationTime.value(), 0.0001, - L"Frame 3's msAnimationTime should be relative to original firstAppSimStartTime"); - Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, L"Should remain at first value"); - } + Assert::AreEqual( + expected3, + metrics3[0].metrics.msAnimationTime.value(), + 0.0001, + L"Frame 3's msAnimationTime should be relative to original firstAppSimStartTime (100 → 250)."); + // firstAppSimStartTime remains the original seed + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, L"firstAppSimStartTime should remain at 100."); + // lastDisplayedSimStartTime should now reflect frame3 + Assert::AreEqual(uint64_t(250), state.lastDisplayedSimStartTime); + } // ======================================================================== // A5: AnimationTime_AppProvider_SkippedFrame_StaysConsistent // ======================================================================== TEST_METHOD(AnimationTime_AppProvider_SkippedFrame_StaysConsistent) { - // Scenario: - // Start with CpuStart - // Baseline: Establish CPU frames before app data arrives - // Frame 1 (displayed): appSimStartTime = qpc(100) → msAnimationTime = 0, switches to AppProvider - // Frame 2 (not displayed): skipped from animation - // Frame 3 (displayed): appSimStartTime = qpc(200) → msAnimationTime = 0.01 ms - // - // Expected Outcome: - // Animation time stays consistent even when frames are skipped - // Frame 3's msAnimationTime = (200 - 100) / 10e6 = 0.01 ms + // We want to verify: + // - Animation source is AppProvider (AppSimStartTime-based). + // - A discarded (not displayed) frame with AppSimStartTime: + // * Produces no animation metrics. + // * Does NOT change firstAppSimStartTime / lastDisplayedSimStartTime / + // lastDisplayedAppScreenTime. + // - A later displayed app frame still computes msAnimationTime correctly + // based on the original firstAppSimStartTime. - QpcConverter qpc(10'000'000, 0); + QpcConverter qpc(10'000'000, 0); SwapChainCoreState state{}; - // state.animationErrorSource defaults to CpuStart - - // Frame 1: CPU-only frame (Presented) - FrameData frame1{}; - frame1.presentStartTime = 100'000; - frame1.timeInPresent = 50'000; - frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 200'000 }); - - // ReportMetrics: frame1 arrives (Presented) - // - First frame: just UpdateChain and return - // - Pending: empty (no processing happens) - state.UpdateAfterPresent(frame1); - - // Frame 2: CPU-only frame (Presented) - FrameData frame2{}; - frame2.presentStartTime = 500'000; - frame2.timeInPresent = 50'000; - frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 600'000 }); - - // ReportMetrics: frame2 arrives (Presented) - // - Pending is empty, so: - // - Process nothing from pending - // - Process frame2 with nullptr (postpone last display) - // - Add frame2 to pending - // Pending: [frame2] - ComputeMetricsForPresent(qpc, frame2, nullptr, state); - - // Frame 3: first valid app sim start (Presented) - FrameData frame3{}; - frame3.presentStartTime = 1'000'000; - frame3.timeInPresent = 500; - frame3.readyTime = 1'500'000; - frame3.appSimStartTime = 100; - frame3.pclSimStartTime = 0; - frame3.finalState = PresentResult::Presented; - frame3.displayed.push_back({ FrameType::Application, 1'000'000 }); - - // ReportMetrics: frame3 arrives (Presented) - // - Process pending [frame2] with frame3 as next - // - Process frame3 with nullptr (postpone) - // - Clear pending, add frame3 - // Pending: [frame3] - ComputeMetricsForPresent(qpc, frame2, &frame3, state); - - // Frame 4: discarded frame (Discarded) - FrameData frame4{}; - frame4.presentStartTime = 2'500'000; - frame4.timeInPresent = 400; - frame4.readyTime = 2'900'000; - frame4.appSimStartTime = 150; - frame4.pclSimStartTime = 0; - frame4.finalState = PresentResult::Discarded; - - // ReportMetrics: frame4 arrives (Discarded) - // - Pending is not empty - // - Add frame4 to pending (no processing) - // Pending: [frame3, frame4] - - // Frame 5: displayed again (Presented) - FrameData frame5{}; - frame5.presentStartTime = 3'000'000; - frame5.timeInPresent = 500; - frame5.readyTime = 3'500'000; - frame5.appSimStartTime = 200; - frame5.pclSimStartTime = 0; - frame5.finalState = PresentResult::Presented; - frame5.displayed.push_back({ FrameType::Application, 3'000'000 }); - - // ReportMetrics: frame5 arrives (Presented) - // - Process pending [frame3, frame4] with frame5 as next - ComputeMetricsForPresent(qpc, frame3, &frame5, state); - - auto metrics3 = ComputeMetricsForPresent(qpc, frame3, &frame5, state); - Assert::AreEqual(size_t(1), metrics3.size()); - Assert::IsTrue(metrics3[0].metrics.msAnimationTime.has_value()); - Assert::AreEqual(0.0, metrics3[0].metrics.msAnimationTime.value(), 0.0001); - Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime); - Assert::AreEqual(uint64_t(100), state.lastDisplayedSimStartTime); - Assert::IsTrue(state.animationErrorSource == AnimationErrorSource::AppProvider, - L"Source should switch to AppProvider after first app data"); - auto metrics4 = ComputeMetricsForPresent(qpc, frame4, &frame5, state); - Assert::AreEqual(size_t(1), metrics4.size()); - Assert::IsFalse(metrics4[0].metrics.msAnimationTime.has_value(), - L"Discarded frame should have no animation time"); - Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime); - Assert::AreEqual(uint64_t(100), state.lastDisplayedSimStartTime); - - // - Process frame5 with nullptr (postpone) - // - Clear pending, add frame5 - // Pending: [frame5] - ComputeMetricsForPresent(qpc, frame5, nullptr, state); - - // Frame 6: next presented frame - FrameData frame6{}; - frame6.presentStartTime = 4'000'000; - frame6.timeInPresent = 400; - frame6.readyTime = 4'500'000; - frame6.finalState = PresentResult::Presented; - frame6.displayed.push_back({ FrameType::Application, 4'000'000 }); - - // ReportMetrics: frame6 arrives (Presented) - // - Process pending [frame5] with frame6 as next - auto metrics5 = ComputeMetricsForPresent(qpc, frame5, &frame6, state); - Assert::AreEqual(size_t(1), metrics5.size()); - Assert::IsTrue(metrics5[0].metrics.msAnimationTime.has_value()); - double expected5 = qpc.DeltaUnsignedMilliSeconds(100, 200); - Assert::AreEqual(expected5, metrics5[0].metrics.msAnimationTime.value(), 0.0001); - - Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime); - Assert::AreEqual(uint64_t(200), state.lastDisplayedSimStartTime); + // Seed state as if we already had one displayed app frame at sim = 100. + state.animationErrorSource = AnimationErrorSource::AppProvider; + state.firstAppSimStartTime = 100; + state.lastDisplayedSimStartTime = 100; + state.lastDisplayedAppScreenTime = 1'000'000; // prior displayed screen time + + // -------------------------------------------------------------------- + // Frame 1: Discarded / not displayed, but with AppSimStartTime = 150 + // -------------------------------------------------------------------- + FrameData frameDropped{}; + frameDropped.presentStartTime = 2'000'000; + frameDropped.timeInPresent = 50'000; + frameDropped.readyTime = 2'050'000; + frameDropped.appSimStartTime = 150; + frameDropped.setFinalState(PresentResult::Discarded); + // No displayed entries -> not displayed + + auto droppedResults = ComputeMetricsForPresent(qpc, frameDropped, nullptr, state); + Assert::AreEqual(size_t(1), droppedResults.size()); + const auto& droppedMetrics = droppedResults[0].metrics; + + // Discarded / not-displayed frame must NOT produce animation metrics. + Assert::IsFalse(droppedMetrics.msAnimationTime.has_value(), + L"Discarded frame should not have msAnimationTime"); + Assert::IsFalse(droppedMetrics.msAnimationError.has_value(), + L"Discarded frame should not have msAnimationError"); + + // And it must NOT disturb the animation anchors from prior displayed frames. + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, + L"firstAppSimStartTime should be unchanged by discarded frame"); + Assert::AreEqual(uint64_t(100), state.lastDisplayedSimStartTime, + L"lastDisplayedSimStartTime should be unchanged by discarded frame"); + Assert::AreEqual(uint64_t(1'000'000), state.lastDisplayedAppScreenTime, + L"lastDisplayedAppScreenTime should be unchanged by discarded frame"); + + // -------------------------------------------------------------------- + // Frame 2: Next displayed app frame with AppSimStartTime = 200 + // -------------------------------------------------------------------- + FrameData frameDisplayed{}; + frameDisplayed.presentStartTime = 3'000'000; + frameDisplayed.timeInPresent = 50'000; + frameDisplayed.readyTime = 3'050'000; + frameDisplayed.appSimStartTime = 200; + frameDisplayed.setFinalState(PresentResult::Presented); + frameDisplayed.displayed.push_back({ FrameType::Application, 3'500'000 }); + + // Dummy "next" frame – just to exercise the Case 3 path so that + // UpdateAfterPresent() is called for frameDisplayed. + FrameData frameNext{}; + frameNext.presentStartTime = 4'000'000; + frameNext.timeInPresent = 50'000; + frameNext.readyTime = 4'050'000; + frameNext.appSimStartTime = 250; + frameNext.setFinalState(PresentResult::Presented); + frameNext.displayed.push_back({ FrameType::Application, 4'500'000 }); + + auto displayedResults = ComputeMetricsForPresent(qpc, frameDisplayed, &frameNext, state); + Assert::AreEqual(size_t(1), displayedResults.size()); + const auto& displayedMetrics = displayedResults[0].metrics; + + Assert::IsTrue(displayedMetrics.msAnimationTime.has_value(), + L"Displayed app frame should have msAnimationTime"); + + // Animation time should be based purely on the AppProvider sim times: + // firstAppSimStartTime = 100, currentSim = 200. + const double expected = qpc.DeltaUnsignedMilliSeconds(100, 200); + Assert::AreEqual(expected, displayedMetrics.msAnimationTime.value(), 0.0001); + + // After processing a displayed app frame via the Case 3 path, + // state should now reflect that frame as the last displayed. + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, + L"firstAppSimStartTime should remain the first displayed AppSimStartTime"); + Assert::AreEqual(uint64_t(200), state.lastDisplayedSimStartTime, + L"lastDisplayedSimStartTime should track the most recent DISPLAYED AppSimStartTime"); + Assert::AreEqual(uint64_t(3'500'000), state.lastDisplayedAppScreenTime, + L"lastDisplayedAppScreenTime should track the most recent displayed screen time"); } // ======================================================================== // B1: AnimationTime_PCLatency_FirstFrame_ZeroWithoutPclSimStartTime @@ -4805,31 +4809,32 @@ namespace MetricsCoreTests frame.displayed.push_back({ FrameType::Application, 1'000'000 }); // Create nextDisplayed - FrameData next{}; - next.presentStartTime = 2'000'000; - next.timeInPresent = 400; - next.readyTime = 2'500'000; - next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'000'000 }); + FrameData frame2{}; + frame2.presentStartTime = 2'000'000; + frame2.timeInPresent = 400; + frame2.readyTime = 2'500'000; + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 2'000'000 }); // Action: Compute metrics - auto metricsVector = ComputeMetricsForPresent(qpc, frame, &next, state); + auto metricsVector = ComputeMetricsForPresent(qpc, frame, &frame2, state); // Assert Assert::AreEqual(size_t(1), metricsVector.size()); const ComputedMetrics& result = metricsVector[0]; - // Assert: msAnimationTime should be 0 (first transition frame) - Assert::IsTrue(result.metrics.msAnimationTime.has_value(), - L"msAnimationTime should have a value on first valid pcl sim start"); - Assert::AreEqual(0.0, result.metrics.msAnimationTime.value(), 0.0001, - L"msAnimationTime should be 0 on first transition frame"); + // Assert: msAnimationTime shouldn't have a value (first frame with valid pcl sim start) + Assert::IsFalse(result.metrics.msAnimationTime.has_value(), + L"msAnimationTime should not have a value on first valid pcl sim start"); // Assert: State should be updated Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, L"State: firstAppSimStartTime should be set to first valid pcl sim start"); Assert::AreEqual(uint64_t(100), state.lastDisplayedSimStartTime, L"State: lastDisplayedSimStartTime should be set to current frame's pcl sim start"); + Assert::IsTrue( + state.animationErrorSource == AnimationErrorSource::PCLatency, + L"Animation source should transition to PCLatency after first appSimStartTime."); } // ======================================================================== @@ -4909,188 +4914,249 @@ namespace MetricsCoreTests TEST_METHOD(AnimationTime_PCLatency_ThreeFrames_CumulativeElapsedTime) { // Scenario: - // Start with CpuStart source - // Frame 1: pclSimStartTime = qpc(100) → msAnimationTime = 0, switches to PCLatency - // Frame 2: pclSimStartTime = qpc(250) → msAnimationTime = 0.015 ms - // Frame 3: pclSimStartTime = qpc(400) → expected msAnimationTime = 0.03 ms - // QPC frequency = 10 MHz - // All frames are app-displayed + // - Start with CpuStart as the animation source. + // - Frame 1: first displayed app frame with appSimStartTime = 100. + // This SEEDS animation state (firstAppSimStartTime), but + // does not yet emit msAnimationTime. + // - Frame 2: displayed app frame with appSimStartTime = 150. + // - Frame 3: displayed app frame with appSimStartTime = 250. // - // Expected Outcome: - // Each frame's msAnimationTime is computed relative to the SAME firstAppSimStartTime (100) - // Frame 3's msAnimationTime should be (400 - 100) / 10e6 = 0.03 ms + // QPC frequency = 10 MHz. + // + // Expected outcome: + // - firstAppSimStartTime is latched to 100 and never changes. + // - Frame 1: msAnimationTime == nullopt (just seeds state). + // - Frame 2: msAnimationTime = (150 - 100) / 10e6. + // - Frame 3: msAnimationTime = (250 - 100) / 10e6. QpcConverter qpc(10'000'000, 0); SwapChainCoreState state{}; - // state.animationErrorSource defaults to CpuStart + // animationErrorSource defaults to CpuStart; will transition to AppProvider + // when the first displayed frame with appSimStartTime arrives. - // Frame 1: first valid pcl sim start + // -------------------------------------------------------------------- + // Frame 1: first valid app sim start, displayed + // -------------------------------------------------------------------- FrameData frame1{}; frame1.presentStartTime = 1'000'000; frame1.timeInPresent = 500; frame1.readyTime = 1'500'000; frame1.appSimStartTime = 0; frame1.pclSimStartTime = 100; - frame1.finalState = PresentResult::Presented; + frame1.setFinalState(PresentResult::Presented); frame1.displayed.push_back({ FrameType::Application, 1'000'000 }); FrameData next1{}; next1.presentStartTime = 2'000'000; next1.timeInPresent = 400; next1.readyTime = 2'500'000; - next1.finalState = PresentResult::Presented; + next1.setFinalState(PresentResult::Presented); next1.displayed.push_back({ FrameType::Application, 2'000'000 }); auto metrics1 = ComputeMetricsForPresent(qpc, frame1, &next1, state); Assert::AreEqual(size_t(1), metrics1.size()); - Assert::IsTrue(metrics1[0].metrics.msAnimationTime.has_value()); - Assert::AreEqual(0.0, metrics1[0].metrics.msAnimationTime.value(), 0.0001); + + // First displayed app frame seeds state; animation time is not reported yet. + Assert::IsFalse( + metrics1[0].metrics.msAnimationTime.has_value(), + L"First PCLatecny frame should seed firstAppSimStartTime but not report msAnimationTime yet."); + + // After processing frame1, the chain should have latched sim start and + // switched to PCLatency. Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime); + Assert::AreEqual(uint64_t(100), state.lastDisplayedSimStartTime); + Assert::IsTrue( + state.animationErrorSource == AnimationErrorSource::PCLatency, + L"Animation source should transition to PCLatency after first appSimStartTime."); - // Frame 2: incremented pcl sim start + // -------------------------------------------------------------------- + // Frame 2: second displayed app frame + // -------------------------------------------------------------------- FrameData frame2{}; frame2.presentStartTime = 3'000'000; frame2.timeInPresent = 500; frame2.readyTime = 3'500'000; frame2.appSimStartTime = 0; - frame2.pclSimStartTime = 250; - frame2.finalState = PresentResult::Presented; + frame2.pclSimStartTime = 150; + frame2.setFinalState(PresentResult::Presented); frame2.displayed.push_back({ FrameType::Application, 3'000'000 }); FrameData next2{}; next2.presentStartTime = 4'000'000; next2.timeInPresent = 400; next2.readyTime = 4'500'000; - next2.finalState = PresentResult::Presented; + next2.setFinalState(PresentResult::Presented); next2.displayed.push_back({ FrameType::Application, 4'000'000 }); auto metrics2 = ComputeMetricsForPresent(qpc, frame2, &next2, state); Assert::AreEqual(size_t(1), metrics2.size()); - Assert::IsTrue(metrics2[0].metrics.msAnimationTime.has_value()); - double expected2 = qpc.DeltaUnsignedMilliSeconds(100, 250); - Assert::AreEqual(expected2, metrics2[0].metrics.msAnimationTime.value(), 0.0001); - Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, L"Should not change"); + Assert::IsTrue( + metrics2[0].metrics.msAnimationTime.has_value(), + L"Second displayed app frame should report msAnimationTime."); + + double expected2 = qpc.DeltaUnsignedMilliSeconds(100, 150); + Assert::AreEqual( + expected2, + metrics2[0].metrics.msAnimationTime.value(), + 0.0001, + L"Frame 2's msAnimationTime should be relative to firstAppSimStartTime (100 → 150)."); + + // firstAppSimStartTime should stay anchored at 100 + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, L"firstAppSimStartTime should not change."); + // lastDisplayedSimStartTime should now reflect frame2 + Assert::AreEqual(uint64_t(150), state.lastDisplayedSimStartTime); - // Frame 3: further incremented pcl sim start + // -------------------------------------------------------------------- + // Frame 3: third displayed app frame + // -------------------------------------------------------------------- FrameData frame3{}; frame3.presentStartTime = 5'000'000; frame3.timeInPresent = 500; frame3.readyTime = 5'500'000; frame3.appSimStartTime = 0; - frame3.pclSimStartTime = 400; - frame3.finalState = PresentResult::Presented; + frame3.pclSimStartTime = 250; + frame3.setFinalState(PresentResult::Presented); frame3.displayed.push_back({ FrameType::Application, 5'000'000 }); FrameData next3{}; next3.presentStartTime = 6'000'000; next3.timeInPresent = 400; next3.readyTime = 6'500'000; - next3.finalState = PresentResult::Presented; + next3.setFinalState(PresentResult::Presented); next3.displayed.push_back({ FrameType::Application, 6'000'000 }); auto metrics3 = ComputeMetricsForPresent(qpc, frame3, &next3, state); Assert::AreEqual(size_t(1), metrics3.size()); - Assert::IsTrue(metrics3[0].metrics.msAnimationTime.has_value()); - double expected3 = qpc.DeltaUnsignedMilliSeconds(100, 400); - Assert::AreEqual(expected3, metrics3[0].metrics.msAnimationTime.value(), 0.0001, - L"Frame 3's msAnimationTime should be relative to original firstAppSimStartTime"); - Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, L"Should remain at first value"); - } + Assert::IsTrue( + metrics3[0].metrics.msAnimationTime.has_value(), + L"Third displayed app frame should report msAnimationTime."); + + double expected3 = qpc.DeltaUnsignedMilliSeconds(100, 250); + Assert::AreEqual( + expected3, + metrics3[0].metrics.msAnimationTime.value(), + 0.0001, + L"Frame 3's msAnimationTime should be relative to original firstAppSimStartTime (100 → 250)."); + // firstAppSimStartTime remains the original seed + Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, L"firstAppSimStartTime should remain at 100."); + // lastDisplayedSimStartTime should now reflect frame3 + Assert::AreEqual(uint64_t(250), state.lastDisplayedSimStartTime); + } // ======================================================================== // B5: AnimationTime_PCLatency_SkippedFrame_StaysConsistent // ======================================================================== TEST_METHOD(AnimationTime_PCLatency_SkippedFrame_StaysConsistent) { // Scenario: - // Frame 1 (displayed): pclSimStartTime = qpc(100) → msAnimationTime = 0, sets firstAppSimStartTime = 100 - // Frame 2 (not displayed): skipped from animation - // Frame 3 (displayed): pclSimStartTime = qpc(300) → msAnimationTime should still be relative to frame 1's sim start + // - Chain is configured to use PCLatency as the animation source. + // - Frame 1: displayed, pclSimStartTime = 100 → seeds animation state. + // - Frame 2: discarded (not displayed), pclSimStartTime = 200 → should NOT + // produce animation time and should NOT advance animation state. + // - Frame 3: displayed again, pclSimStartTime = 300 → animation time should be + // measured from the original 100, skipping the discarded frame. // - // Expected Outcome: - // Animation time stays consistent even when frames in between are skipped - // Frame 3's msAnimationTime = (300 - 100) / 10e6 = 0.02 ms - // firstAppSimStartTime unchanged + // Expectations: + // - Frame 1: msAnimationTime is std::nullopt, but firstAppSimStartTime and + // lastDisplayedSimStartTime are set to 100. + // - Frame 2: msAnimationTime is std::nullopt and state remains 100/100. + // - Frame 3: msAnimationTime == Delta(100, 300) and lastDisplayedSimStartTime == 300. QpcConverter qpc(10'000'000, 0); - SwapChainCoreState state{}; - // state.animationErrorSource defaults to CpuStart + SwapChainCoreState chain{}; - // Frame 1: first valid pcl sim start (displayed) + // Drive sim start from PCL latency + chain.animationErrorSource = AnimationErrorSource::PCLatency; + + // + // Frame 1: first displayed PCL frame + // FrameData frame1{}; frame1.presentStartTime = 1'000'000; - frame1.timeInPresent = 500; - frame1.readyTime = 1'500'000; - frame1.appSimStartTime = 0; + frame1.timeInPresent = 10'000; + frame1.readyTime = 1'010'000; frame1.pclSimStartTime = 100; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 1'000'000 }); + frame1.displayed.push_back({ FrameType::Application, 2'000'000 }); FrameData next1{}; - next1.presentStartTime = 2'000'000; - next1.timeInPresent = 400; - next1.readyTime = 2'500'000; + next1.presentStartTime = 3'000'000; + next1.timeInPresent = 10'000; + next1.readyTime = 3'010'000; next1.finalState = PresentResult::Presented; - next1.displayed.push_back({ FrameType::Application, 2'000'000 }); + next1.displayed.push_back({ FrameType::Application, 4'000'000 }); - auto metrics1 = ComputeMetricsForPresent(qpc, frame1, &next1, state); + auto metrics1 = ComputeMetricsForPresent(qpc, frame1, &next1, chain); Assert::AreEqual(size_t(1), metrics1.size()); - Assert::IsTrue(metrics1[0].metrics.msAnimationTime.has_value()); - Assert::AreEqual(0.0, metrics1[0].metrics.msAnimationTime.value(), 0.0001); - Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime); - Assert::AreEqual(uint64_t(100), state.lastDisplayedSimStartTime); - // Frame 2: not displayed (dropped frame) + // First displayed frame just seeds animation state; no animation time yet + Assert::IsFalse( + metrics1[0].metrics.msAnimationTime.has_value(), + L"First PCL-driven displayed frame should only seed state; no animation time reported yet."); + + Assert::AreEqual(uint64_t(100), chain.firstAppSimStartTime); + Assert::AreEqual(uint64_t(100), chain.lastDisplayedSimStartTime); + Assert::IsTrue(AnimationErrorSource::PCLatency == chain.animationErrorSource); + + // + // Frame 2: discarded (not displayed) but has a PCL sim start + // FrameData frame2{}; - frame2.presentStartTime = 2'500'000; - frame2.timeInPresent = 400; - frame2.readyTime = 2'900'000; - frame2.appSimStartTime = 0; - frame2.pclSimStartTime = 200; // Has pcl sim start but not displayed - frame2.finalState = PresentResult::Discarded; // Not presented, so not displayed + frame2.presentStartTime = 5'000'000; + frame2.timeInPresent = 10'000; + frame2.readyTime = 5'010'000; + frame2.pclSimStartTime = 200; // Has PCL sim start but not displayed + frame2.finalState = PresentResult::Discarded; // Not presented → not displayed - auto metrics2 = ComputeMetricsForPresent(qpc, frame2, nullptr, state); + auto metrics2 = ComputeMetricsForPresent(qpc, frame2, nullptr, chain); Assert::AreEqual(size_t(1), metrics2.size()); - // Not displayed, so animation metrics should be nullopt - Assert::IsFalse(metrics2[0].metrics.msAnimationTime.has_value()); - // State should not update animation sim start for non-displayed frames - Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, - L"Should remain unchanged after skipped frame"); - Assert::AreEqual(uint64_t(100), state.lastDisplayedSimStartTime, - L"Should remain unchanged after skipped frame"); - // Frame 3: displayed again (after skipped frame) + // Not displayed → no animation time, and animation state should not advance + Assert::IsFalse( + metrics2[0].metrics.msAnimationTime.has_value(), + L"Non-displayed frame should not report animation time."); + + Assert::AreEqual(uint64_t(100), chain.firstAppSimStartTime, + L"firstAppSimStartTime must remain anchored to Frame 1 after skipped frame."); + Assert::AreEqual(uint64_t(100), chain.lastDisplayedSimStartTime, + L"lastDisplayedSimStartTime must remain anchored to Frame 1 after skipped frame."); + + // + // Frame 3: displayed again after the skipped frame + // FrameData frame3{}; - frame3.presentStartTime = 3'000'000; - frame3.timeInPresent = 500; - frame3.readyTime = 3'500'000; - frame3.appSimStartTime = 0; + frame3.presentStartTime = 6'000'000; + frame3.timeInPresent = 10'000; + frame3.readyTime = 6'010'000; frame3.pclSimStartTime = 300; frame3.finalState = PresentResult::Presented; - frame3.displayed.push_back({ FrameType::Application, 3'000'000 }); + frame3.displayed.push_back({ FrameType::Application, 7'000'000 }); FrameData next3{}; - next3.presentStartTime = 4'000'000; - next3.timeInPresent = 400; - next3.readyTime = 4'500'000; + next3.presentStartTime = 8'000'000; + next3.timeInPresent = 10'000; + next3.readyTime = 8'010'000; next3.finalState = PresentResult::Presented; - next3.displayed.push_back({ FrameType::Application, 4'000'000 }); + next3.displayed.push_back({ FrameType::Application, 9'000'000 }); - auto metrics3 = ComputeMetricsForPresent(qpc, frame3, &next3, state); + auto metrics3 = ComputeMetricsForPresent(qpc, frame3, &next3, chain); Assert::AreEqual(size_t(1), metrics3.size()); - Assert::IsTrue(metrics3[0].metrics.msAnimationTime.has_value()); - // Frame 3 should compute relative to original Frame 1's pcl sim start + Assert::IsTrue( + metrics3[0].metrics.msAnimationTime.has_value(), + L"Displayed frame with valid PCL sim start should report animation time."); + double expected3 = qpc.DeltaUnsignedMilliSeconds(100, 300); - Assert::AreEqual(expected3, metrics3[0].metrics.msAnimationTime.value(), 0.0001, - L"Frame 3's msAnimationTime should be relative to Frame 1's pcl sim start, skipping Frame 2"); + Assert::AreEqual( + expected3, + metrics3[0].metrics.msAnimationTime.value(), + 0.0001, + L"Frame 3's msAnimationTime should be measured from Frame 1's PCL sim start, skipping Frame 2."); - // State should be updated to Frame 3's pcl sim start - Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime, - L"firstAppSimStartTime should remain at Frame 1's value"); - Assert::AreEqual(uint64_t(300), state.lastDisplayedSimStartTime, - L"lastDisplayedSimStartTime should update to Frame 3's pcl sim start"); + Assert::AreEqual(uint64_t(100), chain.firstAppSimStartTime, + L"firstAppSimStartTime should remain at Frame 1's value."); + Assert::AreEqual(uint64_t(300), chain.lastDisplayedSimStartTime, + L"lastDisplayedSimStartTime should advance to Frame 3's PCL sim start."); } - // ======================================================================== // B6: AnimationTime_PCLatency_FallsBackToCpuStart_WhenPclSimStartTimeZero // ======================================================================== From e90db922c29b02b6d169c537388ed2e3fdd486ab Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 8 Dec 2025 16:54:34 +0900 Subject: [PATCH 030/205] polled cpu telemetry test --- .../Interprocess/source/HistoryRing.h | 5 +++ .../InterimBroadcasterTests.cpp | 45 +++++++++++++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/HistoryRing.h b/IntelPresentMon/Interprocess/source/HistoryRing.h index 30c395e2..c55c2aa4 100644 --- a/IntelPresentMon/Interprocess/source/HistoryRing.h +++ b/IntelPresentMon/Interprocess/source/HistoryRing.h @@ -28,6 +28,11 @@ namespace pmon::ipc { samples_.Push({ value, timestamp }); } + const Sample& Front() const + { + auto&&[first, last] = samples_.GetSerialRange(); + return At(first); + } const Sample& At(size_t serial) const { return samples_.At(serial); diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index faa397fd..9c96b373 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -11,6 +11,8 @@ #include "../PresentMonMiddleware/ActionClient.h" #include "../Interprocess/source/Interprocess.h" +#include "../PresentMonAPIWrapperCommon/EnumMap.h" +#include "../PresentMonService/AllActions.h" using namespace Microsoft::VisualStudio::CppUnitTestFramework; @@ -100,7 +102,7 @@ namespace InterimBroadcasterTests { mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); - std::this_thread::sleep_for(100ms); + // get the store containing system-wide telemetry (cpu etc.) auto& sys = pComms->GetSystemDataStore(); Assert::AreEqual((int)PM_DEVICE_VENDOR_AMD, (int)sys.statics.cpuVendor); Assert::AreEqual("AMD Ryzen 7 5800X 8-Core Processor", sys.statics.cpuName.c_str()); @@ -111,9 +113,46 @@ namespace InterimBroadcasterTests { mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); - std::this_thread::sleep_for(100ms); + // acquire introspection with enhanced wrapper interface + auto pIntro = pComms->GetIntrospectionRoot(); + pmapi::intro::Root intro{ pIntro, [](auto* p){delete p;} }; + pmapi::EnumMap::Refresh(intro); + auto pMetricMap = pmapi::EnumMap::GetKeyMap(PM_ENUM_METRIC); + // as a stopgap we target a process in order to trigger service-side telemetry collection + // TODO: remove this when we enable service-side query awareness of connected clients + auto pres = fixture_.LaunchPresenter(); + client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pres.GetId() }); + // get the store containing system-wide telemetry (cpu etc.) auto& sys = pComms->GetSystemDataStore(); - Assert::AreEqual(1ull, (size_t)rn::distance(sys.telemetryData.Rings())); + for (auto&&[met, r] : sys.telemetryData.Rings()) { + Logger::WriteMessage(std::format(" TeleRing@{}\n", pMetricMap->at(met).narrowName).c_str()); + // TODO: understand the disconnect between CPU Core Utility showing up here + // and now showing up in the UI + } + Assert::AreEqual(3ull, (size_t)rn::distance(sys.telemetryData.Rings())); + std::this_thread::sleep_for(1000ms); + // check that we have data for frequency and utilization + for (int i = 0; i < 50; i++) { + std::this_thread::sleep_for(100ms); + { + constexpr auto m = PM_METRIC_CPU_UTILIZATION; + auto& r = sys.telemetryData.FindRing(m).at(0); + Assert::IsFalse(r.Empty()); + auto sample = r.Front(); + Logger::WriteMessage(std::format("({}) {}: {}\n", + i, pMetricMap->at(m).narrowName, sample.value).c_str()); + Assert::IsTrue(sample.value > 1.); + } + { + constexpr auto m = PM_METRIC_CPU_FREQUENCY; + auto& r = sys.telemetryData.FindRing(m).at(0); + Assert::IsFalse(r.Empty()); + auto sample = r.Front(); + Logger::WriteMessage(std::format("({}) {}: {}\n", + i, pMetricMap->at(m).narrowName, sample.value).c_str()); + Assert::IsTrue(sample.value > 1500.); + } + } } }; } \ No newline at end of file From 47dd9bb20e1ba644b9ca3d30801e5f27086f123c Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 9 Dec 2025 08:42:30 +0900 Subject: [PATCH 031/205] basic ipc component tests --- .../Interprocess/source/OwnedDataSegment.h | 1 + IntelPresentMon/PresentMonAPI2Tests/Folders.h | 6 + .../PresentMonAPI2Tests/IpcComponentTests.cpp | 131 ++++++++++++++++++ .../PresentMonAPI2Tests.vcxproj | 1 + .../PresentMonAPI2Tests.vcxproj.filters | 3 + IntelPresentMon/SampleClient/CliOptions.h | 1 + .../SampleClient/IpcComponentServer.cpp | 116 ++++++++++++++++ .../SampleClient/IpcComponentServer.h | 2 + IntelPresentMon/SampleClient/SampleClient.cpp | 3 + .../SampleClient/SampleClient.vcxproj | 2 + .../SampleClient/SampleClient.vcxproj.filters | 2 + 11 files changed, 268 insertions(+) create mode 100644 IntelPresentMon/PresentMonAPI2Tests/IpcComponentTests.cpp create mode 100644 IntelPresentMon/SampleClient/IpcComponentServer.cpp create mode 100644 IntelPresentMon/SampleClient/IpcComponentServer.h diff --git a/IntelPresentMon/Interprocess/source/OwnedDataSegment.h b/IntelPresentMon/Interprocess/source/OwnedDataSegment.h index d8371990..b338e032 100644 --- a/IntelPresentMon/Interprocess/source/OwnedDataSegment.h +++ b/IntelPresentMon/Interprocess/source/OwnedDataSegment.h @@ -1,6 +1,7 @@ #pragma once #include "SharedMemoryTypes.h" #include "MetricCapabilities.h" +#include "DataStores.h" #include namespace pmon::ipc diff --git a/IntelPresentMon/PresentMonAPI2Tests/Folders.h b/IntelPresentMon/PresentMonAPI2Tests/Folders.h index 38f38d4c..3d513943 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/Folders.h +++ b/IntelPresentMon/PresentMonAPI2Tests/Folders.h @@ -21,4 +21,10 @@ namespace InterimBroadcasterTests { static constexpr const char* logFolder_ = "TestLogs\\InterimBroadcaster"; static constexpr const char* outFolder_ = "TestOutput\\InterimBroadcaster"; +} + +namespace IpcComponentTests +{ + static constexpr const char* logFolder_ = "TestLogs\\IpcComponent"; + static constexpr const char* outFolder_ = "TestOutput\\IpcComponent"; } \ No newline at end of file diff --git a/IntelPresentMon/PresentMonAPI2Tests/IpcComponentTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/IpcComponentTests.cpp new file mode 100644 index 00000000..f372610b --- /dev/null +++ b/IntelPresentMon/PresentMonAPI2Tests/IpcComponentTests.cpp @@ -0,0 +1,131 @@ +// Copyright (C) 2022-2025 Intel Corporation +// SPDX-License-Identifier: MIT + +#include "../CommonUtilities/win/WinAPI.h" +#include "CppUnitTest.h" +#include "TestProcess.h" +#include "Folders.h" +#include "JobManager.h" + +#include "../Interprocess/source/ViewedDataSegment.h" +#include "../Interprocess/source/DataStores.h" + +#include "../PresentMonAPI2/PresentMonAPI.h" + +#include +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace std::literals; + +namespace IpcComponentTests +{ + namespace ipc = pmon::ipc; + + // Must match the server submode constant. + static constexpr const char* kSystemSegName = "pm_ipc_system_store_test_seg"; + + static constexpr PM_METRIC kScalarMetric = PM_METRIC_CPU_FREQUENCY; + static constexpr PM_METRIC kArrayMetric = PM_METRIC_CPU_UTILIZATION; + + class TestFixture : public CommonTestFixture + { + protected: + const CommonProcessArgs& GetCommonArgs() const override + { + static CommonProcessArgs args{ + .ctrlPipe = R"(\\.\pipe\pm-ipc-sys-store-test-ctrl)", + .shmNamePrefix = "pm_ipc_sys_store_unused_prefix", + .logLevel = "debug", + .logFolder = logFolder_, + .sampleClientMode = "IpcComponentServer", + }; + return args; + } + }; + + TEST_CLASS(SystemDataStoreIpcTests) + { + TestFixture fixture_; + + public: + TEST_METHOD_INITIALIZE(Setup) + { + fixture_.Setup(); + } + + TEST_METHOD_CLEANUP(Cleanup) + { + fixture_.Cleanup(); + } + + TEST_METHOD(ReadTwoMetricsWithOneArrayMetric) + { + // Launch SampleClient in IpcSystemServer submode. + // The harness should handle the %ping gate. + auto server = fixture_.LaunchClient(); + + // Allow a brief moment for push after ping. + std::this_thread::sleep_for(25ms); + + // Open the hardcoded segment in-process (client built into the test). + ipc::ViewedDataSegment view{ kSystemSegName }; + const auto& store = view.GetStore(); + + // 1) Scalar metric expectations + { + const auto& scalarVect = store.telemetryData.FindRing(kScalarMetric); + Assert::AreEqual(1, scalarVect.size()); + + const auto& ring = scalarVect.at(0); + + const auto range = ring.GetSerialRange(); + const size_t count = range.second - range.first; + + Assert::IsTrue(count >= 12ull, L"Expected at least 12 scalar samples"); + + const auto& first = ring.At(range.first); + Assert::AreEqual(10'000ull, first.timestamp); + Assert::AreEqual(3000.0, first.value, 1e-9); + + const auto& last = ring.At(range.second - 1); + Assert::AreEqual(10'011ull, last.timestamp); + Assert::AreEqual(3000.0 + 10.0 * 11.0, last.value, 1e-9); + } + + // 2) Array metric with 2 elements expectations + { + const auto& arrVect = store.telemetryData.FindRing(kArrayMetric); + Assert::AreEqual(2, arrVect.size()); + + const auto& ring0 = arrVect.at(0); + const auto& ring1 = arrVect.at(1); + + const auto r0 = ring0.GetSerialRange(); + const auto r1 = ring1.GetSerialRange(); + + Assert::IsTrue((r0.second - r0.first) >= 12ull, L"Expected at least 12 samples in array[0]"); + Assert::IsTrue((r1.second - r1.first) >= 12ull, L"Expected at least 12 samples in array[1]"); + + const auto& first0 = ring0.At(r0.first); + const auto& first1 = ring1.At(r1.first); + + Assert::AreEqual(10'000ull, first0.timestamp); + Assert::AreEqual(10'000ull, first1.timestamp); + + Assert::AreEqual(5.0, first0.value, 1e-9); + Assert::AreEqual(50.0, first1.value, 1e-9); + + const auto& last0 = ring0.At(r0.second - 1); + const auto& last1 = ring1.At(r1.second - 1); + + Assert::AreEqual(10'011ull, last0.timestamp); + Assert::AreEqual(10'011ull, last1.timestamp); + + Assert::AreEqual(5.0 + 11.0, last0.value, 1e-9); + Assert::AreEqual(50.0 + 2.0 * 11.0, last1.value, 1e-9); + } + } + }; +} diff --git a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj index e2c61ae5..a324d17e 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj +++ b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj @@ -98,6 +98,7 @@ + diff --git a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj.filters b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj.filters index b9a1614c..4087f71c 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj.filters +++ b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj.filters @@ -42,6 +42,9 @@ Source Files + + Source Files + diff --git a/IntelPresentMon/SampleClient/CliOptions.h b/IntelPresentMon/SampleClient/CliOptions.h index 3764d5ee..8276e3ad 100644 --- a/IntelPresentMon/SampleClient/CliOptions.h +++ b/IntelPresentMon/SampleClient/CliOptions.h @@ -24,6 +24,7 @@ namespace clio MultiClient, EtlLogger, PacedPlayback, + IpcComponentServer, Count, }; diff --git a/IntelPresentMon/SampleClient/IpcComponentServer.cpp b/IntelPresentMon/SampleClient/IpcComponentServer.cpp new file mode 100644 index 00000000..a21020b4 --- /dev/null +++ b/IntelPresentMon/SampleClient/IpcComponentServer.cpp @@ -0,0 +1,116 @@ +// Copyright (C) 2022-2025 Intel Corporation +// SPDX-License-Identifier: MIT + +#include "../Interprocess/source/OwnedDataSegment.h" +#include "../Interprocess/source/DataStores.h" + +#include "../CommonUtilities/Exception.h" +#include "../CommonUtilities/log/Log.h" +#include "../PresentMonAPI2/PresentMonAPI.h" + +#include +#include +#include +#include +#include + +using namespace std::literals; + +namespace ipc = pmon::ipc; + +// Hardcoded segment name shared with the test. +static constexpr const char* kSystemSegName = "pm_ipc_system_store_test_seg"; + +// We only create two metrics: +// 1) A scalar metric with 1 element (count = 1) +// 2) An "array-like" metric with 2 elements (count = 2) +// +// The test goal is ring push/read plumbing, not capability validation. +static constexpr PM_METRIC kScalarMetric = PM_METRIC_CPU_FREQUENCY; +static constexpr PM_METRIC kArrayMetric = PM_METRIC_CPU_UTILIZATION; + +static void BuildRings_(ipc::SystemDataStore& store) +{ + constexpr size_t ringCapacity = 32; + + // Scalar metric + store.telemetryData.AddRing(kScalarMetric, ringCapacity, 1, PM_DATA_TYPE_DOUBLE); + + // Array metric with 2 elements + store.telemetryData.AddRing(kArrayMetric, ringCapacity, 2, PM_DATA_TYPE_DOUBLE); +} + +static void PushDeterministicSamples_(ipc::SystemDataStore& store) +{ + auto& scalar = store.telemetryData.FindRing(kScalarMetric); + + auto& array = store.telemetryData.FindRing(kArrayMetric); + + // Expect sizes: scalar = 1 ring, array = 2 rings + if (scalar.size() != 1 || array.size() != 2) { + throw std::logic_error("IpcSystemServer: ring vectors not sized as expected"); + } + + auto& scalarRing = scalar.at(0); + auto& arr0 = array.at(0); + auto& arr1 = array.at(1); + + constexpr size_t sampleCount = 12; + + for (size_t i = 0; i < sampleCount; ++i) { + const uint64_t ts = 10'000ull + static_cast(i); + + // Scalar sequence + const double freq = 3000.0 + 10.0 * static_cast(i); + scalarRing.Push(freq, ts); + + // Array element 0 sequence + const double util0 = 5.0 + static_cast(i); + arr0.Push(util0, ts); + + // Array element 1 sequence (offset so we can tell them apart) + const double util1 = 50.0 + static_cast(i) * 2.0; + arr1.Push(util1, ts); + } +} + +// Submode entry point. +int IpcComponentServer() +{ + // Create the shared memory segment hosting SystemDataStore. + ipc::OwnedDataSegment seg{ kSystemSegName }; + auto& store = seg.GetStore(); + + // Only build the two test rings. + BuildRings_(store); + + // Ping gate to sync "server ready" with the test harness. + std::string line; + std::getline(std::cin, line); + if (line != "%ping") { + std::cout << "%%{ping-error}%%" << std::endl; + return -1; + } + std::cout << "%%{ping-ok}%%" << std::endl; + + // Push a deterministic batch after ping. + PushDeterministicSamples_(store); + + // Command loop. + while (std::getline(std::cin, line)) { + if (line == "%quit") { + std::cout << "%%{quit-ok}%%" << std::endl; + std::this_thread::sleep_for(25ms); + return 0; + } + else if (line == "%push-more") { + PushDeterministicSamples_(store); + std::cout << "%%{push-more-ok}%%" << std::endl; + } + else { + std::cout << "%%{err-bad-command}%%" << std::endl; + } + } + + return -1; +} diff --git a/IntelPresentMon/SampleClient/IpcComponentServer.h b/IntelPresentMon/SampleClient/IpcComponentServer.h new file mode 100644 index 00000000..0d82228a --- /dev/null +++ b/IntelPresentMon/SampleClient/IpcComponentServer.h @@ -0,0 +1,2 @@ +#pragma once +int IpcComponentServer(); \ No newline at end of file diff --git a/IntelPresentMon/SampleClient/SampleClient.cpp b/IntelPresentMon/SampleClient/SampleClient.cpp index f0b5f709..bcfb9789 100644 --- a/IntelPresentMon/SampleClient/SampleClient.cpp +++ b/IntelPresentMon/SampleClient/SampleClient.cpp @@ -33,6 +33,7 @@ #include "MetricListSample.h" #include "MultiClient.h" #include "EtlLogger.h" +#include "IpcComponentServer.h" #include "PacedPlayback.h" #include "LogDemo.h" #include "DiagnosticDemo.h" @@ -378,6 +379,8 @@ int main(int argc, char* argv[]) RunPlaybackFrameQuery(); break; case clio::Mode::IntrospectAllDynamicOptions: IntrospectAllDynamicOptions(); break; + case clio::Mode::IpcComponentServer: + IpcComponentServer(); break; default: throw std::runtime_error{ "unknown sample client mode" }; } diff --git a/IntelPresentMon/SampleClient/SampleClient.vcxproj b/IntelPresentMon/SampleClient/SampleClient.vcxproj index ec564cb9..d533a812 100644 --- a/IntelPresentMon/SampleClient/SampleClient.vcxproj +++ b/IntelPresentMon/SampleClient/SampleClient.vcxproj @@ -109,6 +109,7 @@ + @@ -124,6 +125,7 @@ + diff --git a/IntelPresentMon/SampleClient/SampleClient.vcxproj.filters b/IntelPresentMon/SampleClient/SampleClient.vcxproj.filters index f99ae36f..368a51cc 100644 --- a/IntelPresentMon/SampleClient/SampleClient.vcxproj.filters +++ b/IntelPresentMon/SampleClient/SampleClient.vcxproj.filters @@ -9,6 +9,7 @@ + @@ -26,5 +27,6 @@ + \ No newline at end of file From 4dd37a3e55a5b94d40b389b98ce510db92ccceae Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 9 Dec 2025 10:21:52 +0900 Subject: [PATCH 032/205] more ipc test cases --- .../PresentMonAPI2Tests/IpcComponentTests.cpp | 303 +++++++++++++++--- 1 file changed, 263 insertions(+), 40 deletions(-) diff --git a/IntelPresentMon/PresentMonAPI2Tests/IpcComponentTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/IpcComponentTests.cpp index f372610b..1dd11fe8 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/IpcComponentTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/IpcComponentTests.cpp @@ -13,8 +13,10 @@ #include "../PresentMonAPI2/PresentMonAPI.h" #include +#include #include #include +#include using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace std::literals; @@ -29,6 +31,10 @@ namespace IpcComponentTests static constexpr PM_METRIC kScalarMetric = PM_METRIC_CPU_FREQUENCY; static constexpr PM_METRIC kArrayMetric = PM_METRIC_CPU_UTILIZATION; + // Expected test child patterns + static constexpr uint64_t kBaseTs = 10'000ull; + static constexpr size_t kSampleCount = 12; + class TestFixture : public CommonTestFixture { protected: @@ -45,7 +51,55 @@ namespace IpcComponentTests } }; - TEST_CLASS(SystemDataStoreIpcTests) + static std::string DumpRing_(const ipc::HistoryRing& ring, size_t maxSamples = 8) + { + std::ostringstream oss; + const auto [first, last] = ring.GetSerialRange(); + const size_t count = last - first; + + oss << "serial range [" << first << ", " << last << "), count=" << count << "\n"; + + const size_t n = (count < maxSamples) ? count : maxSamples; + for (size_t i = 0; i < n; ++i) { + const auto& s = ring.At(first + i); + oss << " [" << (first + i) << "] ts=" << s.timestamp << " val=" << s.value << "\n"; + } + + if (count > n) { + oss << " ...\n"; + const auto& sLast = ring.At(last - 1); + oss << " [" << (last - 1) << "] ts=" << sLast.timestamp << " val=" << sLast.value << "\n"; + } + + return oss.str(); + } + + static void LogRing_(const char* label, const ipc::HistoryRing& ring) + { + std::ostringstream oss; + oss << label << "\n" << DumpRing_(ring); + Logger::WriteMessage(oss.str().c_str()); + } + + static double ExpectedScalarValue_(uint64_t timestamp) + { + const size_t i = static_cast(timestamp - kBaseTs); + return 3000.0 + 10.0 * static_cast(i); + } + + static double ExpectedArray0Value_(uint64_t timestamp) + { + const size_t i = static_cast(timestamp - kBaseTs); + return 5.0 + static_cast(i); + } + + static double ExpectedArray1Value_(uint64_t timestamp) + { + const size_t i = static_cast(timestamp - kBaseTs); + return 50.0 + 2.0 * static_cast(i); + } + + TEST_CLASS(SystemDataStoreHistoryRingInterfaceTests) { TestFixture fixture_; @@ -60,72 +114,241 @@ namespace IpcComponentTests fixture_.Cleanup(); } - TEST_METHOD(ReadTwoMetricsWithOneArrayMetric) + TEST_METHOD(RingsArePresentAndSized) + { + auto server = fixture_.LaunchClient(); + std::this_thread::sleep_for(25ms); + + ipc::ViewedDataSegment view{ kSystemSegName }; + const auto& store = view.GetStore(); + + const auto& scalarVect = store.telemetryData.FindRing(kScalarMetric); + const auto& arrayVect = store.telemetryData.FindRing(kArrayMetric); + + Logger::WriteMessage("Checking ring vector sizes...\n"); + + Assert::AreEqual(1, scalarVect.size(), L"Scalar metric should have 1 ring"); + Assert::AreEqual(2, arrayVect.size(), L"Array metric should have 2 rings"); + } + + TEST_METHOD(EmptyRangeAndFrontWorkForScalar) { - // Launch SampleClient in IpcSystemServer submode. - // The harness should handle the %ping gate. auto server = fixture_.LaunchClient(); + std::this_thread::sleep_for(25ms); + + ipc::ViewedDataSegment view{ kSystemSegName }; + const auto& store = view.GetStore(); + + const auto& ring = store.telemetryData.FindRing(kScalarMetric).at(0); + + Logger::WriteMessage("Validating Empty/GetSerialRange/Front for scalar ring\n"); + LogRing_("Scalar ring dump:", ring); + + Assert::IsFalse(ring.Empty(), L"Ring should not be empty after server push"); - // Allow a brief moment for push after ping. + const auto [first, last] = ring.GetSerialRange(); + Assert::IsTrue(last >= first, L"Serial range should be valid"); + Assert::IsTrue((last - first) >= kSampleCount, L"Expected at least 12 samples"); + + const auto& front = ring.Front(); + const auto& atFirst = ring.At(first); + + Assert::AreEqual(front.timestamp, atFirst.timestamp); + Assert::AreEqual(front.value, atFirst.value, 1e-9); + + Assert::AreEqual(kBaseTs, front.timestamp); + Assert::AreEqual(3000.0, front.value, 1e-9); + } + + TEST_METHOD(AtReadsExpectedValuesForArrayElements) + { + auto server = fixture_.LaunchClient(); std::this_thread::sleep_for(25ms); - // Open the hardcoded segment in-process (client built into the test). ipc::ViewedDataSegment view{ kSystemSegName }; const auto& store = view.GetStore(); - // 1) Scalar metric expectations + const auto& arrVect = store.telemetryData.FindRing(kArrayMetric); + + const auto& ring0 = arrVect.at(0); + const auto& ring1 = arrVect.at(1); + + Logger::WriteMessage("Validating At() value mapping for array rings\n"); + LogRing_("Array ring[0] dump:", ring0); + LogRing_("Array ring[1] dump:", ring1); + + const auto [f0, l0] = ring0.GetSerialRange(); + const auto [f1, l1] = ring1.GetSerialRange(); + + Assert::IsTrue((l0 - f0) >= kSampleCount); + Assert::IsTrue((l1 - f1) >= kSampleCount); + + // Check a few specific timestamps + for (uint64_t ts : { kBaseTs, kBaseTs + 5, kBaseTs + 11 }) { + const size_t i = static_cast(ts - kBaseTs); + + const auto& s0 = ring0.At(f0 + i); + const auto& s1 = ring1.At(f1 + i); + + Assert::AreEqual(ts, s0.timestamp); + Assert::AreEqual(ts, s1.timestamp); + + Assert::AreEqual(ExpectedArray0Value_(ts), s0.value, 1e-9); + Assert::AreEqual(ExpectedArray1Value_(ts), s1.value, 1e-9); + } + } + + TEST_METHOD(LowerBoundSerialEdgeAndExactCases) + { + auto server = fixture_.LaunchClient(); + std::this_thread::sleep_for(25ms); + + ipc::ViewedDataSegment view{ kSystemSegName }; + const auto& store = view.GetStore(); + + const auto& ring = store.telemetryData.FindRing(kScalarMetric).at(0); + + Logger::WriteMessage("Validating LowerBoundSerial cases\n"); + + const auto [first, last] = ring.GetSerialRange(); + + // Before first timestamp -> should return first + { + const size_t s = ring.LowerBoundSerial(kBaseTs - 1); + Assert::AreEqual(first, s); + } + + // Exact timestamp match + { + const uint64_t ts = kBaseTs + 5; + const size_t s = ring.LowerBoundSerial(ts); + const auto& sample = ring.At(s); + + Assert::AreEqual(ts, sample.timestamp); + Assert::AreEqual(ExpectedScalarValue_(ts), sample.value, 1e-9); + } + + // After last timestamp -> should return last (one past end) { - const auto& scalarVect = store.telemetryData.FindRing(kScalarMetric); - Assert::AreEqual(1, scalarVect.size()); + const size_t s = ring.LowerBoundSerial(kBaseTs + static_cast(kSampleCount)); + Assert::AreEqual(last, s); + } + } - const auto& ring = scalarVect.at(0); + TEST_METHOD(UpperBoundSerialEdgeAndExactCases) + { + auto server = fixture_.LaunchClient(); + std::this_thread::sleep_for(25ms); + + ipc::ViewedDataSegment view{ kSystemSegName }; + const auto& store = view.GetStore(); - const auto range = ring.GetSerialRange(); - const size_t count = range.second - range.first; + const auto& ring = store.telemetryData.FindRing(kScalarMetric).at(0); - Assert::IsTrue(count >= 12ull, L"Expected at least 12 scalar samples"); + Logger::WriteMessage("Validating UpperBoundSerial cases\n"); - const auto& first = ring.At(range.first); - Assert::AreEqual(10'000ull, first.timestamp); - Assert::AreEqual(3000.0, first.value, 1e-9); + const auto [first, last] = ring.GetSerialRange(); - const auto& last = ring.At(range.second - 1); - Assert::AreEqual(10'011ull, last.timestamp); - Assert::AreEqual(3000.0 + 10.0 * 11.0, last.value, 1e-9); + // Before first timestamp -> should return first + { + const size_t s = ring.UpperBoundSerial(kBaseTs - 1); + Assert::AreEqual(first, s); } - // 2) Array metric with 2 elements expectations + // Upper bound of first sample timestamp -> should point to second sample { - const auto& arrVect = store.telemetryData.FindRing(kArrayMetric); - Assert::AreEqual(2, arrVect.size()); + const size_t s = ring.UpperBoundSerial(kBaseTs); + Assert::IsTrue(s > first); + const auto& sample = ring.At(s); + Assert::AreEqual(kBaseTs + 1, sample.timestamp); + } - const auto& ring0 = arrVect.at(0); - const auto& ring1 = arrVect.at(1); + // Upper bound of last sample timestamp -> should return last + { + const uint64_t lastTs = kBaseTs + static_cast(kSampleCount - 1); + const size_t s = ring.UpperBoundSerial(lastTs); + Assert::AreEqual(last, s); + } + } - const auto r0 = ring0.GetSerialRange(); - const auto r1 = ring1.GetSerialRange(); + TEST_METHOD(NearestSerialClampsAndExact) + { + auto server = fixture_.LaunchClient(); + std::this_thread::sleep_for(25ms); - Assert::IsTrue((r0.second - r0.first) >= 12ull, L"Expected at least 12 samples in array[0]"); - Assert::IsTrue((r1.second - r1.first) >= 12ull, L"Expected at least 12 samples in array[1]"); + ipc::ViewedDataSegment view{ kSystemSegName }; + const auto& store = view.GetStore(); - const auto& first0 = ring0.At(r0.first); - const auto& first1 = ring1.At(r1.first); + const auto& ring = store.telemetryData.FindRing(kScalarMetric).at(0); - Assert::AreEqual(10'000ull, first0.timestamp); - Assert::AreEqual(10'000ull, first1.timestamp); + Logger::WriteMessage("Validating NearestSerial cases\n"); - Assert::AreEqual(5.0, first0.value, 1e-9); - Assert::AreEqual(50.0, first1.value, 1e-9); + const auto [first, last] = ring.GetSerialRange(); + + // Before first -> clamp to first + { + const size_t s = ring.NearestSerial(kBaseTs - 500); + Assert::AreEqual(first, s); + Assert::AreEqual(kBaseTs, ring.At(s).timestamp); + } - const auto& last0 = ring0.At(r0.second - 1); - const auto& last1 = ring1.At(r1.second - 1); + // After last -> clamp to last-1 + { + const size_t s = ring.NearestSerial(kBaseTs + 500); + Assert::AreEqual(last - 1, s); + Assert::AreEqual(kBaseTs + static_cast(kSampleCount - 1), + ring.At(s).timestamp); + } - Assert::AreEqual(10'011ull, last0.timestamp); - Assert::AreEqual(10'011ull, last1.timestamp); + // Exact timestamp -> should return that sample + { + const uint64_t ts = kBaseTs + 7; + const size_t s = ring.NearestSerial(ts); + const auto& sample = ring.At(s); - Assert::AreEqual(5.0 + 11.0, last0.value, 1e-9); - Assert::AreEqual(50.0 + 2.0 * 11.0, last1.value, 1e-9); + Assert::AreEqual(ts, sample.timestamp); + Assert::AreEqual(ExpectedScalarValue_(ts), sample.value, 1e-9); } } + + TEST_METHOD(ForEachInTimestampRangeVisitsExpectedSamples) + { + auto server = fixture_.LaunchClient(); + std::this_thread::sleep_for(25ms); + + ipc::ViewedDataSegment view{ kSystemSegName }; + const auto& store = view.GetStore(); + + const auto& ring = store.telemetryData.FindRing(kScalarMetric).at(0); + + Logger::WriteMessage("Validating ForEachInTimestampRange\n"); + LogRing_("Scalar ring dump:", ring); + + const uint64_t start = kBaseTs + 3; + const uint64_t end = kBaseTs + 6; + + size_t visited = 0; + double sum = 0.0; + + const size_t count = ring.ForEachInTimestampRange(start, end, [&](const auto& s) { + ++visited; + sum += s.value; + }); + + // Timestamps are contiguous and inclusive + // Expected: 10003, 10004, 10005, 10006 -> 4 samples + Assert::AreEqual(4, count); + Assert::AreEqual(4, visited); + + const double expectedSum = + ExpectedScalarValue_(start) + + ExpectedScalarValue_(start + 1) + + ExpectedScalarValue_(start + 2) + + ExpectedScalarValue_(end); + + Logger::WriteMessage(std::format("ForEach visited={}, sum={}\n", visited, sum).c_str()); + + Assert::AreEqual(expectedSum, sum, 1e-9); + } }; } From aa8d323c46989aeb5830cb2b123f1918465bf610 Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 9 Dec 2025 11:30:18 +0900 Subject: [PATCH 033/205] hist shm newest --- .../Interprocess/source/HistoryRing.h | 4 +- .../InterimBroadcasterTests.cpp | 262 +++++++++--------- .../PresentMonAPI2Tests/IpcComponentTests.cpp | 32 ++- 3 files changed, 158 insertions(+), 140 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/HistoryRing.h b/IntelPresentMon/Interprocess/source/HistoryRing.h index c55c2aa4..42b954a2 100644 --- a/IntelPresentMon/Interprocess/source/HistoryRing.h +++ b/IntelPresentMon/Interprocess/source/HistoryRing.h @@ -28,10 +28,10 @@ namespace pmon::ipc { samples_.Push({ value, timestamp }); } - const Sample& Front() const + const Sample& Newest() const { auto&&[first, last] = samples_.GetSerialRange(); - return At(first); + return At(last - 1); } const Sample& At(size_t serial) const { diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 9c96b373..10de64c0 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -14,6 +14,8 @@ #include "../PresentMonAPIWrapperCommon/EnumMap.h" #include "../PresentMonService/AllActions.h" +#include +#include using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace vi = std::views; @@ -23,136 +25,136 @@ using namespace pmon; namespace InterimBroadcasterTests { - class TestFixture : public CommonTestFixture - { - public: - const CommonProcessArgs& GetCommonArgs() const override - { - static CommonProcessArgs args{ - .ctrlPipe = R"(\\.\pipe\pm-intbroad-test-ctrl)", - .shmNamePrefix = "pm_intborad_test_intro", - .logLevel = "debug", - .logFolder = logFolder_, - .sampleClientMode = "NONE", - }; - return args; - } - }; + class TestFixture : public CommonTestFixture + { + public: + const CommonProcessArgs& GetCommonArgs() const override + { + static CommonProcessArgs args{ + .ctrlPipe = R"(\\.\pipe\pm-intbroad-test-ctrl)", + .shmNamePrefix = "pm_intborad_test_intro", + .logLevel = "debug", + .logFolder = logFolder_, + .sampleClientMode = "NONE", + }; + return args; + } + }; - TEST_CLASS(CommonFixtureTests) - { - TestFixture fixture_; + TEST_CLASS(CommonFixtureTests) + { + TestFixture fixture_; - public: - TEST_METHOD_INITIALIZE(Setup) - { - fixture_.Setup(); - } - TEST_METHOD_CLEANUP(Cleanup) - { - fixture_.Cleanup(); - } - // verify service lifetime and status command functionality - TEST_METHOD(ServiceStatusTest) - { - // verify initial status - const auto status = fixture_.service->QueryStatus(); - Assert::AreEqual(0ull, status.nsmStreamedPids.size()); - Assert::AreEqual(16u, status.telemetryPeriodMs); - Assert::IsTrue((bool)status.etwFlushPeriodMs); - Assert::AreEqual(1000u, *status.etwFlushPeriodMs); - } - // verify action system can connect - TEST_METHOD(ActionConnect) - { - mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; - Assert::IsFalse(client.GetShmPrefix().empty()); - // there is a bit of a race condition on creating a service, immediately connecting - // and then immediately terminating it via the test control module - // not a concern for normal operation and is entirely synthetic; don't waste - // effort on trying to rework this, just add a little wait for odd tests like this - std::this_thread::sleep_for(150ms); - } - // verify comms work with introspection (no wrapper) - TEST_METHOD(IntrospectionConnect) - { - mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; - auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); - auto pIntro = pComms->GetIntrospectionRoot(); - Assert::AreEqual(2ull, pIntro->pDevices->size); - auto pDevice = static_cast(pIntro->pDevices->pData[1]); - Assert::AreEqual("NVIDIA GeForce RTX 2080 Ti", pDevice->pName->pData); - } - }; + public: + TEST_METHOD_INITIALIZE(Setup) + { + fixture_.Setup(); + } + TEST_METHOD_CLEANUP(Cleanup) + { + fixture_.Cleanup(); + } + // verify service lifetime and status command functionality + TEST_METHOD(ServiceStatusTest) + { + // verify initial status + const auto status = fixture_.service->QueryStatus(); + Assert::AreEqual(0ull, status.nsmStreamedPids.size()); + Assert::AreEqual(16u, status.telemetryPeriodMs); + Assert::IsTrue((bool)status.etwFlushPeriodMs); + Assert::AreEqual(1000u, *status.etwFlushPeriodMs); + } + // verify action system can connect + TEST_METHOD(ActionConnect) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + Assert::IsFalse(client.GetShmPrefix().empty()); + // there is a bit of a race condition on creating a service, immediately connecting + // and then immediately terminating it via the test control module + // not a concern for normal operation and is entirely synthetic; don't waste + // effort on trying to rework this, just add a little wait for odd tests like this + std::this_thread::sleep_for(150ms); + } + // verify comms work with introspection (no wrapper) + TEST_METHOD(IntrospectionConnect) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + auto pIntro = pComms->GetIntrospectionRoot(); + Assert::AreEqual(2ull, pIntro->pDevices->size); + auto pDevice = static_cast(pIntro->pDevices->pData[1]); + Assert::AreEqual("NVIDIA GeForce RTX 2080 Ti", pDevice->pName->pData); + } + }; - TEST_CLASS(CpuStoreTests) - { - TestFixture fixture_; - public: - TEST_METHOD_INITIALIZE(Setup) - { - fixture_.Setup(); - } - TEST_METHOD_CLEANUP(Cleanup) - { - fixture_.Cleanup(); - } - // static store - TEST_METHOD(StaticData) - { - mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; - auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); - // get the store containing system-wide telemetry (cpu etc.) - auto& sys = pComms->GetSystemDataStore(); - Assert::AreEqual((int)PM_DEVICE_VENDOR_AMD, (int)sys.statics.cpuVendor); - Assert::AreEqual("AMD Ryzen 7 5800X 8-Core Processor", sys.statics.cpuName.c_str()); - Assert::AreEqual(0., sys.statics.cpuPowerLimit); - } - // polled store - TEST_METHOD(PolledData) - { - mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; - auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); - // acquire introspection with enhanced wrapper interface - auto pIntro = pComms->GetIntrospectionRoot(); - pmapi::intro::Root intro{ pIntro, [](auto* p){delete p;} }; - pmapi::EnumMap::Refresh(intro); - auto pMetricMap = pmapi::EnumMap::GetKeyMap(PM_ENUM_METRIC); - // as a stopgap we target a process in order to trigger service-side telemetry collection - // TODO: remove this when we enable service-side query awareness of connected clients - auto pres = fixture_.LaunchPresenter(); - client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pres.GetId() }); - // get the store containing system-wide telemetry (cpu etc.) - auto& sys = pComms->GetSystemDataStore(); - for (auto&&[met, r] : sys.telemetryData.Rings()) { - Logger::WriteMessage(std::format(" TeleRing@{}\n", pMetricMap->at(met).narrowName).c_str()); - // TODO: understand the disconnect between CPU Core Utility showing up here - // and now showing up in the UI - } - Assert::AreEqual(3ull, (size_t)rn::distance(sys.telemetryData.Rings())); - std::this_thread::sleep_for(1000ms); - // check that we have data for frequency and utilization - for (int i = 0; i < 50; i++) { - std::this_thread::sleep_for(100ms); - { - constexpr auto m = PM_METRIC_CPU_UTILIZATION; - auto& r = sys.telemetryData.FindRing(m).at(0); - Assert::IsFalse(r.Empty()); - auto sample = r.Front(); - Logger::WriteMessage(std::format("({}) {}: {}\n", - i, pMetricMap->at(m).narrowName, sample.value).c_str()); - Assert::IsTrue(sample.value > 1.); - } - { - constexpr auto m = PM_METRIC_CPU_FREQUENCY; - auto& r = sys.telemetryData.FindRing(m).at(0); - Assert::IsFalse(r.Empty()); - auto sample = r.Front(); - Logger::WriteMessage(std::format("({}) {}: {}\n", - i, pMetricMap->at(m).narrowName, sample.value).c_str()); - Assert::IsTrue(sample.value > 1500.); - } - } - } - }; -} \ No newline at end of file + TEST_CLASS(CpuStoreTests) + { + TestFixture fixture_; + public: + TEST_METHOD_INITIALIZE(Setup) + { + fixture_.Setup(); + } + TEST_METHOD_CLEANUP(Cleanup) + { + fixture_.Cleanup(); + } + // static store + TEST_METHOD(StaticData) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + // get the store containing system-wide telemetry (cpu etc.) + auto& sys = pComms->GetSystemDataStore(); + Assert::AreEqual((int)PM_DEVICE_VENDOR_AMD, (int)sys.statics.cpuVendor); + Assert::AreEqual("AMD Ryzen 7 5800X 8-Core Processor", sys.statics.cpuName.c_str()); + Assert::AreEqual(0., sys.statics.cpuPowerLimit); + } + // polled store + TEST_METHOD(PolledData) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + // acquire introspection with enhanced wrapper interface + auto pIntro = pComms->GetIntrospectionRoot(); + pmapi::intro::Root intro{ pIntro, [](auto* p) {delete p; } }; + pmapi::EnumMap::Refresh(intro); + auto pMetricMap = pmapi::EnumMap::GetKeyMap(PM_ENUM_METRIC); + // as a stopgap we target a process in order to trigger service-side telemetry collection + // TODO: remove this when we enable service-side query awareness of connected clients + auto pres = fixture_.LaunchPresenter(); + client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pres.GetId() }); + // get the store containing system-wide telemetry (cpu etc.) + auto& sys = pComms->GetSystemDataStore(); + for (auto&& [met, r] : sys.telemetryData.Rings()) { + Logger::WriteMessage(std::format(" TeleRing@{}\n", pMetricMap->at(met).narrowName).c_str()); + // TODO: understand the disconnect between CPU Core Utility showing up here + // and now showing up in the UI + } + Assert::AreEqual(3ull, (size_t)rn::distance(sys.telemetryData.Rings())); + std::this_thread::sleep_for(1000ms); + // check that we have data for frequency and utilization + for (int i = 0; i < 50; i++) { + std::this_thread::sleep_for(100ms); + { + constexpr auto m = PM_METRIC_CPU_UTILIZATION; + auto& r = sys.telemetryData.FindRing(m).at(0); + Assert::IsFalse(r.Empty()); + auto sample = r.Newest(); + Logger::WriteMessage(std::format("({}) {}: {}\n", + i, pMetricMap->at(m).narrowName, sample.value).c_str()); + Assert::IsTrue(sample.value > 1.); + } + { + constexpr auto m = PM_METRIC_CPU_FREQUENCY; + auto& r = sys.telemetryData.FindRing(m).at(0); + Assert::IsFalse(r.Empty()); + auto sample = r.Newest(); + Logger::WriteMessage(std::format("({}) {}: {}\n", + i, pMetricMap->at(m).narrowName, sample.value).c_str()); + Assert::IsTrue(sample.value > 1500.); + } + } + } + }; +} diff --git a/IntelPresentMon/PresentMonAPI2Tests/IpcComponentTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/IpcComponentTests.cpp index 1dd11fe8..15a7c169 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/IpcComponentTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/IpcComponentTests.cpp @@ -13,6 +13,7 @@ #include "../PresentMonAPI2/PresentMonAPI.h" #include +#include #include #include #include @@ -59,6 +60,10 @@ namespace IpcComponentTests oss << "serial range [" << first << ", " << last << "), count=" << count << "\n"; + if (count == 0) { + return oss.str(); + } + const size_t n = (count < maxSamples) ? count : maxSamples; for (size_t i = 0; i < n; ++i) { const auto& s = ring.At(first + i); @@ -71,6 +76,15 @@ namespace IpcComponentTests oss << " [" << (last - 1) << "] ts=" << sLast.timestamp << " val=" << sLast.value << "\n"; } + // Try to include Newest() summary for debugging + try { + const auto& newest = ring.Newest(); + oss << "newest: ts=" << newest.timestamp << " val=" << newest.value << "\n"; + } + catch (...) { + // If Newest() throws on empty in some impls, ignore here. + } + return oss.str(); } @@ -131,7 +145,7 @@ namespace IpcComponentTests Assert::AreEqual(2, arrayVect.size(), L"Array metric should have 2 rings"); } - TEST_METHOD(EmptyRangeAndFrontWorkForScalar) + TEST_METHOD(EmptyRangeAndNewestWorkForScalar) { auto server = fixture_.LaunchClient(); std::this_thread::sleep_for(25ms); @@ -141,7 +155,7 @@ namespace IpcComponentTests const auto& ring = store.telemetryData.FindRing(kScalarMetric).at(0); - Logger::WriteMessage("Validating Empty/GetSerialRange/Front for scalar ring\n"); + Logger::WriteMessage("Validating Empty/GetSerialRange/Newest for scalar ring\n"); LogRing_("Scalar ring dump:", ring); Assert::IsFalse(ring.Empty(), L"Ring should not be empty after server push"); @@ -150,14 +164,16 @@ namespace IpcComponentTests Assert::IsTrue(last >= first, L"Serial range should be valid"); Assert::IsTrue((last - first) >= kSampleCount, L"Expected at least 12 samples"); - const auto& front = ring.Front(); - const auto& atFirst = ring.At(first); + const uint64_t expectedNewestTs = kBaseTs + static_cast(kSampleCount - 1); + + const auto& newest = ring.Newest(); + const auto& atLast = ring.At(last - 1); - Assert::AreEqual(front.timestamp, atFirst.timestamp); - Assert::AreEqual(front.value, atFirst.value, 1e-9); + Assert::AreEqual(atLast.timestamp, newest.timestamp); + Assert::AreEqual(atLast.value, newest.value, 1e-9); - Assert::AreEqual(kBaseTs, front.timestamp); - Assert::AreEqual(3000.0, front.value, 1e-9); + Assert::AreEqual(expectedNewestTs, newest.timestamp); + Assert::AreEqual(ExpectedScalarValue_(expectedNewestTs), newest.value, 1e-9); } TEST_METHOD(AtReadsExpectedValuesForArrayElements) From 48b5eee16a236f433845e41a12f89a511fe5eff2 Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 9 Dec 2025 13:14:01 +0900 Subject: [PATCH 034/205] fixing old history structure and better sys telemetry tests --- IntelPresentMon/ControlLib/WmiCpu.cpp | 2 +- .../InterimBroadcasterTests.cpp | 59 ++++++++++++++++++- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/IntelPresentMon/ControlLib/WmiCpu.cpp b/IntelPresentMon/ControlLib/WmiCpu.cpp index d783b9fb..c1d478de 100644 --- a/IntelPresentMon/ControlLib/WmiCpu.cpp +++ b/IntelPresentMon/ControlLib/WmiCpu.cpp @@ -75,7 +75,7 @@ WmiCpu::WmiCpu() { const CpuTelemetryInfo& WmiCpu::GetNewest() const noexcept { - return *history_.begin(); + return *std::prev(history_.end()); } bool WmiCpu::Sample() noexcept { diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 10de64c0..22bcece7 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -25,6 +25,42 @@ using namespace pmon; namespace InterimBroadcasterTests { + static std::string DumpRing_(const ipc::HistoryRing& ring, size_t maxSamples = 8) + { + std::ostringstream oss; + const auto [first, last] = ring.GetSerialRange(); + const size_t count = last - first; + + oss << "serial range [" << first << ", " << last << "), count=" << count << "\n"; + + if (count == 0) { + return oss.str(); + } + + const size_t n = (count < maxSamples) ? count : maxSamples; + for (size_t i = 0; i < n; ++i) { + const auto& s = ring.At(first + i); + oss << " [" << (first + i) << "] ts=" << s.timestamp << " val=" << s.value << "\n"; + } + + if (count > n) { + oss << " ...\n"; + const auto& sLast = ring.At(last - 1); + oss << " [" << (last - 1) << "] ts=" << sLast.timestamp << " val=" << sLast.value << "\n"; + } + + // Try to include Newest() summary for debugging + try { + const auto& newest = ring.Newest(); + oss << "newest: ts=" << newest.timestamp << " val=" << newest.value << "\n"; + } + catch (...) { + // If Newest() throws on empty in some impls, ignore here. + } + + return oss.str(); + } + class TestFixture : public CommonTestFixture { public: @@ -120,6 +156,8 @@ namespace InterimBroadcasterTests pmapi::intro::Root intro{ pIntro, [](auto* p) {delete p; } }; pmapi::EnumMap::Refresh(intro); auto pMetricMap = pmapi::EnumMap::GetKeyMap(PM_ENUM_METRIC); + // set telemetry period so we have a known baseline + client.DispatchSync(svc::acts::SetTelemetryPeriod::Params{ .telemetrySamplePeriodMs = 100 }); // as a stopgap we target a process in order to trigger service-side telemetry collection // TODO: remove this when we enable service-side query awareness of connected clients auto pres = fixture_.LaunchPresenter(); @@ -132,15 +170,21 @@ namespace InterimBroadcasterTests // and now showing up in the UI } Assert::AreEqual(3ull, (size_t)rn::distance(sys.telemetryData.Rings())); - std::this_thread::sleep_for(1000ms); + std::this_thread::sleep_for(500ms); // check that we have data for frequency and utilization - for (int i = 0; i < 50; i++) { - std::this_thread::sleep_for(100ms); + std::vector::Sample> utilizSamples; + std::vector::Sample> freqSamples; + for (int i = 0; i < 10; i++) { + std::this_thread::sleep_for(250ms); { constexpr auto m = PM_METRIC_CPU_UTILIZATION; auto& r = sys.telemetryData.FindRing(m).at(0); Assert::IsFalse(r.Empty()); + if (i == 0 || i == 9) { + Logger::WriteMessage(DumpRing_(r).c_str()); + } auto sample = r.Newest(); + utilizSamples.push_back(sample); Logger::WriteMessage(std::format("({}) {}: {}\n", i, pMetricMap->at(m).narrowName, sample.value).c_str()); Assert::IsTrue(sample.value > 1.); @@ -149,12 +193,21 @@ namespace InterimBroadcasterTests constexpr auto m = PM_METRIC_CPU_FREQUENCY; auto& r = sys.telemetryData.FindRing(m).at(0); Assert::IsFalse(r.Empty()); + if (i == 0 || i == 9) { + Logger::WriteMessage(DumpRing_(r).c_str()); + } auto sample = r.Newest(); + freqSamples.push_back(sample); Logger::WriteMessage(std::format("({}) {}: {}\n", i, pMetricMap->at(m).narrowName, sample.value).c_str()); Assert::IsTrue(sample.value > 1500.); } } + // make sure samples actually change over time + Assert::AreNotEqual(utilizSamples.front().timestamp, utilizSamples.back().timestamp); + Assert::AreNotEqual(utilizSamples.front().value, utilizSamples.back().value); + Assert::AreNotEqual(freqSamples.front().timestamp, freqSamples.back().timestamp); + Assert::AreNotEqual(freqSamples.front().value, freqSamples.back().value); } }; } From 56c898fec982dfcb7797a9ff621e5fb7bb271f4c Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 9 Dec 2025 17:55:00 +0900 Subject: [PATCH 035/205] populate and test gpu polled telemetry --- .../ControlLib/AmdPowerTelemetryAdapter.cpp | 6 + .../ControlLib/AmdPowerTelemetryAdapter.h | 1 + .../ControlLib/IntelPowerTelemetryAdapter.cpp | 6 + .../ControlLib/IntelPowerTelemetryAdapter.h | 1 + .../NvidiaPowerTelemetryAdapter.cpp | 7 + .../ControlLib/NvidiaPowerTelemetryAdapter.h | 1 + .../ControlLib/PowerTelemetryAdapter.h | 1 + .../InterimBroadcasterTests.cpp | 90 +++++++ .../PresentMonService/PMMainThread.cpp | 224 +++++++++++++++++- 9 files changed, 336 insertions(+), 1 deletion(-) diff --git a/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.cpp b/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.cpp index 6f1e32aa..034c0a95 100644 --- a/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.cpp +++ b/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.cpp @@ -53,6 +53,12 @@ bool AmdPowerTelemetryAdapter::GetVideoMemoryInfo(uint64_t& gpu_mem_size, uint64 return success; } + +const PresentMonPowerTelemetryInfo& AmdPowerTelemetryAdapter::GetNewest() const noexcept +{ + return *std::prev(history_.end()); +} + bool AmdPowerTelemetryAdapter::GetSustainedPowerLimit(double& sustainedPowerLimit) const noexcept { sustainedPowerLimit = 0.f; if (overdrive_version_ == 5) { diff --git a/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.h b/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.h index bd63d95a..ff9641bb 100644 --- a/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.h +++ b/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.h @@ -25,6 +25,7 @@ class AmdPowerTelemetryAdapter : public PowerTelemetryAdapter { bool Sample() noexcept override; std::optional GetClosest( uint64_t qpc) const noexcept override; + const PresentMonPowerTelemetryInfo& GetNewest() const noexcept override; PM_DEVICE_VENDOR GetVendor() const noexcept override; std::string GetName() const noexcept override; uint64_t GetDedicatedVideoMemory() const noexcept override; diff --git a/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.cpp b/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.cpp index 4e8c94a2..80314bdd 100644 --- a/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.cpp +++ b/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.cpp @@ -913,6 +913,12 @@ namespace pwr::intel return CTL_RESULT_SUCCESS; } + + const PresentMonPowerTelemetryInfo& IntelPowerTelemetryAdapter::GetNewest() const noexcept + { + return *std::prev(history.end()); + } + ctl_result_t IntelPowerTelemetryAdapter::GetPowerTelemetryItemUsage( const ctl_oc_telemetry_item_t& current_telemetry_item, const ctl_oc_telemetry_item_t& previous_telemetry_item, diff --git a/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.h b/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.h index e84afde5..e61cf22b 100644 --- a/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.h +++ b/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.h @@ -19,6 +19,7 @@ namespace pwr::intel IntelPowerTelemetryAdapter(ctl_device_adapter_handle_t handle); bool Sample() noexcept override; std::optional GetClosest(uint64_t qpc) const noexcept override; + const PresentMonPowerTelemetryInfo& GetNewest() const noexcept override; PM_DEVICE_VENDOR GetVendor() const noexcept override; std::string GetName() const noexcept override; uint64_t GetDedicatedVideoMemory() const noexcept override; diff --git a/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.cpp b/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.cpp index a9b4c082..830fe025 100644 --- a/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.cpp +++ b/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.cpp @@ -27,6 +27,13 @@ namespace pwr::nv } } + + + const PresentMonPowerTelemetryInfo& NvidiaPowerTelemetryAdapter::GetNewest() const noexcept + { + return *std::prev(history.end()); + } + uint64_t NvidiaPowerTelemetryAdapter::GetDedicatedVideoMemory() const noexcept { uint64_t video_mem_size = 0; nvmlMemory_t mem{}; diff --git a/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.h b/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.h index 060c22c9..41ab3057 100644 --- a/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.h +++ b/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.h @@ -20,6 +20,7 @@ namespace pwr::nv std::optional hGpuNvml); bool Sample() noexcept override; std::optional GetClosest(uint64_t qpc) const noexcept override; + const PresentMonPowerTelemetryInfo& GetNewest() const noexcept override; PM_DEVICE_VENDOR GetVendor() const noexcept override; std::string GetName() const noexcept override; uint64_t GetDedicatedVideoMemory() const noexcept override; diff --git a/IntelPresentMon/ControlLib/PowerTelemetryAdapter.h b/IntelPresentMon/ControlLib/PowerTelemetryAdapter.h index 07d02093..3893b404 100644 --- a/IntelPresentMon/ControlLib/PowerTelemetryAdapter.h +++ b/IntelPresentMon/ControlLib/PowerTelemetryAdapter.h @@ -20,6 +20,7 @@ namespace pwr virtual ~PowerTelemetryAdapter() = default; virtual bool Sample() noexcept = 0; virtual std::optional GetClosest(uint64_t qpc) const noexcept = 0; + virtual const PresentMonPowerTelemetryInfo& GetNewest() const noexcept = 0; virtual PM_DEVICE_VENDOR GetVendor() const noexcept = 0; virtual std::string GetName() const noexcept = 0; virtual uint64_t GetDedicatedVideoMemory() const noexcept = 0; diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 22bcece7..0e3be067 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -210,4 +210,94 @@ namespace InterimBroadcasterTests Assert::AreNotEqual(freqSamples.front().value, freqSamples.back().value); } }; + + TEST_CLASS(GpuStoreTests) + { + TestFixture fixture_; + public: + TEST_METHOD_INITIALIZE(Setup) + { + fixture_.Setup(); + } + TEST_METHOD_CLEANUP(Cleanup) + { + fixture_.Cleanup(); + } + // polled store + TEST_METHOD(PolledData) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + + // acquire introspection with enhanced wrapper interface + auto pIntro = pComms->GetIntrospectionRoot(); + pmapi::intro::Root intro{ pIntro, [](auto* p) { delete p; } }; + pmapi::EnumMap::Refresh(intro); + auto pMetricMap = pmapi::EnumMap::GetKeyMap(PM_ENUM_METRIC); + + // set telemetry period so we have a known baseline + client.DispatchSync(svc::acts::SetTelemetryPeriod::Params{ .telemetrySamplePeriodMs = 100 }); + + // as a stopgap we target a process in order to trigger service-side telemetry collection + // TODO: remove this when we enable service-side query awareness of connected clients + auto pres = fixture_.LaunchPresenter(); + client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pres.GetId() }); + + // get the store containing adapter telemetry + auto& gpu = pComms->GetGpuDataStore(1); + + // allow a short warmup + std::this_thread::sleep_for(500ms); + + std::vector::Sample> tempSamples; + std::vector::Sample> powerSamples; + + for (int i = 0; i < 10; i++) { + std::this_thread::sleep_for(250ms); + + { + constexpr auto m = PM_METRIC_GPU_TEMPERATURE; + auto& r = gpu.telemetryData.FindRing(m).at(0); + Assert::IsFalse(r.Empty()); + + if (i == 0 || i == 9) { + Logger::WriteMessage(DumpRing_(r).c_str()); + } + + auto sample = r.Newest(); + tempSamples.push_back(sample); + + Logger::WriteMessage(std::format("({}) {}: {}\n", + i, pMetricMap->at(m).narrowName, sample.value).c_str()); + + // loose sanity check to avoid flakiness + Assert::IsTrue(sample.value > 10.); + } + + { + constexpr auto m = PM_METRIC_GPU_POWER; + auto& r = gpu.telemetryData.FindRing(m).at(0); + Assert::IsFalse(r.Empty()); + + if (i == 0 || i == 9) { + Logger::WriteMessage(DumpRing_(r).c_str()); + } + + auto sample = r.Newest(); + powerSamples.push_back(sample); + + Logger::WriteMessage(std::format("({}) {}: {}\n", + i, pMetricMap->at(m).narrowName, sample.value).c_str()); + + // loose sanity check to avoid flakiness + Assert::IsTrue(sample.value > 1.); + } + } + + // make sure samples actually change over time + Assert::AreNotEqual(tempSamples.front().timestamp, tempSamples.back().timestamp); + Assert::AreNotEqual(powerSamples.front().timestamp, powerSamples.back().timestamp); + Assert::AreNotEqual(powerSamples.front().value, powerSamples.back().value); + } + }; } diff --git a/IntelPresentMon/PresentMonService/PMMainThread.cpp b/IntelPresentMon/PresentMonService/PMMainThread.cpp index 2f22fdec..fc73272c 100644 --- a/IntelPresentMon/PresentMonService/PMMainThread.cpp +++ b/IntelPresentMon/PresentMonService/PMMainThread.cpp @@ -77,6 +77,214 @@ void EventFlushThreadEntry_(Service* const srv, PresentMon* const pm) } } +// Translate a single power-telemetry sample into the rings for one GPU store. +// This mirrors the CPU placeholder approach but handles double/uint64/bool rings. +static void PopulateGpuTelemetryRings_( + ipc::GpuDataStore& store, + const PresentMonPowerTelemetryInfo& s) noexcept +{ + for (auto&& [metric, ringVariant] : store.telemetryData.Rings()) { + switch (metric) { + + // -------- double metrics -------- + case PM_METRIC_GPU_SUSTAINED_POWER_LIMIT: + std::get>(ringVariant)[0] + .Push(s.gpu_sustained_power_limit_w, s.qpc); + break; + + case PM_METRIC_GPU_POWER: + std::get>(ringVariant)[0] + .Push(s.gpu_power_w, s.qpc); + break; + + case PM_METRIC_GPU_VOLTAGE: + std::get>(ringVariant)[0] + .Push(s.gpu_voltage_v, s.qpc); + break; + + case PM_METRIC_GPU_FREQUENCY: + std::get>(ringVariant)[0] + .Push(s.gpu_frequency_mhz, s.qpc); + break; + + case PM_METRIC_GPU_EFFECTIVE_FREQUENCY: + std::get>(ringVariant)[0] + .Push(s.gpu_effective_frequency_mhz, s.qpc); + break; + + case PM_METRIC_GPU_TEMPERATURE: + std::get>(ringVariant)[0] + .Push(s.gpu_temperature_c, s.qpc); + break; + + case PM_METRIC_GPU_VOLTAGE_REGULATOR_TEMPERATURE: + std::get>(ringVariant)[0] + .Push(s.gpu_voltage_regulator_temperature_c, s.qpc); + break; + + case PM_METRIC_GPU_UTILIZATION: + std::get>(ringVariant)[0] + .Push(s.gpu_utilization, s.qpc); + break; + + case PM_METRIC_GPU_RENDER_COMPUTE_UTILIZATION: + std::get>(ringVariant)[0] + .Push(s.gpu_render_compute_utilization, s.qpc); + break; + + case PM_METRIC_GPU_MEDIA_UTILIZATION: + std::get>(ringVariant)[0] + .Push(s.gpu_media_utilization, s.qpc); + break; + + case PM_METRIC_GPU_MEM_EFFECTIVE_BANDWIDTH: + std::get>(ringVariant)[0] + .Push(s.gpu_mem_effective_bandwidth_gbps, s.qpc); + break; + + case PM_METRIC_GPU_OVERVOLTAGE_PERCENT: + std::get>(ringVariant)[0] + .Push(s.gpu_overvoltage_percent, s.qpc); + break; + + case PM_METRIC_GPU_TEMPERATURE_PERCENT: + std::get>(ringVariant)[0] + .Push(s.gpu_temperature_percent, s.qpc); + break; + + case PM_METRIC_GPU_POWER_PERCENT: + std::get>(ringVariant)[0] + .Push(s.gpu_power_percent, s.qpc); + break; + + case PM_METRIC_GPU_CARD_POWER: + std::get>(ringVariant)[0] + .Push(s.gpu_card_power_w, s.qpc); + break; + + case PM_METRIC_GPU_FAN_SPEED: + { + auto& ringVect = + std::get>(ringVariant); + + const size_t n = std::min(ringVect.size(), s.fan_speed_rpm.size()); + for (size_t i = 0; i < n; ++i) { + ringVect[i].Push(s.fan_speed_rpm[i], s.qpc); + } + break; + } + + // VRAM-related doubles + case PM_METRIC_GPU_MEM_POWER: + std::get>(ringVariant)[0] + .Push(s.vram_power_w, s.qpc); + break; + + case PM_METRIC_GPU_MEM_VOLTAGE: + std::get>(ringVariant)[0] + .Push(s.vram_voltage_v, s.qpc); + break; + + case PM_METRIC_GPU_MEM_FREQUENCY: + std::get>(ringVariant)[0] + .Push(s.vram_frequency_mhz, s.qpc); + break; + + case PM_METRIC_GPU_MEM_EFFECTIVE_FREQUENCY: + std::get>(ringVariant)[0] + .Push(s.vram_effective_frequency_gbps, s.qpc); + break; + + case PM_METRIC_GPU_MEM_TEMPERATURE: + std::get>(ringVariant)[0] + .Push(s.vram_temperature_c, s.qpc); + break; + + // Memory bandwidth doubles + case PM_METRIC_GPU_MEM_WRITE_BANDWIDTH: + std::get>(ringVariant)[0] + .Push(s.gpu_mem_write_bandwidth_bps, s.qpc); + break; + + case PM_METRIC_GPU_MEM_READ_BANDWIDTH: + std::get>(ringVariant)[0] + .Push(s.gpu_mem_read_bandwidth_bps, s.qpc); + break; + + // -------- uint64 metrics -------- + case PM_METRIC_GPU_MEM_SIZE: + std::get>(ringVariant)[0] + .Push(s.gpu_mem_total_size_b, s.qpc); + break; + + case PM_METRIC_GPU_MEM_USED: + std::get>(ringVariant)[0] + .Push(s.gpu_mem_used_b, s.qpc); + break; + + case PM_METRIC_GPU_MEM_MAX_BANDWIDTH: + std::get>(ringVariant)[0] + .Push(s.gpu_mem_max_bandwidth_bps, s.qpc); + break; + + // -------- bool metrics -------- + case PM_METRIC_GPU_POWER_LIMITED: + std::get>(ringVariant)[0] + .Push(s.gpu_power_limited, s.qpc); + break; + + case PM_METRIC_GPU_TEMPERATURE_LIMITED: + std::get>(ringVariant)[0] + .Push(s.gpu_temperature_limited, s.qpc); + break; + + case PM_METRIC_GPU_CURRENT_LIMITED: + std::get>(ringVariant)[0] + .Push(s.gpu_current_limited, s.qpc); + break; + + case PM_METRIC_GPU_VOLTAGE_LIMITED: + std::get>(ringVariant)[0] + .Push(s.gpu_voltage_limited, s.qpc); + break; + + case PM_METRIC_GPU_UTILIZATION_LIMITED: + std::get>(ringVariant)[0] + .Push(s.gpu_utilization_limited, s.qpc); + break; + + case PM_METRIC_GPU_MEM_POWER_LIMITED: + std::get>(ringVariant)[0] + .Push(s.vram_power_limited, s.qpc); + break; + + case PM_METRIC_GPU_MEM_TEMPERATURE_LIMITED: + std::get>(ringVariant)[0] + .Push(s.vram_temperature_limited, s.qpc); + break; + + case PM_METRIC_GPU_MEM_CURRENT_LIMITED: + std::get>(ringVariant)[0] + .Push(s.vram_current_limited, s.qpc); + break; + + case PM_METRIC_GPU_MEM_VOLTAGE_LIMITED: + std::get>(ringVariant)[0] + .Push(s.vram_voltage_limited, s.qpc); + break; + + case PM_METRIC_GPU_MEM_UTILIZATION_LIMITED: + std::get>(ringVariant)[0] + .Push(s.vram_utilization_limited, s.qpc); + break; + + default: + break; + } + } +} + + void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, PowerTelemetryContainer* const ptc, ipc::ServiceComms* const pComms) { @@ -135,8 +343,21 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, // TODO: log error here or inside of repopulate ptc->Repopulate(); } - for (auto& adapter : ptc->GetPowerTelemetryAdapters()) { + auto& adapters = ptc->GetPowerTelemetryAdapters(); + for (size_t idx = 0; idx < adapters.size(); ++idx) { + auto& adapter = adapters[idx]; adapter->Sample(); + + // Get the newest sample from the provider + const auto& sample = adapter->GetNewest(); + + // Retrieve the matching GPU store. + // Adjust this to your actual ServiceComms API. + auto& store = pComms->GetGpuDataStore(uint32_t(idx + 1)); + // Alternative if your API is keyed differently: + // auto& store = pComms->GetGpuDataStore(adapter->GetVendor(), adapter->GetName()); + + PopulateGpuTelemetryRings_(store, sample); } // Convert from the ms to seconds as GetTelemetryPeriod returns back // ms and SetInterval expects seconds. @@ -205,6 +426,7 @@ void CpuTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, ipc::Ser ringVect[0].Push(sample.cpu_temperature, sample.qpc); break; default: + pmlog_warn("Unhandled metric ring").pmwatch((int)metric); break; } } From 125160f6aee9edcd38bb9c3339e9992f330c8a75 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Tue, 9 Dec 2025 10:54:47 -0800 Subject: [PATCH 036/205] Animation Error and TIme fixes. All ULTs passing. --- .../CommonUtilities/mc/MetricsCalculator.cpp | 13 +- IntelPresentMon/UnitTests/MetricsCore.cpp | 489 ++++++++++++++---- 2 files changed, 396 insertions(+), 106 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 0d9ec1ca..20fce385 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -270,13 +270,22 @@ namespace pmon::util::metrics bool isAppFrame, FrameMetrics& out) { - if (!isDisplayed || !isAppFrame || chain.firstAppSimStartTime == 0) { + if (!isDisplayed || !isAppFrame) { out.msAnimationTime = std::nullopt; return; } - uint64_t currentSimStart = CalculateSimStartTime(chain, present, chain.animationErrorSource); + bool isFirstProviderSimTime = + chain.animationErrorSource == AnimationErrorSource::CpuStart && + (present.getAppSimStartTime() != 0 || present.getPclSimStartTime() != 0); + if (isFirstProviderSimTime) { + // Seed only: no animation time yet. UpdateAfterPresent will flip us + // into AppProvider/PCL and latch firstAppSimStartTime. + out.msAnimationTime = std::nullopt; + return; + } + uint64_t currentSimStart = CalculateSimStartTime(chain, present, chain.animationErrorSource); if (currentSimStart == 0) { out.msAnimationTime = std::nullopt; return; diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index 68eb899f..09508fb6 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -771,44 +771,6 @@ namespace MetricsCoreTests // Should use pclSimStartTime Assert::AreEqual(6000ull, result); } - - TEST_METHOD(AppProviderFallsBackToCpuStartWhenZero) - { - QpcConverter qpc{ 10000000, 0 }; - - SwapChainCoreState swapChain{}; - FrameData lastApp{}; - lastApp.presentStartTime = 1000; - lastApp.timeInPresent = 50; - swapChain.lastAppPresent = lastApp; - - FrameData current{}; - current.appSimStartTime = 0; // Not available - - auto result = CalculateSimStartTime(swapChain, current, AnimationErrorSource::AppProvider); - - // Should fall back to CPU start: 1000 + 50 = 1050 - Assert::AreEqual(1050ull, result); - } - - TEST_METHOD(PCLatencyFallsBackToCpuStartWhenZero) - { - QpcConverter qpc{ 10000000, 0 }; - - SwapChainCoreState swapChain{}; - FrameData lastApp{}; - lastApp.presentStartTime = 1000; - lastApp.timeInPresent = 50; - swapChain.lastAppPresent = lastApp; - - FrameData current{}; - current.pclSimStartTime = 0; // Not available - - auto result = CalculateSimStartTime(swapChain, current, AnimationErrorSource::PCLatency); - - // Should fall back to CPU start: 1000 + 50 = 1050 - Assert::AreEqual(1050ull, result); - } }; TEST_CLASS(CalculateAnimationTimeTests) @@ -5065,9 +5027,6 @@ namespace MetricsCoreTests QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; - // Drive sim start from PCL latency - chain.animationErrorSource = AnimationErrorSource::PCLatency; - // // Frame 1: first displayed PCL frame // @@ -5174,7 +5133,6 @@ namespace MetricsCoreTests QpcConverter qpc(10'000'000, 0); SwapChainCoreState state{}; - // state.animationErrorSource defaults to CpuStart // Frame 1: First PCL data FrameData frame1{}; @@ -5191,8 +5149,6 @@ namespace MetricsCoreTests auto metrics1 = ComputeMetricsForPresent(qpc, frame1, &next1, state); Assert::AreEqual(size_t(1), metrics1.size()); - Assert::IsTrue(metrics1[0].metrics.msAnimationTime.has_value()); - Assert::AreEqual(0.0, metrics1[0].metrics.msAnimationTime.value(), 0.0001); Assert::AreEqual(uint64_t(100), state.firstAppSimStartTime); Assert::IsTrue(state.animationErrorSource == AnimationErrorSource::PCLatency); @@ -5314,7 +5270,6 @@ namespace MetricsCoreTests // // Expected Outcome: // - msAnimationTime = 0 (first frame with valid CPU start) - // - firstAppSimStartTime is set to cpuStart (1'000'000) for subsequent frames // - lastDisplayedSimStartTime is updated to cpuStart (1'000'000) QpcConverter qpc(10'000'000, 0); @@ -5365,86 +5320,110 @@ namespace MetricsCoreTests // Assert: State should be updated with CPU start // cpuStart = 800'000 + 200'000 = 1'000'000 uint64_t expectedCpuStart = 800'000 + 200'000; - Assert::AreEqual(expectedCpuStart, state.firstAppSimStartTime, - L"State: firstAppSimStartTime should be set to CPU start value"); Assert::AreEqual(expectedCpuStart, state.lastDisplayedSimStartTime, L"State: lastDisplayedSimStartTime should be set to CPU start value"); } // ======================================================================== - // D3: AnimationTime_CpuStart_SecondFrame_IncrementsCorrectly + // D3: AnimationTime_CpuStart_IncreasesAcrossFramesWithoutProvider // ======================================================================== - TEST_METHOD(AnimationTime_CpuStart_SecondFrame_IncrementsCorrectly) + TEST_METHOD(AnimationTime_CpuStart_IncreasesAcrossFramesWithoutProvider) { // Scenario: - // - Prior state: firstAppSimStartTime = cpuStart1 = 1'000'000, lastDisplayedSimStartTime = 1'000'000 - // - Chain.lastAppPresent points to first frame - // - Second frame: displayed, new lastAppPresent - // - cpuStart2 = 1'100'000 + 100'000 = 1'200'000 (new prior frame's end time) - // - QPC frequency = 10 MHz → delta = 200'000 ticks = 0.02 ms - // - animationErrorSource = CpuStart - // - // Expected Outcome: - // - msAnimationTime ≈ 0.02 ms (elapsed CPU start time from first to current) - // - firstAppSimStartTime unchanged (still 1'000'000) - // - lastDisplayedSimStartTime updated to 1'200'000 - - QpcConverter qpc(10'000'000, 0); + // - animationErrorSource = CpuStart + // - No App or PCL sim start timestamps on any frame. + // - We seed lastAppPresent so the first tested frame already has + // a non-zero CpuStart. + // - We then process two displayed frames and expect: + // * Both report msAnimationTime (has_value()). + // * Frame 2's msAnimationTime > Frame 1's msAnimationTime. + // * firstAppSimStartTime stays 0 (no provider events yet). + + QpcConverter qpc(10'000'000, 500'000); SwapChainCoreState state{}; - // state.animationErrorSource defaults to CpuStart + state.animationErrorSource = AnimationErrorSource::CpuStart; + + // Seed lastAppPresent so CalculateCPUStart() for frame1 is non-zero. + // cpuStart1 = prior.presentStartTime + prior.timeInPresent + FrameData prior{}; + prior.presentStartTime = 1'000'000; + prior.timeInPresent = 100'000; // cpuStart1 = 1'100'000 + prior.readyTime = 1'200'000; + prior.setFinalState(PresentResult::Presented); + prior.displayed.push_back({ FrameType::Application, 1'300'000 }); + state.lastAppPresent = prior; + + // Sanity: no provider sim-start yet. + state.firstAppSimStartTime = 0; + state.lastDisplayedSimStartTime = 0; - // First frame setup + // -------------------------------------------------------------------- + // Frame 1: Presented + displayed, no App/PCL sim start + // -------------------------------------------------------------------- FrameData frame1{}; - frame1.presentStartTime = 800'000; - frame1.timeInPresent = 200'000; - frame1.readyTime = 1'000'000; + frame1.presentStartTime = 2'000'000; + frame1.timeInPresent = 80'000; + frame1.readyTime = 2'100'000; + frame1.appSimStartTime = 0; + frame1.pclSimStartTime = 0; frame1.setFinalState(PresentResult::Presented); - frame1.displayed.push_back({ FrameType::Application, 1'100'000 }); + frame1.displayed.push_back({ FrameType::Application, 2'500'000 }); + + FrameData next1{}; + next1.presentStartTime = 3'000'000; + next1.timeInPresent = 50'000; + next1.readyTime = 3'100'000; + next1.setFinalState(PresentResult::Presented); + next1.displayed.push_back({ FrameType::Application, 3'500'000 }); + + auto metrics1 = ComputeMetricsForPresent(qpc, frame1, &next1, state); + Assert::AreEqual(size_t(1), metrics1.size()); + const auto& m1 = metrics1[0].metrics; - state.lastAppPresent = frame1; - state.firstAppSimStartTime = 1'000'000; - state.lastDisplayedSimStartTime = 1'000'000; + Assert::IsTrue(m1.msAnimationTime.has_value(), + L"CpuStart animation should report msAnimationTime even without App/PCL provider."); + const double anim1 = m1.msAnimationTime.value(); + Assert::IsTrue(anim1 > 0.0, + L"First CpuStart-driven frame should have a positive animation time relative to session/start anchor."); - // Second frame with new timing + // No provider yet → firstAppSimStartTime must remain 0. + Assert::AreEqual(uint64_t(0), state.firstAppSimStartTime, + L"firstAppSimStartTime should not be set until App/PCL provider events arrive."); + + // After frame1, lastAppPresent is now frame1; CpuStart for frame2 will be later. + // -------------------------------------------------------------------- + // Frame 2: later Presented + displayed frame, still no App/PCL provider + // -------------------------------------------------------------------- FrameData frame2{}; - frame2.presentStartTime = 1'100'000; - frame2.timeInPresent = 100'000; - frame2.readyTime = 1'200'000; + frame2.presentStartTime = 4'000'000; + frame2.timeInPresent = 120'000; + frame2.readyTime = 4'200'000; frame2.appSimStartTime = 0; frame2.pclSimStartTime = 0; - frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 1'300'000 }); + frame2.setFinalState(PresentResult::Presented); + frame2.displayed.push_back({ FrameType::Application, 4'600'000 }); - // Create nextDisplayed - FrameData next{}; - next.presentStartTime = 1'500'000; - next.timeInPresent = 50'000; - next.readyTime = 1'600'000; - next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'700'000 }); + FrameData next2{}; + next2.presentStartTime = 5'000'000; + next2.timeInPresent = 50'000; + next2.readyTime = 5'100'000; + next2.setFinalState(PresentResult::Presented); + next2.displayed.push_back({ FrameType::Application, 5'500'000 }); - // Action: Compute metrics - auto metricsVector = ComputeMetricsForPresent(qpc, frame2, &next, state); + auto metrics2 = ComputeMetricsForPresent(qpc, frame2, &next2, state); + Assert::AreEqual(size_t(1), metrics2.size()); + const auto& m2 = metrics2[0].metrics; - Assert::AreEqual(size_t(1), metricsVector.size()); - const ComputedMetrics& result = metricsVector[0]; + Assert::IsTrue(m2.msAnimationTime.has_value(), + L"Second CpuStart-driven frame should also report msAnimationTime."); + const double anim2 = m2.msAnimationTime.value(); - // Assert: msAnimationTime should be 0.02 ms - // cpuStart2 = 1'100'000 + 100'000 = 1'200'000 - // delta = 1'200'000 - 1'000'000 = 200'000 ticks = 0.02 ms - Assert::IsTrue(result.metrics.msAnimationTime.has_value()); - double expectedMs = qpc.DeltaUnsignedMilliSeconds(1'000'000, 1'200'000); - Assert::AreEqual(expectedMs, result.metrics.msAnimationTime.value(), 0.0001, - L"msAnimationTime should reflect elapsed CPU start time"); + Assert::IsTrue(anim2 > anim1, + L"CpuStart-based animation time should increase across frames as CpuStart advances."); - // Assert: firstAppSimStartTime unchanged - Assert::AreEqual(uint64_t(1'000'000), state.firstAppSimStartTime, - L"firstAppSimStartTime should remain at first value"); - - // Assert: lastDisplayedSimStartTime updated - uint64_t expectedNewCpuStart = 1'100'000 + 100'000; - Assert::AreEqual(expectedNewCpuStart, state.lastDisplayedSimStartTime, - L"lastDisplayedSimStartTime should be updated to new CPU start value"); + // Still no provider events → anchor remains "non-provider", so firstAppSimStartTime should be 0. + Assert::AreEqual(uint64_t(0), state.firstAppSimStartTime, + L"firstAppSimStartTime should still be 0 without App/PCL provider data."); } }; @@ -6216,5 +6195,307 @@ namespace MetricsCoreTests double expected = simElapsed - displayElapsed; Assert::AreEqual(expected, resultsPartial[1].metrics.msAnimationError.value(), 0.0001); } - }; + TEST_METHOD(Animation_AppProvider_PendingSequence_P1P2P3) + { + // This test mimics the real ReportMetrics pipeline for a single swapchain: + // + // P1 arrives: + // - ComputeMetricsForPresent(P1, nullptr, state) // Case 2, pending = P1 + // + // P2 arrives: + // - ComputeMetricsForPresent(P1, &P2, state) // Case 3, finalize P1 + // - ComputeMetricsForPresent(P2, nullptr, state) // Case 2, pending = P2 + // + // P3 arrives: + // - ComputeMetricsForPresent(P2, &P3, state) // Case 3, finalize P2 + // - ComputeMetricsForPresent(P3, nullptr, state) // Case 2, pending = P3 + // + // We verify: + // - P1's final metrics: no animation error/time (it seeds animation state). + // - P2's final metrics: non-null msAnimationTime & msAnimationError == 0.0. + // - SwapChainCoreState's animation fields evolve correctly: + // firstAppSimStartTime = 100 + // lastDisplayedSimStartTime = 200 after P2 + // lastDisplayedAppScreenTime = P2's screenTime + // + // QPC frequency = 10 MHz. + // App sim times: P1=100, P2=200, P3=300 + // Screen times: P1=1'000'000, P2=1'100'000, P3=1'200'000 + // -> sim Δ P1→P2: 100 ticks = 0.01 ms + // -> display Δ P1→P2: 100'000 ticks = 0.01 ms + // => animation error for P2 = 0.0 ms + // => animation time for P2 = (200 - 100) / 10e6 = 0.01 ms + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + + // -------------------------------------------------------------------- + // P1: first displayed app frame with AppProvider data + // -------------------------------------------------------------------- + FrameData p1{}; + p1.presentStartTime = 500'000; + p1.timeInPresent = 10'000; + p1.readyTime = 510'000; + p1.appSimStartTime = 475'000; + p1.pclSimStartTime = 0; + p1.setFinalState(PresentResult::Presented); + p1.displayed.push_back({ FrameType::Application, 1'000'000 }); + + // Arrival of P1 -> Case 2 (no nextDisplayed yet), becomes pending + auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, state); + Assert::AreEqual(size_t(0), p1_phase1.size(), + L"First call for P1 with next=nullptr should produce no metrics (pending only)."); + + // Animation state should still be in CpuStart mode; no provider seeded yet. + Assert::IsTrue(state.animationErrorSource == AnimationErrorSource::CpuStart); + Assert::AreEqual(uint64_t(0), state.firstAppSimStartTime); + Assert::AreEqual(uint64_t(0), state.lastDisplayedSimStartTime); + Assert::AreEqual(uint64_t(0), state.lastDisplayedAppScreenTime); + + // -------------------------------------------------------------------- + // P2: second displayed app frame + // -------------------------------------------------------------------- + FrameData p2{}; + p2.presentStartTime = 600'000; + p2.timeInPresent = 10'000; + p2.readyTime = 610'000; + p2.appSimStartTime = 575'000; + p2.pclSimStartTime = 0; + p2.setFinalState(PresentResult::Presented); + p2.displayed.push_back({ FrameType::Application, 1'100'000 }); + + // Arrival of P2: + // 1) Flush pending P1 using P2 as nextDisplayed -> Case 3, finalize P1. + auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, state); + Assert::AreEqual(size_t(1), p1_final.size()); + const auto& p1_metrics = p1_final[0].metrics; + + // P1 is the FIRST provider-driven frame, so it should only seed the state: + // no animation error or time yet. + Assert::IsFalse(p1_metrics.msAnimationError.has_value(), + L"P1 should not report animation error; it seeds the animation state."); + Assert::IsFalse(p1_metrics.msAnimationTime.has_value(), + L"P1 should not report animation time; it seeds the animation state."); + + // UpdateAfterPresent should have run for P1 and switched to AppProvider: + Assert::IsTrue(state.animationErrorSource == AnimationErrorSource::AppProvider, + L"Animation source should transition to AppProvider after P1."); + Assert::AreEqual(uint64_t(475'000), state.firstAppSimStartTime, + L"firstAppSimStartTime should latch P1's appSimStartTime."); + Assert::AreEqual(uint64_t(475'000), state.lastDisplayedSimStartTime, + L"lastDisplayedSimStartTime should match P1's appSimStartTime."); + Assert::AreEqual(uint64_t(1'000'000), state.lastDisplayedAppScreenTime, + L"lastDisplayedAppScreenTime should match P1's screenTime."); + + // 2) Now process P2's arrival as pending: Case 2 with next=nullptr + auto p2_phase1 = ComputeMetricsForPresent(qpc, p2, nullptr, state); + Assert::AreEqual(size_t(0), p2_phase1.size(), + L"First call for P2 with next=nullptr should produce no metrics (pending only)."); + + // -------------------------------------------------------------------- + // P3: third displayed app frame + // -------------------------------------------------------------------- + FrameData p3{}; + p3.presentStartTime = 700'000; + p3.timeInPresent = 10'000; + p3.readyTime = 710'000; + p3.appSimStartTime = 675'000; + p3.pclSimStartTime = 0; + p3.setFinalState(PresentResult::Presented); + p3.displayed.push_back({ FrameType::Application, 1'200'000 }); + + // Arrival of P3: + // 1) Flush pending P2 using P3 as nextDisplayed -> Case 3, finalize P2. + auto p2_final = ComputeMetricsForPresent(qpc, p2, &p3, state); + Assert::AreEqual(size_t(1), p2_final.size()); + const auto& p2_metrics = p2_final[0].metrics; + + // For P2: + // - previous displayed sim start = P1.appSimStartTime = 100 + // - current sim start = P2.appSimStartTime = 200 + // - previous screen time = P1.screenTime = 1'000'000 + // - current screen time = P2.screenTime = 1'100'000 + // => simElapsed = 100 ticks → 0.01 ms + // => displayElapsed = 100'000 ticks → 0.01 ms + // => animationError = 0.0 ms + // => animationTime = (200 - 100) ticks from firstAppSimStartTime -> 0.01 ms + + Assert::IsTrue(p2_metrics.msAnimationError.has_value(), + L"P2 should report animation error."); + Assert::IsTrue(p2_metrics.msAnimationTime.has_value(), + L"P2 should report animation time."); + + double expectedError = 0.0; + Assert::AreEqual(expectedError, p2_metrics.msAnimationError.value(), 0.0001, + L"P2's msAnimationError should be 0.0 when sim and display deltas match."); + + double expectedAnim = qpc.DeltaUnsignedMilliSeconds(475'000, 575'000); + Assert::AreEqual(expectedAnim, p2_metrics.msAnimationTime.value(), 0.0001, + L"P2's msAnimationTime should be based on firstAppSimStartTime (100) to current sim (200)."); + + // After finalizing P2, chain state should now reflect P2 as "last displayed" + Assert::AreEqual(uint64_t(475'000), state.firstAppSimStartTime, + L"firstAppSimStartTime should remain anchored to P1."); + Assert::AreEqual(uint64_t(575'000), state.lastDisplayedSimStartTime, + L"lastDisplayedSimStartTime should advance to P2's appSimStartTime."); + Assert::AreEqual(uint64_t(1'100'000), state.lastDisplayedAppScreenTime, + L"lastDisplayedAppScreenTime should advance to P2's screenTime."); + + // 2) Finally, process P3 as the new pending (Case 2 with next=nullptr). + auto p3_phase1 = ComputeMetricsForPresent(qpc, p3, nullptr, state); + Assert::AreEqual(size_t(0), p3_phase1.size(), + L"First call for P3 with next=nullptr should produce no metrics (pending only)."); + } + // ======================================================================== + // A7: Animation_AppProvider_PendingSequence_P2Discarded_SkipsAnimation + // ======================================================================== + TEST_METHOD(Animation_AppProvider_PendingSequence_P2Discarded_SkipsAnimation) + { + // This test mimics the real ReportMetrics pipeline for a single swapchain + // when a middle frame (P2) is discarded and never displayed. + // + // P1 arrives (displayed, has AppProvider sim start): + // - ComputeMetricsForPresent(P1, nullptr, state) // Case 2, pending = P1 + // + // P2 arrives (DISCARDED, not displayed, but with appSimStartTime): + // - ComputeMetricsForPresent(P2, nullptr, state) // Case 1, not displayed + // * Should produce a single metrics entry with NO animation time/error + // * Must NOT change firstAppSimStartTime / lastDisplayedSimStartTime + // + // P3 arrives (displayed, has AppProvider sim start): + // - ComputeMetricsForPresent(P1, &P3, state) // Case 3, finalize P1 + // * P1 is the FIRST provider-driven displayed frame, so it seeds state. + // * P1 should not report animation time/error. + // * State switches to AppProvider and latches P1's sim + screen times. + // - ComputeMetricsForPresent(P3, nullptr, state) // Case 2, pending = P3 + // + // App sim times are in QPC domain: + // P1.appSimStartTime = 475'000 + // P2.appSimStartTime = 575'000 + // P3.appSimStartTime = 675'000 + // + // Screen times: + // P1.screenTime = 1'000'000 + // P2 has no screenTime (discarded, not displayed) + // P3.screenTime = 1'100'000 + // + // We verify: + // - P2's metrics have no msAnimationTime / msAnimationError. + // - P2 does not change firstAppSimStartTime / lastDisplayedSimStartTime. + // - After finalizing P1 with P3 as nextDisplayed: + // * animationErrorSource == AppProvider + // * firstAppSimStartTime == 475'000 + // * lastDisplayedSimStartTime == 475'000 + // * lastDisplayedAppScreenTime == 1'000'000 + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + + // -------------------------------------------------------------------- + // P1: first displayed app frame with AppProvider data + // -------------------------------------------------------------------- + FrameData p1{}; + p1.presentStartTime = 500'000; + p1.timeInPresent = 10'000; + p1.readyTime = 510'000; + p1.appSimStartTime = 475'000; // APC-provided sim start (QPC ticks) + p1.pclSimStartTime = 0; + p1.setFinalState(PresentResult::Presented); + p1.displayed.push_back({ FrameType::Application, 1'000'000 }); // screen time + + // P1 arrives -> Case 2 (no nextDisplayed yet), becomes pending + auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, state); + Assert::AreEqual(size_t(0), p1_phase1.size(), + L"First call for P1 with next=nullptr should produce no metrics (pending only)."); + + // Still in CpuStart mode; no provider-seeded animation state yet. + Assert::IsTrue(state.animationErrorSource == AnimationErrorSource::CpuStart); + Assert::AreEqual(uint64_t(0), state.firstAppSimStartTime); + Assert::AreEqual(uint64_t(0), state.lastDisplayedSimStartTime); + Assert::AreEqual(uint64_t(0), state.lastDisplayedAppScreenTime); + + // -------------------------------------------------------------------- + // P2: discarded, not displayed, but with AppProvider sim start + // -------------------------------------------------------------------- + FrameData p2{}; + p2.presentStartTime = 600'000; + p2.timeInPresent = 10'000; + p2.readyTime = 610'000; + p2.appSimStartTime = 575'000; // APC timestamp, but this frame is not displayed + p2.pclSimStartTime = 0; + p2.setFinalState(PresentResult::Discarded); + // No displayed entries -> not displayed + + auto p2_results = ComputeMetricsForPresent(qpc, p2, nullptr, state); + Assert::AreEqual(size_t(1), p2_results.size(), + L"Discarded frame should produce a single not-displayed metrics entry."); + + const auto& p2_metrics = p2_results[0].metrics; + + // Discarded / not-displayed frame must NOT produce animation metrics. + Assert::IsFalse(p2_metrics.msAnimationTime.has_value(), + L"P2 (discarded) should not have msAnimationTime."); + Assert::IsFalse(p2_metrics.msAnimationError.has_value(), + L"P2 (discarded) should not have msAnimationError."); + + // And it must NOT disturb animation anchors, since it's not displayed. + Assert::AreEqual(uint64_t(0), state.firstAppSimStartTime, + L"P2 must not set firstAppSimStartTime; only displayed App/PCL frames do that."); + Assert::AreEqual(uint64_t(0), state.lastDisplayedSimStartTime, + L"P2 must not change lastDisplayedSimStartTime when not displayed."); + Assert::AreEqual(uint64_t(0), state.lastDisplayedAppScreenTime, + L"P2 must not change lastDisplayedAppScreenTime when not displayed."); + + // (Optional sanity: lastSimStartTime may track P2's appSimStartTime, which is fine for + // simulation plumbing but not for animation anchors.) + + // -------------------------------------------------------------------- + // P3: next displayed app frame + // -------------------------------------------------------------------- + FrameData p3{}; + p3.presentStartTime = 700'000; + p3.timeInPresent = 10'000; + p3.readyTime = 710'000; + p3.appSimStartTime = 675'000; // another +100'000 ticks in sim space + p3.pclSimStartTime = 0; + p3.setFinalState(PresentResult::Presented); + p3.displayed.push_back({ FrameType::Application, 1'100'000 }); // next screen time + + // P3 arrives: + // 1) Flush pending P1 using P3 as nextDisplayed -> Case 3, finalize P1. + auto p1_final = ComputeMetricsForPresent(qpc, p1, &p3, state); + Assert::AreEqual(size_t(1), p1_final.size()); + const auto& p1_metrics = p1_final[0].metrics; + + // P1 is the FIRST displayed frame with AppProvider sim start. + // It should only seed animation state; no error/time yet. + Assert::IsFalse(p1_metrics.msAnimationError.has_value(), + L"P1 should not report animation error; it seeds the animation state."); + Assert::IsFalse(p1_metrics.msAnimationTime.has_value(), + L"P1 should not report animation time; it seeds the animation state."); + + // After finalizing P1, we must now be in AppProvider mode with anchors from P1. + Assert::IsTrue(state.animationErrorSource == AnimationErrorSource::AppProvider, + L"Animation source should transition to AppProvider after first displayed AppSimStart frame (P1)."); + Assert::AreEqual(uint64_t(475'000), state.firstAppSimStartTime, + L"firstAppSimStartTime should latch P1's appSimStartTime."); + Assert::AreEqual(uint64_t(475'000), state.lastDisplayedSimStartTime, + L"lastDisplayedSimStartTime should match P1's appSimStartTime after P1 is finalized."); + Assert::AreEqual(uint64_t(1'000'000), state.lastDisplayedAppScreenTime, + L"lastDisplayedAppScreenTime should match P1's screenTime."); + + // 2) Process P3 as the new pending frame (Case 2 with next=nullptr). + auto p3_phase1 = ComputeMetricsForPresent(qpc, p3, nullptr, state); + Assert::AreEqual(size_t(0), p3_phase1.size(), + L"First call for P3 with next=nullptr should produce no metrics (pending only)."); + + // State remains anchored to P1 until a later frame finalizes P3 with a true nextDisplayed. + Assert::AreEqual(uint64_t(475'000), state.firstAppSimStartTime, + L"firstAppSimStartTime should remain anchored to P1 after P3's pending pass."); + Assert::AreEqual(uint64_t(475'000), state.lastDisplayedSimStartTime, + L"lastDisplayedSimStartTime should still reflect P1 until P3 is finalized."); + } +}; + + } \ No newline at end of file From ca5ce7aaf4eb66d10cd4db7d6de46392e2728f4f Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Tue, 9 Dec 2025 12:38:50 -0800 Subject: [PATCH 037/205] Adding in initial input latency metrics --- .../CommonUtilities/mc/MetricsCalculator.cpp | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 20fce385..334973b8 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -312,6 +312,102 @@ namespace pmon::util::metrics nextDisplayedPresent->displayed[0].second = nextScreenTime; } } + + std::optional ComputeClickToPhotonLatency( + const QpcConverter& qpc, + SwapChainCoreState& chain, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + uint64_t screenTime) + { + if (!isAppFrame) { + return std::nullopt; + } + + if (isDisplayed) { + auto inputTime = chain.lastReceivedNotDisplayedMouseClickTime != 0 ? + chain.lastReceivedNotDisplayedMouseClickTime : + present.mouseClickTime; + if (inputTime != 0) { + return qpc.DeltaUnsignedMilliSeconds(inputTime, screenTime); + } + // Reset all last received device time + chain.lastReceivedNotDisplayedMouseClickTime = 0; + return std::nullopt; + } + else { + // Update last received device time + if (present.mouseClickTime != 0) { + chain.lastReceivedNotDisplayedMouseClickTime = present.mouseClickTime; + } + return std::nullopt; + } + } + + std::optional ComputeAllInputToPhotonLatency( + const QpcConverter& qpc, + SwapChainCoreState& chain, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + uint64_t screenTime) + { + if (!isAppFrame) { + return std::nullopt; + } + + if (isDisplayed) { + auto inputTime = chain.lastReceivedNotDisplayedAllInputTime != 0 ? + chain.lastReceivedNotDisplayedAllInputTime : + present.inputTime; + if (inputTime != 0) { + return qpc.DeltaUnsignedMilliSeconds(inputTime, screenTime); + } + // Reset all last received device time + chain.lastReceivedNotDisplayedMouseClickTime = 0; + return std::nullopt; + } + else { + // Update last received device time + if (present.inputTime != 0) { + chain.lastReceivedNotDisplayedMouseClickTime = present.inputTime; + } + return std::nullopt; + } + } + + std::optional ComputeInstrumentedInputToPhotonLatency( + const QpcConverter& qpc, + SwapChainCoreState& chain, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + uint64_t screenTime) + { + if (!isAppFrame) { + return std::nullopt; + } + + if (isDisplayed) { + auto inputTime = chain.lastReceivedNotDisplayedAppProviderInputTime != 0 ? + chain.lastReceivedNotDisplayedAppProviderInputTime : + present.appInputSample.first; + if (inputTime != 0) { + return qpc.DeltaUnsignedMilliSeconds(inputTime, screenTime); + } + // Reset all last received device time + chain.lastReceivedNotDisplayedMouseClickTime = 0; + return std::nullopt; + } + else { + // Update last received device time + if (present.appInputSample.first != 0) { + chain.lastReceivedNotDisplayedAppProviderInputTime = present.appInputSample.first; + } + return std::nullopt; + } + } } DisplayIndexing DisplayIndexing::Calculate( @@ -608,6 +704,39 @@ namespace pmon::util::metrics metrics); } + void CalculateInputLatencyMetrics( + const QpcConverter& qpc, + SwapChainCoreState& swapChain, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + FrameMetrics& metrics) + { + metrics.msClickToPhotonLatency = ComputeClickToPhotonLatency( + qpc, + swapChain, + present, + isDisplayed, + isAppFrame, + metrics.screenTimeQpc); + + metrics.msAllInputPhotonLatency = ComputeAllInputToPhotonLatency( + qpc, + swapChain, + present, + isDisplayed, + isAppFrame, + metrics.screenTimeQpc); + + metrics.msInstrumentedInputTime = ComputeInstrumentedInputToPhotonLatency( + qpc, + swapChain, + present, + isDisplayed, + isAppFrame, + metrics.screenTimeQpc); + } + ComputedMetrics ComputeFrameMetrics( const QpcConverter& qpc, const FrameData& present, From 150b64cc819d0190d765fe1b06c43d40d7f0e24a Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 10 Dec 2025 13:51:34 +0900 Subject: [PATCH 038/205] gpu static populated --- .../Interprocess/source/DataStores.h | 5 ++- .../Interprocess/source/TelemetryMap.h | 10 +++++ .../InterimBroadcasterTests.cpp | 12 ++++++ .../PresentMonService/PMMainThread.cpp | 41 ++++++++++--------- 4 files changed, 47 insertions(+), 21 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/DataStores.h b/IntelPresentMon/Interprocess/source/DataStores.h index 0abf8e6f..ab18d750 100644 --- a/IntelPresentMon/Interprocess/source/DataStores.h +++ b/IntelPresentMon/Interprocess/source/DataStores.h @@ -36,7 +36,9 @@ namespace pmon::ipc GpuDataStore(ShmSegmentManager& segMan) : telemetryData{ segMan.get_allocator() }, - statics{ .name{ segMan.get_allocator() } } + statics{ + .name{ segMan.get_allocator() }, + .maxFanSpeedRpm{ segMan.get_allocator() } } {} struct Statics { @@ -45,6 +47,7 @@ namespace pmon::ipc double sustainedPowerLimit; uint64_t memSize; uint64_t maxMemBandwidth; + ShmVector maxFanSpeedRpm; } statics; TelemetryMap telemetryData; }; diff --git a/IntelPresentMon/Interprocess/source/TelemetryMap.h b/IntelPresentMon/Interprocess/source/TelemetryMap.h index 5443b761..2eda76b5 100644 --- a/IntelPresentMon/Interprocess/source/TelemetryMap.h +++ b/IntelPresentMon/Interprocess/source/TelemetryMap.h @@ -96,6 +96,16 @@ namespace pmon::ipc { return ringMap_.at(id); } + size_t ArraySize(PM_METRIC id) const + { + auto it = ringMap_.find(id); + if (it == ringMap_.end()) { + return 0; + } + return std::visit([](auto const& rings) -> size_t { + return rings.size(); + }, it->second); + } auto Rings() { return std::ranges::subrange{ ringMap_.begin(), ringMap_.end() }; diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 0e3be067..691c8116 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -223,6 +223,18 @@ namespace InterimBroadcasterTests { fixture_.Cleanup(); } + // static store + TEST_METHOD(StaticData) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + // get the store containing gpu telemetry + auto& gpu = pComms->GetGpuDataStore(1); + Assert::AreEqual((int)PM_DEVICE_VENDOR_NVIDIA, (int)gpu.statics.vendor); + Assert::AreEqual("NVIDIA GeForce RTX 2080 Ti", gpu.statics.name.c_str()); + Assert::AreEqual(260., gpu.statics.sustainedPowerLimit); + Assert::AreEqual(11811160064ull, gpu.statics.memSize); + } // polled store TEST_METHOD(PolledData) { diff --git a/IntelPresentMon/PresentMonService/PMMainThread.cpp b/IntelPresentMon/PresentMonService/PMMainThread.cpp index fc73272c..1e3fea3c 100644 --- a/IntelPresentMon/PresentMonService/PMMainThread.cpp +++ b/IntelPresentMon/PresentMonService/PMMainThread.cpp @@ -27,6 +27,7 @@ using namespace pmon; using namespace svc; using namespace util; using v = log::V; +namespace vi = std::views; void EventFlushThreadEntry_(Service* const srv, PresentMon* const pm) @@ -87,10 +88,6 @@ static void PopulateGpuTelemetryRings_( switch (metric) { // -------- double metrics -------- - case PM_METRIC_GPU_SUSTAINED_POWER_LIMIT: - std::get>(ringVariant)[0] - .Push(s.gpu_sustained_power_limit_w, s.qpc); - break; case PM_METRIC_GPU_POWER: std::get>(ringVariant)[0] @@ -212,21 +209,11 @@ static void PopulateGpuTelemetryRings_( break; // -------- uint64 metrics -------- - case PM_METRIC_GPU_MEM_SIZE: - std::get>(ringVariant)[0] - .Push(s.gpu_mem_total_size_b, s.qpc); - break; - case PM_METRIC_GPU_MEM_USED: std::get>(ringVariant)[0] .Push(s.gpu_mem_used_b, s.qpc); break; - case PM_METRIC_GPU_MEM_MAX_BANDWIDTH: - std::get>(ringVariant)[0] - .Push(s.gpu_mem_max_bandwidth_bps, s.qpc); - break; - // -------- bool metrics -------- case PM_METRIC_GPU_POWER_LIMITED: std::get>(ringVariant)[0] @@ -279,6 +266,7 @@ static void PopulateGpuTelemetryRings_( break; default: + pmlog_warn("Unhandled metric").pmwatch((int)metric); break; } } @@ -310,12 +298,29 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, } pmon::util::QpcTimer timer; ptc->Repopulate(); - for (auto& adapter : ptc->GetPowerTelemetryAdapters()) { + for (auto&& [i, adapter] : ptc->GetPowerTelemetryAdapters() | vi::enumerate) { // sample 2x here as workaround/kludge because Intel provider misreports 1st sample adapter->Sample(); adapter->Sample(); pComms->RegisterGpuDevice(adapter->GetVendor(), adapter->GetName(), ipc::intro::ConvertBitset(adapter->GetPowerTelemetryCapBits())); + // after registering, we know that at least the store is available even + // if the introspection itself is not complete + auto& gpuStore = pComms->GetGpuDataStore(uint32_t(i + 1)); + // TODO: replace this placeholder routine for populating statics + gpuStore.statics.name = adapter->GetName().c_str(); + gpuStore.statics.vendor = adapter->GetVendor(); + gpuStore.statics.memSize = adapter->GetDedicatedVideoMemory(); + gpuStore.statics.maxMemBandwidth = adapter->GetVideoMemoryMaxBandwidth(); + gpuStore.statics.sustainedPowerLimit = adapter->GetSustainedPowerLimit(); + // max fanspeed is polled in old system but static in new system, shim here + // TODO: make this fully static + // infer number of fans by the size of the telemetry ring array for fan speed + const auto nFans = gpuStore.telemetryData.ArraySize(PM_METRIC_GPU_FAN_SPEED); + auto& sample = adapter->GetNewest(); + for (size_t i = 0; i < nFans; i++) { + gpuStore.statics.maxFanSpeedRpm.push_back(sample.max_fan_speed_rpm[i]); + } } pComms->FinalizeGpuDevices(); pmlog_info(std::format("Finished populating GPU telemetry introspection, {} seconds elapsed", timer.Mark())); @@ -352,10 +357,7 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, const auto& sample = adapter->GetNewest(); // Retrieve the matching GPU store. - // Adjust this to your actual ServiceComms API. auto& store = pComms->GetGpuDataStore(uint32_t(idx + 1)); - // Alternative if your API is keyed differently: - // auto& store = pComms->GetGpuDataStore(adapter->GetVendor(), adapter->GetName()); PopulateGpuTelemetryRings_(store, sample); } @@ -564,11 +566,10 @@ void PresentMonMainThread(Service* const pSvc) // register cpu pComms->RegisterCpuDevice(vendor, cpu->GetCpuName(), ipc::intro::ConvertBitset(cpu->GetCpuTelemetryCapBits())); - // after registering, we know that at lest the store is available even + // after registering, we know that at least the store is available even // if the introspection itself is not complete auto& systemStore = pComms->GetSystemDataStore(); // TODO: replace this placeholder routine for populating statics - systemStore.statics.cpuName = cpu->GetCpuName().c_str(); systemStore.statics.cpuPowerLimit = cpu->GetCpuPowerLimit(); systemStore.statics.cpuVendor = vendor; From 3814d378160efa47b37f4a338f0deb4c2de93a04 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 10 Dec 2025 17:21:46 +0900 Subject: [PATCH 039/205] testing ring creation, population, introspection --- .../Interprocess/source/Interprocess.cpp | 5 ++ .../InterimBroadcasterTests.cpp | 85 +++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/IntelPresentMon/Interprocess/source/Interprocess.cpp b/IntelPresentMon/Interprocess/source/Interprocess.cpp index dee865e2..dd862140 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.cpp +++ b/IntelPresentMon/Interprocess/source/Interprocess.cpp @@ -81,6 +81,11 @@ namespace pmon::ipc if (metricType == PM_METRIC_TYPE_STATIC) { continue; } + // TODO: systemize the labelling of metrics that are middleware-derived + if (m == PM_METRIC_GPU_FAN_SPEED_PERCENT || + m == PM_METRIC_GPU_MEM_UTILIZATION) { + continue; + } const auto dataType = metric.GetDataTypeInfo().GetFrameType(); gpuShm.GetStore().telemetryData.AddRing( m, telemetryRingSize_, count, dataType diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 691c8116..1cf0a601 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -311,5 +311,90 @@ namespace InterimBroadcasterTests Assert::AreNotEqual(powerSamples.front().timestamp, powerSamples.back().timestamp); Assert::AreNotEqual(powerSamples.front().value, powerSamples.back().value); } + // full 1:1 correspondence between ring creation, ring population, and introspection availability + TEST_METHOD(RingUtilization) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + + // acquire introspection with enhanced wrapper interface + auto pIntro = pComms->GetIntrospectionRoot(); + pmapi::intro::Root intro{ pIntro, [](auto* p) { delete p; } }; + pmapi::EnumMap::Refresh(intro); + auto pMetricMap = pmapi::EnumMap::GetKeyMap(PM_ENUM_METRIC); + + // set telemetry period so we have a known baseline + client.DispatchSync(svc::acts::SetTelemetryPeriod::Params{ .telemetrySamplePeriodMs = 40 }); + + // as a stopgap we target a process in order to trigger service-side telemetry collection + // TODO: remove this when we enable service-side query awareness of connected clients + auto pres = fixture_.LaunchPresenter(); + client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pres.GetId() }); + + // get the store containing adapter telemetry + auto& gpu = pComms->GetGpuDataStore(1); + + // allow a short warmup + std::this_thread::sleep_for(500ms); + + // build the set of expected rings from introspection + Logger::WriteMessage("Introspection Metrics\n=====================\n"); + std::map introspectionAvailability; + for (auto&& m : intro.GetMetrics()) { + // only consider metrics that are polled + if (m.GetType() != PM_METRIC_TYPE_DYNAMIC && + m.GetType() != PM_METRIC_TYPE_DYNAMIC_FRAME) { + continue; + } + // some polled metrics are derived in middleware thus not present in shm + if (m.GetId() == PM_METRIC_GPU_MEM_UTILIZATION || + m.GetId() == PM_METRIC_GPU_FAN_SPEED_PERCENT) { + continue; + } + // check availability for target gpu + size_t arraySize = 0; + for (auto&& di : m.GetDeviceMetricInfo()) { + if (di.GetDevice().GetId() != 1) { + // skip over non-matching devices + continue; + } + if (di.GetAvailability() == PM_METRIC_AVAILABILITY_AVAILABLE) { + // if available get size (otherwise leave at 0 default) + arraySize = di.GetArraySize(); + } + // either way, if we get here, device matched so no need to continue + break; + } + // only consider metrics associated with and available for target gpu + if (arraySize > 0) { + introspectionAvailability[m.GetId()] = arraySize; + // dump for review in output pane + Logger::WriteMessage(std::format("[{}] {}\n", arraySize, + pMetricMap->at(m.GetId()).narrowName).c_str()); + } + } + Logger::WriteMessage(std::format("Total: {}", introspectionAvailability.size()).c_str()); + + // validate that the expected number of rings sets are present in the store + Assert::AreEqual(introspectionAvailability.size(), (size_t)rn::distance(gpu.telemetryData.Rings())); + + // validate that exepected rings are present and are populated with samples + for (auto&& [met, size] : introspectionAvailability) { + // array sizes should match + Assert::AreEqual(size, gpu.telemetryData.ArraySize(met), + pMetricMap->at(met).wideName.c_str()); + std::visit([&](auto const& rings) { + // for each history ring in set, make sure it has at least one sample in it + for (size_t i = 0; i < size; i++) { + auto& name = pMetricMap->at(met).wideName; + Assert::IsFalse(rings[i].Empty(), + std::format(L"{}[{}]", name, i).c_str()); + auto& sample = rings[i].Newest(); + Logger::WriteMessage(std::format(L"{}[{}]: {}@{}\n", name, i, + sample.value, sample.timestamp).c_str()); + } + }, gpu.telemetryData.FindRingVariant(met)); + } + } }; } From 90504c244612efa17991dc4554ac81c8358544b6 Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 11 Dec 2025 06:06:41 +0900 Subject: [PATCH 040/205] ring utilization test for cpu --- IntelPresentMon/ControlLib/CpuTelemetryInfo.h | 1 + .../source/IntrospectionCapsLookup.h | 12 ++- .../source/MetricCapabilitiesShim.cpp | 9 +-- .../InterimBroadcasterTests.cpp | 79 ++++++++++++++++++- 4 files changed, 87 insertions(+), 14 deletions(-) diff --git a/IntelPresentMon/ControlLib/CpuTelemetryInfo.h b/IntelPresentMon/ControlLib/CpuTelemetryInfo.h index b8a746e8..23c2fa47 100644 --- a/IntelPresentMon/ControlLib/CpuTelemetryInfo.h +++ b/IntelPresentMon/ControlLib/CpuTelemetryInfo.h @@ -21,6 +21,7 @@ enum class CpuTelemetryCapBits { cpu_power_limit, cpu_temperature, cpu_frequency, + cpu_core_utility, cpu_telemetry_count }; diff --git a/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h b/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h index fca73c79..f282db01 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h +++ b/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h @@ -11,7 +11,6 @@ namespace pmon::ipc::intro template struct IntrospectionCapsLookup { using Universal = std::true_type; }; // GPU caps template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_power; }; - template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_sustained_power_limit; }; template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_voltage; }; template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_temperature; }; template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_frequency; }; @@ -38,10 +37,8 @@ namespace pmon::ipc::intro static constexpr auto gpuCapBitArray = std::array{ GpuTelemetryCapBits::max_fan_speed_0, GpuTelemetryCapBits::max_fan_speed_1, GpuTelemetryCapBits::max_fan_speed_2, GpuTelemetryCapBits::max_fan_speed_3, GpuTelemetryCapBits::max_fan_speed_4, }; }; - template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_mem_size; }; template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_mem_used; }; template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_mem_used; }; - template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_mem_max_bandwidth; }; template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_mem_write_bandwidth; }; template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_mem_read_bandwidth; }; template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_power_limited; }; @@ -55,16 +52,23 @@ namespace pmon::ipc::intro template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::vram_voltage_limited; }; template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::vram_utilization_limited; }; // static GPU + // TODO: static and cap bit signalling are NOT mutually exclusive!! template<> struct IntrospectionCapsLookup { using GpuDeviceStatic = std::true_type; }; template<> struct IntrospectionCapsLookup { using GpuDeviceStatic = std::true_type; }; + template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_sustained_power_limit; }; + template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_mem_size; }; + template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_mem_max_bandwidth; }; // CPU caps template<> struct IntrospectionCapsLookup { static constexpr auto cpuCapBit = CpuTelemetryCapBits::cpu_utilization; }; template<> struct IntrospectionCapsLookup { static constexpr auto cpuCapBit = CpuTelemetryCapBits::cpu_power; }; template<> struct IntrospectionCapsLookup { static constexpr auto cpuCapBit = CpuTelemetryCapBits::cpu_temperature; }; template<> struct IntrospectionCapsLookup { static constexpr auto cpuCapBit = CpuTelemetryCapBits::cpu_frequency; }; - template<> struct IntrospectionCapsLookup { using ManualDisable = std::true_type; }; + template<> struct IntrospectionCapsLookup { using ManualDisable = std::true_type; + static constexpr auto cpuCapBit = CpuTelemetryCapBits::cpu_core_utility; + }; // static CPU template<> struct IntrospectionCapsLookup { static constexpr auto cpuCapBit = CpuTelemetryCapBits::cpu_power_limit; }; + // TODO: what about name/vendor? (currently functioning as universal) // concepts to help determine device-metric mapping type diff --git a/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp b/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp index 427d2210..d5b6157c 100644 --- a/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp +++ b/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp @@ -46,7 +46,7 @@ namespace pmon::ipc::intro } // Array GPU capability bits (fan speeds, etc.) - if constexpr (IsGpuDeviceMetricArray) { + if constexpr (IsGpuDeviceMetricArray && !IsManualDisableMetric) { std::size_t count = 0; for (auto flag : Lookup::gpuCapBitArray) { if (HasCap(bits, flag)) { @@ -73,16 +73,11 @@ namespace pmon::ipc::intro using Lookup = IntrospectionCapsLookup; // CPU metrics gated by a capability bit - if constexpr (IsCpuMetric) { + if constexpr (IsCpuMetric && !IsManualDisableMetric) { if (HasCap(bits, Lookup::cpuCapBit)) { caps.Set(metricEnum, 1); } } - - // Metrics that exist but are intended for manual disable by default - if constexpr (IsManualDisableMetric) { - caps.Set(metricEnum, 1); - } } // Compile-time recursion over underlying values [0, MaxMetricUnderlying) diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 1cf0a601..640c6638 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -123,7 +123,7 @@ namespace InterimBroadcasterTests } }; - TEST_CLASS(CpuStoreTests) + TEST_CLASS(SystemStoreTests) { TestFixture fixture_; public: @@ -169,7 +169,7 @@ namespace InterimBroadcasterTests // TODO: understand the disconnect between CPU Core Utility showing up here // and now showing up in the UI } - Assert::AreEqual(3ull, (size_t)rn::distance(sys.telemetryData.Rings())); + Assert::AreEqual(2ull, (size_t)rn::distance(sys.telemetryData.Rings())); std::this_thread::sleep_for(500ms); // check that we have data for frequency and utilization std::vector::Sample> utilizSamples; @@ -209,6 +209,79 @@ namespace InterimBroadcasterTests Assert::AreNotEqual(freqSamples.front().timestamp, freqSamples.back().timestamp); Assert::AreNotEqual(freqSamples.front().value, freqSamples.back().value); } + // full 1:1 correspondence between ring creation, ring population, and introspection availability + TEST_METHOD(RingUtilization) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + + // acquire introspection with enhanced wrapper interface + auto pIntro = pComms->GetIntrospectionRoot(); + pmapi::intro::Root intro{ pIntro, [](auto* p) { delete p; } }; + pmapi::EnumMap::Refresh(intro); + auto pMetricMap = pmapi::EnumMap::GetKeyMap(PM_ENUM_METRIC); + + // set telemetry period so we have a known baseline + client.DispatchSync(svc::acts::SetTelemetryPeriod::Params{ .telemetrySamplePeriodMs = 40 }); + + // as a stopgap we target a process in order to trigger service-side telemetry collection + // TODO: remove this when we enable service-side query awareness of connected clients + auto pres = fixture_.LaunchPresenter(); + client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pres.GetId() }); + + // get the store containing adapter telemetry + auto& sys = pComms->GetSystemDataStore(); + + // allow a short warmup + std::this_thread::sleep_for(500ms); + + // build the set of expected rings from introspection + Logger::WriteMessage("Introspection Metrics\n=====================\n"); + std::map introspectionAvailability; + const auto TryAddMetric = [&](PM_METRIC metric) { + auto&& m = intro.FindMetric(metric); + if (m.GetDeviceMetricInfo().empty()) { + return; + } + auto&& dmi = m.GetDeviceMetricInfo().front(); + if (dmi.IsAvailable()) { + const auto arraySize = dmi.GetArraySize(); + introspectionAvailability[metric] = arraySize; + // dump for review in output pane + Logger::WriteMessage(std::format("[{}] {}\n", arraySize, + pMetricMap->at(m.GetId()).narrowName).c_str()); + } + }; + // TODO: replace this with code that iterates over all metrics and automatically + // evaluates all cpu telemetry metrics + TryAddMetric(PM_METRIC_CPU_POWER); + TryAddMetric(PM_METRIC_CPU_TEMPERATURE); + TryAddMetric(PM_METRIC_CPU_UTILIZATION); + TryAddMetric(PM_METRIC_CPU_FREQUENCY); + TryAddMetric(PM_METRIC_CPU_CORE_UTILITY); + Logger::WriteMessage(std::format("Total: {}\n", introspectionAvailability.size()).c_str()); + + // validate that the expected number of rings sets are present in the store + Assert::AreEqual(introspectionAvailability.size(), (size_t)rn::distance(sys.telemetryData.Rings())); + + // validate that exepected rings are present and are populated with samples + for (auto&& [met, size] : introspectionAvailability) { + // array sizes should match + Assert::AreEqual(size, sys.telemetryData.ArraySize(met), + pMetricMap->at(met).wideName.c_str()); + std::visit([&](auto const& rings) { + // for each history ring in set, make sure it has at least one sample in it + for (size_t i = 0; i < size; i++) { + auto& name = pMetricMap->at(met).wideName; + Assert::IsFalse(rings[i].Empty(), + std::format(L"{}[{}]", name, i).c_str()); + auto& sample = rings[i].Newest(); + Logger::WriteMessage(std::format(L"{}[{}]: {}@{}\n", name, i, + sample.value, sample.timestamp).c_str()); + } + }, sys.telemetryData.FindRingVariant(met)); + } + } }; TEST_CLASS(GpuStoreTests) @@ -373,7 +446,7 @@ namespace InterimBroadcasterTests pMetricMap->at(m.GetId()).narrowName).c_str()); } } - Logger::WriteMessage(std::format("Total: {}", introspectionAvailability.size()).c_str()); + Logger::WriteMessage(std::format("Total: {}\n", introspectionAvailability.size()).c_str()); // validate that the expected number of rings sets are present in the store Assert::AreEqual(introspectionAvailability.size(), (size_t)rn::distance(gpu.telemetryData.Rings())); From b8b36e71fbd67bfe5770854e9776d91da7febeb6 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Wed, 10 Dec 2025 15:14:29 -0800 Subject: [PATCH 041/205] Adding in Input Latency Tests --- IntelPresentMon/UnitTests/MetricsCore.cpp | 372 +++++++++++++++++++++- 1 file changed, 371 insertions(+), 1 deletion(-) diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index 09508fb6..50fe7450 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -6495,7 +6495,377 @@ namespace MetricsCoreTests Assert::AreEqual(uint64_t(475'000), state.lastDisplayedSimStartTime, L"lastDisplayedSimStartTime should still reflect P1 until P3 is finalized."); } -}; + }; + // ============================================================================ + // SECTION: Input Latency Tests + // ============================================================================ + + TEST_CLASS(InputLatencyTests) + { + public: + // ======================================================================== + // Test 1: ClickToPhoton - displayed frame uses its own click + // ======================================================================== + TEST_METHOD(InputLatency_ClickToPhoton_DisplayedFrame_UsesOwnClickTime) + { + // Scenario: + // - P1 (displayed frame) has its own mouseClickTime = 400'000 + // - P1 computes msClickToPhotonLatency from click to its own display time + // - No pending click should remain in the chain + // + // Expected: + // - P1's msClickToPhotonLatency uses P1's own click (400'000 -> 1'000'000) + // - state.lastReceivedNotDisplayedMouseClickTime == 0 + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + + // P1: displayed app frame with its own click + FrameData p1{}; + p1.presentStartTime = 500'000; + p1.timeInPresent = 100'000; + p1.mouseClickTime = 400'000; + p1.inputTime = 0; + p1.appSimStartTime = 450'000; + p1.finalState = PresentResult::Presented; + p1.displayed.push_back({ FrameType::Application, 1'000'000 }); + + // P2: next displayed frame + FrameData p2{}; + p2.presentStartTime = 1'050'000; + p2.timeInPresent = 50'000; + p2.mouseClickTime = 0; + p2.inputTime = 0; + p2.finalState = PresentResult::Presented; + p2.displayed.push_back({ FrameType::Application, 1'100'000 }); + + // P1 arrives (pending) + auto p1_pending = ComputeMetricsForPresent(qpc, p1, nullptr, state); + Assert::AreEqual(size_t(0), p1_pending.size(), L"P1 pending should be empty"); + + // P2 arrives, finalizes P1 + auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, state); + auto p2_pending = ComputeMetricsForPresent(qpc, p2, nullptr, state); + + // Assertions for P1 + Assert::AreEqual(size_t(1), p1_final.size()); + Assert::IsTrue(p1_final[0].metrics.msClickToPhotonLatency.has_value(), + L"P1 should have msClickToPhotonLatency"); + + double expected = qpc.DeltaUnsignedMilliSeconds(400'000, 1'000'000); + Assert::AreEqual(expected, p1_final[0].metrics.msClickToPhotonLatency.value(), 0.0001, + L"P1's click-to-photon should use its own click time"); + + // Verify no pending click remains + Assert::AreEqual(uint64_t(0), state.lastReceivedNotDisplayedMouseClickTime, + L"No pending click should remain after P1 used its own click"); + } + + // ======================================================================== + // Test 2: ClickToPhoton - dropped frame carries click to next displayed + // ======================================================================== + TEST_METHOD(InputLatency_ClickToPhoton_DroppedFrame_CarriesClickToNextDisplayed) + { + // Scenario: + // - P1 (dropped, not displayed) has mouseClickTime = 400'000 + // - P1 does not produce msClickToPhotonLatency + // - P1 stores click in lastReceivedNotDisplayedMouseClickTime + // - P2 (displayed, no own click) uses the stored click from P1 + // + // Expected: + // - P1: msClickToPhotonLatency == nullopt + // - After P1: state.lastReceivedNotDisplayedMouseClickTime == 400'000 + // - P2: msClickToPhotonLatency uses stored click (400'000 -> 1'000'000) + // - After P2: state.lastReceivedNotDisplayedMouseClickTime == 0 (consumed) + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + + // P1: dropped frame with click + FrameData p1{}; + p1.presentStartTime = 300'000; + p1.timeInPresent = 50'000; + p1.mouseClickTime = 400'000; + p1.inputTime = 0; + p1.finalState = PresentResult::Discarded; + // displayed is empty (not displayed) + + // P2: first displayed frame (no own click) + FrameData p2{}; + p2.presentStartTime = 900'000; + p2.timeInPresent = 100'000; + p2.mouseClickTime = 0; + p2.inputTime = 0; + p2.finalState = PresentResult::Presented; + p2.displayed.push_back({ FrameType::Application, 1'000'000 }); + + // P3: later frame to finalize P2 + FrameData p3{}; + p3.presentStartTime = 1'050'000; + p3.timeInPresent = 50'000; + p3.finalState = PresentResult::Presented; + p3.displayed.push_back({ FrameType::Application, 1'100'000 }); + + // P1 arrives (dropped) + auto p1_results = ComputeMetricsForPresent(qpc, p1, nullptr, state); + + // Assertions for P1 + Assert::AreEqual(size_t(1), p1_results.size()); + Assert::IsFalse(p1_results[0].metrics.msClickToPhotonLatency.has_value(), + L"P1 (dropped) should not have msClickToPhotonLatency"); + Assert::AreEqual(uint64_t(400'000), state.lastReceivedNotDisplayedMouseClickTime, + L"P1's click should be stored as pending"); + + // P2 arrives (pending) + auto p2_pending = ComputeMetricsForPresent(qpc, p2, nullptr, state); + + // P3 arrives, finalizes P2 + auto p2_final = ComputeMetricsForPresent(qpc, p2, &p3, state); + auto p3_pending = ComputeMetricsForPresent(qpc, p3, nullptr, state); + + // Assertions for P2 + Assert::AreEqual(size_t(1), p2_final.size()); + Assert::IsTrue(p2_final[0].metrics.msClickToPhotonLatency.has_value(), + L"P2 should have msClickToPhotonLatency using P1's stored click"); + + double expected = qpc.DeltaUnsignedMilliSeconds(400'000, 1'000'000); + Assert::AreEqual(expected, p2_final[0].metrics.msClickToPhotonLatency.value(), 0.0001, + L"P2's click-to-photon should use P1's stored click"); + + // Optional: verify pending click is consumed + Assert::AreEqual(uint64_t(0), state.lastReceivedNotDisplayedMouseClickTime, + L"Pending click should be consumed after P2 uses it"); + } + + // ======================================================================== + // Test 3: AllInputPhoton - multiple dropped frames, last input wins + // ======================================================================== + TEST_METHOD(InputLatency_AllInputPhoton_MultipleDroppedFrames_LastInputWins) + { + // Scenario: + // - P1 (dropped) has inputTime = 300'000 + // - P2 (dropped) has inputTime = 450'000 (should override P1) + // - P3 (displayed, no own input) uses the last stored input (450'000) + // + // Expected: + // - P1: msAllInputPhotonLatency == nullopt, state stores 300'000 + // - P2: msAllInputPhotonLatency == nullopt, state updates to 450'000 + // - P3: msAllInputPhotonLatency uses 450'000 (last wins) + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + + // P1: dropped with first input + FrameData p1{}; + p1.presentStartTime = 200'000; + p1.timeInPresent = 50'000; + p1.inputTime = 300'000; + p1.mouseClickTime = 0; + p1.finalState = PresentResult::Discarded; + // displayed empty + + // P2: dropped with later input (overrides P1) + FrameData p2{}; + p2.presentStartTime = 400'000; + p2.timeInPresent = 50'000; + p2.inputTime = 450'000; + p2.mouseClickTime = 0; + p2.finalState = PresentResult::Discarded; + // displayed empty + + // P3: first displayed frame (no own input) + FrameData p3{}; + p3.presentStartTime = 900'000; + p3.timeInPresent = 100'000; + p3.inputTime = 0; + p3.mouseClickTime = 0; + p3.finalState = PresentResult::Presented; + p3.displayed.push_back({ FrameType::Application, 1'000'000 }); + + // P4: later frame to finalize P3 + FrameData p4{}; + p4.presentStartTime = 1'050'000; + p4.timeInPresent = 50'000; + p4.finalState = PresentResult::Presented; + p4.displayed.push_back({ FrameType::Application, 1'100'000 }); + + // P1 arrives (dropped) + auto p1_results = ComputeMetricsForPresent(qpc, p1, nullptr, state); + Assert::AreEqual(size_t(1), p1_results.size()); + Assert::IsFalse(p1_results[0].metrics.msAllInputPhotonLatency.has_value(), + L"P1 (dropped) should not have msAllInputPhotonLatency"); + Assert::AreEqual(uint64_t(300'000), state.lastReceivedNotDisplayedAllInputTime, + L"P1's input should be stored"); + + // P2 arrives (dropped, overrides P1) + auto p2_results = ComputeMetricsForPresent(qpc, p2, nullptr, state); + Assert::AreEqual(size_t(1), p2_results.size()); + Assert::IsFalse(p2_results[0].metrics.msAllInputPhotonLatency.has_value(), + L"P2 (dropped) should not have msAllInputPhotonLatency"); + Assert::AreEqual(uint64_t(450'000), state.lastReceivedNotDisplayedAllInputTime, + L"P2's input should override P1's stored input (last wins)"); + // P3 arrives (pending) + auto p3_pending = ComputeMetricsForPresent(qpc, p3, nullptr, state); + // P4 arrives, finalizes P3 + auto p3_final = ComputeMetricsForPresent(qpc, p3, &p4, state); + auto p4_pending = ComputeMetricsForPresent(qpc, p4, nullptr, state); + + // Assertions for P3 + Assert::AreEqual(size_t(1), p3_final.size()); + Assert::IsTrue(p3_final[0].metrics.msAllInputPhotonLatency.has_value(), + L"P3 should have msAllInputPhotonLatency using last stored input"); + + double expected = qpc.DeltaUnsignedMilliSeconds(450'000, 1'000'000); + Assert::AreEqual(expected, p3_final[0].metrics.msAllInputPhotonLatency.value(), 0.0001, + L"P3's all-input-to-photon should use P2's input (last wins)"); + } + + // ======================================================================== + // Test 4: AllInputPhoton - displayed frame with own input overrides pending + // ======================================================================== + TEST_METHOD(InputLatency_AllInputPhoton_DisplayedFrame_WithOwnInput_OverridesPending) + { + // Scenario: + // - P0 (dropped) seeds pending input = 300'000 + // - P1 (displayed) has its own inputTime = 500'000 + // - P1's own input should override the pending 300'000 + // + // Expected: + // - P0: state.lastReceivedNotDisplayedAllInputTime == 300'000 + // - P1: msAllInputPhotonLatency uses P1's own input (500'000 -> 1'000'000) + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + + // P0: dropped, seeds pending input + FrameData p0{}; + p0.presentStartTime = 200'000; + p0.timeInPresent = 50'000; + p0.inputTime = 300'000; + p0.mouseClickTime = 0; + p0.finalState = PresentResult::Discarded; + // displayed empty + + // P1: displayed with its own input + FrameData p1{}; + p1.presentStartTime = 900'000; + p1.timeInPresent = 100'000; + p1.inputTime = 500'000; + p1.mouseClickTime = 0; + p1.finalState = PresentResult::Presented; + p1.displayed.push_back({ FrameType::Application, 1'000'000 }); + + // P2: later frame to finalize P1 + FrameData p2{}; + p2.presentStartTime = 1'050'000; + p2.timeInPresent = 50'000; + p2.finalState = PresentResult::Presented; + p2.displayed.push_back({ FrameType::Application, 1'100'000 }); + + // P0 arrives (dropped) + auto p0_results = ComputeMetricsForPresent(qpc, p0, nullptr, state); + Assert::AreEqual(uint64_t(300'000), state.lastReceivedNotDisplayedAllInputTime, + L"P0's input should be stored as pending"); + + // P1 arrives (pending) + auto p1_pending = ComputeMetricsForPresent(qpc, p1, nullptr, state); + + // P2 arrives, finalizes P1 + auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, state); + auto p2_pending = ComputeMetricsForPresent(qpc, p2, nullptr, state); + + // Assertions for P1 + Assert::AreEqual(size_t(1), p1_final.size()); + Assert::IsTrue(p1_final[0].metrics.msAllInputPhotonLatency.has_value(), + L"P1 should have msAllInputPhotonLatency using its own input"); + + double expected = qpc.DeltaUnsignedMilliSeconds(500'000, 1'000'000); + Assert::AreEqual(expected, p1_final[0].metrics.msAllInputPhotonLatency.value(), 0.0001, + L"P1's all-input-to-photon should use its own input (500'000), not pending (300'000)"); + } + + // ======================================================================== + // Test 5: InstrumentedInputTime - uses same sim start as animation source (AppProvider) + // ======================================================================== + TEST_METHOD(InputLatency_InstrumentedInputTime_UsesAnimationSimStart_AppProvider) + { + // Scenario: + // - Frame 1: First AppProvider frame (appSimStartTime = 475'000) - seeds state + // - Frame 2: Has inputTime = 500'000, appSimStartTime = 575'000 + // - msInstrumentedInputTime should use Frame 2's animation sim start (575'000) + // + // Expected: + // - After Frame 1: animationErrorSource == AppProvider + // - Frame 2: msInstrumentedInputTime = (575'000 - 500'000) in ms + // - msInstrumentedInputTime uses same sim start as animation + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + // state.animationErrorSource defaults to CpuStart + + // P1: First AppProvider frame (no input) + FrameData p1{}; + p1.presentStartTime = 500'000; + p1.timeInPresent = 100'000; + p1.appSimStartTime = 475'000; + p1.pclSimStartTime = 0; + p1.finalState = PresentResult::Presented; + p1.displayed.push_back({ FrameType::Application, 1'000'000 }); + + // P2: Frame with input (we assert on this) + FrameData p2{}; + p2.presentStartTime = 1'000'000; + p2.timeInPresent = 100'000; + p2.appSimStartTime = 575'000; + p2.pclSimStartTime = 0; + p2.inputTime = 500'000; + p2.mouseClickTime = 500'000; // Using same value for simplicity + p2.finalState = PresentResult::Presented; + p2.displayed.push_back({ FrameType::Application, 1'100'000 }); + + // P3: Later frame to finalize P2 + FrameData p3{}; + p3.presentStartTime = 1'500'000; + p3.timeInPresent = 100'000; + p3.appSimStartTime = 675'000; + p3.finalState = PresentResult::Presented; + p3.displayed.push_back({ FrameType::Application, 1'200'000 }); + + // P1 arrives (pending) + auto p1_pending = ComputeMetricsForPresent(qpc, p1, nullptr, state); + + // P2 arrives, finalizes P1 (switches to AppProvider) + auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, state); + auto p2_pending = ComputeMetricsForPresent(qpc, p2, nullptr, state); + + // Verify state after P1 + Assert::IsTrue(state.animationErrorSource == AnimationErrorSource::AppProvider, + L"Animation source should switch to AppProvider after P1"); + Assert::AreEqual(uint64_t(475'000), state.firstAppSimStartTime, + L"firstAppSimStartTime should be set to P1's appSimStartTime"); + + // P3 arrives, finalizes P2 + auto p2_final = ComputeMetricsForPresent(qpc, p2, &p3, state); + auto p3_pending = ComputeMetricsForPresent(qpc, p3, nullptr, state); + + // Assertions for P2 + Assert::AreEqual(size_t(1), p2_final.size()); + + // Verify P2 has animation time (sanity check we're in AppProvider mode) + Assert::IsTrue(p2_final[0].metrics.msAnimationTime.has_value(), + L"P2 should have msAnimationTime (AppProvider mode)"); + + // Verify msInstrumentedInputTime is present + Assert::IsTrue(p2_final[0].metrics.msInstrumentedInputTime.has_value(), + L"P2 should have msInstrumentedInputTime"); + + // Calculate expected: input -> current sim start (AppProvider) + // currentSimStart for P2 in AppProvider mode = p2.appSimStartTime = 575'000 + double expectedInstr = qpc.DeltaUnsignedMilliSeconds(500'000, 575'000); + Assert::AreEqual(expectedInstr, p2_final[0].metrics.msInstrumentedInputTime.value(), 0.0001, + L"msInstrumentedInputTime should use same sim start as animation (AppProvider)"); + } + }; } \ No newline at end of file From 53c97821312a540c149c84caf4a84a884ca40c7b Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Wed, 10 Dec 2025 16:07:37 -0800 Subject: [PATCH 042/205] Update for Input latency calculations and ULTs. All test passing --- .../CommonUtilities/mc/MetricsCalculator.cpp | 216 +++++++++++++----- IntelPresentMon/UnitTests/MetricsCore.cpp | 21 +- 2 files changed, 171 insertions(+), 66 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 334973b8..5c079d0b 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -315,98 +315,187 @@ namespace pmon::util::metrics std::optional ComputeClickToPhotonLatency( const QpcConverter& qpc, - SwapChainCoreState& chain, + const SwapChainCoreState& chain, const FrameData& present, bool isDisplayed, bool isAppFrame, - uint64_t screenTime) + uint64_t screenTime, + ComputedMetrics::StateDeltas& stateDeltas) { + // Only app frames participate in click-to-photon. if (!isAppFrame) { return std::nullopt; } - if (isDisplayed) { - auto inputTime = chain.lastReceivedNotDisplayedMouseClickTime != 0 ? - chain.lastReceivedNotDisplayedMouseClickTime : - present.mouseClickTime; - if (inputTime != 0) { - return qpc.DeltaUnsignedMilliSeconds(inputTime, screenTime); + uint64_t inputTime = 0; + + // Case 1: This frame *has* a click. + if (present.mouseClickTime != 0) { + inputTime = present.mouseClickTime; + + if (!isDisplayed) { + // Not displayed: stash the click for a future displayed frame. + stateDeltas.lastReceivedNotDisplayedMouseClickTime = inputTime; + return std::nullopt; } - // Reset all last received device time - chain.lastReceivedNotDisplayedMouseClickTime = 0; - return std::nullopt; } - else { - // Update last received device time - if (present.mouseClickTime != 0) { - chain.lastReceivedNotDisplayedMouseClickTime = present.mouseClickTime; - } + // Case 2: No click on this frame, but frame is displayed: see if we can + // reuse a pending click from a previously dropped frame. + else if (isDisplayed && chain.lastReceivedNotDisplayedMouseClickTime != 0) { + inputTime = chain.lastReceivedNotDisplayedMouseClickTime; + stateDeltas.shouldResetInputTimes = true; + } + + // If we still have no inputTime, nothing to compute. + if (inputTime == 0) { return std::nullopt; } + + return qpc.DeltaUnsignedMilliSeconds(inputTime, screenTime); } + std::optional ComputeAllInputToPhotonLatency( const QpcConverter& qpc, - SwapChainCoreState& chain, + const SwapChainCoreState& chain, const FrameData& present, bool isDisplayed, bool isAppFrame, - uint64_t screenTime) + uint64_t screenTime, + ComputedMetrics::StateDeltas& stateDeltas) { + // Only app frames participate in click-to-photon. if (!isAppFrame) { return std::nullopt; } - if (isDisplayed) { - auto inputTime = chain.lastReceivedNotDisplayedAllInputTime != 0 ? - chain.lastReceivedNotDisplayedAllInputTime : - present.inputTime; - if (inputTime != 0) { - return qpc.DeltaUnsignedMilliSeconds(inputTime, screenTime); + uint64_t inputTime = 0; + + // Case 1: This frame *has* a click. + if (present.inputTime != 0) { + inputTime = present.inputTime; + + if (!isDisplayed) { + // Not displayed: stash the click for a future displayed frame. + stateDeltas.lastReceivedNotDisplayedAllInputTime = inputTime; + return std::nullopt; } - // Reset all last received device time - chain.lastReceivedNotDisplayedMouseClickTime = 0; - return std::nullopt; } - else { - // Update last received device time - if (present.inputTime != 0) { - chain.lastReceivedNotDisplayedMouseClickTime = present.inputTime; - } + // Case 2: No click on this frame, but frame is displayed: see if we can + // reuse a pending click from a previously dropped frame. + else if (isDisplayed && chain.lastReceivedNotDisplayedAllInputTime != 0) { + inputTime = chain.lastReceivedNotDisplayedAllInputTime; + stateDeltas.shouldResetInputTimes = true; + } + + // If we still have no inputTime, nothing to compute. + if (inputTime == 0) { return std::nullopt; } + + return qpc.DeltaUnsignedMilliSeconds(inputTime, screenTime); } std::optional ComputeInstrumentedInputToPhotonLatency( const QpcConverter& qpc, - SwapChainCoreState& chain, + const SwapChainCoreState& chain, const FrameData& present, bool isDisplayed, bool isAppFrame, - uint64_t screenTime) + uint64_t screenTime, + ComputedMetrics::StateDeltas& stateDeltas) { + // Only app frames participate in click-to-photon. if (!isAppFrame) { return std::nullopt; } - if (isDisplayed) { - auto inputTime = chain.lastReceivedNotDisplayedAppProviderInputTime != 0 ? - chain.lastReceivedNotDisplayedAppProviderInputTime : - present.appInputSample.first; - if (inputTime != 0) { - return qpc.DeltaUnsignedMilliSeconds(inputTime, screenTime); + uint64_t inputTime = 0; + + // Case 1: This frame *has* a click. + if (present.appInputSample.first != 0) { + inputTime = present.appInputSample.first; + + if (!isDisplayed) { + // Not displayed: stash the click for a future displayed frame. + stateDeltas.lastReceivedNotDisplayedAppProviderInputTime = inputTime; + return std::nullopt; } - // Reset all last received device time - chain.lastReceivedNotDisplayedMouseClickTime = 0; - return std::nullopt; } - else { - // Update last received device time - if (present.appInputSample.first != 0) { - chain.lastReceivedNotDisplayedAppProviderInputTime = present.appInputSample.first; - } + // Case 2: No click on this frame, but frame is displayed: see if we can + // reuse a pending click from a previously dropped frame. + else if (isDisplayed && chain.lastReceivedNotDisplayedAppProviderInputTime != 0) { + inputTime = chain.lastReceivedNotDisplayedAppProviderInputTime; + stateDeltas.shouldResetInputTimes = true; + } + + // If we still have no inputTime, nothing to compute. + if (inputTime == 0) { return std::nullopt; } + + return qpc.DeltaUnsignedMilliSeconds(inputTime, screenTime); + } + + void ApplyStateDeltas( + SwapChainCoreState& chainState, + const ComputedMetrics::StateDeltas& d) + { + // If we consumed pending input from a dropped frame, clear all + // "not displayed" input caches. + if (d.shouldResetInputTimes) + { + chainState.lastReceivedNotDisplayedAllInputTime = 0; + chainState.lastReceivedNotDisplayedMouseClickTime = 0; + chainState.lastReceivedNotDisplayedAppProviderInputTime = 0; + chainState.lastReceivedNotDisplayedPclSimStart = 0; + chainState.lastReceivedNotDisplayedPclInputTime = 0; + } + + // Dropped-frame input for all-input latency + if (d.lastReceivedNotDisplayedAllInputTime) + { + chainState.lastReceivedNotDisplayedAllInputTime = + *d.lastReceivedNotDisplayedAllInputTime; + } + + // Dropped-frame mouse click + if (d.lastReceivedNotDisplayedMouseClickTime) + { + chainState.lastReceivedNotDisplayedMouseClickTime = + *d.lastReceivedNotDisplayedMouseClickTime; + } + + // Dropped-frame app-provider input + if (d.lastReceivedNotDisplayedAppProviderInputTime) + { + chainState.lastReceivedNotDisplayedAppProviderInputTime = + *d.lastReceivedNotDisplayedAppProviderInputTime; + } + + // Dropped-frame PC Latency sim start/input + if (d.newLastReceivedPclSimStart) + { + chainState.lastReceivedNotDisplayedPclSimStart = + *d.newLastReceivedPclSimStart; + } + + if (d.newLastReceivedPclInputTime) + { + chainState.lastReceivedNotDisplayedPclInputTime = + *d.newLastReceivedPclInputTime; + } + + // Accumulated PC latency input→frame-start time + if (d.newAccumulatedInput2FrameStart) + { + chainState.accumulatedInput2FrameStartTime = + *d.newAccumulatedInput2FrameStart; + } + + // NOTE: d.newEmaInput2FrameStart is currently unused because + // SwapChainCoreState does not yet expose an EMA field. Once you + // add that, just wire it up here the same way. } } @@ -490,10 +579,11 @@ namespace pmon::util::metrics isDisplayed, isAppFrame, chainState); + + ApplyStateDeltas(chainState, metrics.stateDeltas); + results.push_back(std::move(metrics)); - // TODO - Check and remove this comment -> - // Matches old ReportMetricsHelper: UpdateChain is called for not-displayed frames. chainState.UpdateAfterPresent(present); return results; @@ -533,6 +623,9 @@ namespace pmon::util::metrics isDisplayed, isAppFrame, chainState); + + ApplyStateDeltas(chainState, metrics.stateDeltas); + results.push_back(std::move(metrics)); } @@ -706,11 +799,12 @@ namespace pmon::util::metrics void CalculateInputLatencyMetrics( const QpcConverter& qpc, - SwapChainCoreState& swapChain, + const SwapChainCoreState& swapChain, const FrameData& present, bool isDisplayed, bool isAppFrame, - FrameMetrics& metrics) + FrameMetrics& metrics, + ComputedMetrics::StateDeltas& stateDeltas) { metrics.msClickToPhotonLatency = ComputeClickToPhotonLatency( qpc, @@ -718,7 +812,8 @@ namespace pmon::util::metrics present, isDisplayed, isAppFrame, - metrics.screenTimeQpc); + metrics.screenTimeQpc, + stateDeltas); metrics.msAllInputPhotonLatency = ComputeAllInputToPhotonLatency( qpc, @@ -726,7 +821,8 @@ namespace pmon::util::metrics present, isDisplayed, isAppFrame, - metrics.screenTimeQpc); + metrics.screenTimeQpc, + stateDeltas); metrics.msInstrumentedInputTime = ComputeInstrumentedInputToPhotonLatency( qpc, @@ -734,7 +830,8 @@ namespace pmon::util::metrics present, isDisplayed, isAppFrame, - metrics.screenTimeQpc); + metrics.screenTimeQpc, + stateDeltas); } ComputedMetrics ComputeFrameMetrics( @@ -781,6 +878,15 @@ namespace pmon::util::metrics screenTime, metrics); + CalculateInputLatencyMetrics( + qpc, + chain, + present, + isDisplayed, + isAppFrame, + metrics, + result.stateDeltas); + metrics.cpuStartQpc = CalculateCPUStart(chain, present); return result; diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index 50fe7450..595efd00 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -6789,17 +6789,17 @@ namespace MetricsCoreTests // ======================================================================== // Test 5: InstrumentedInputTime - uses same sim start as animation source (AppProvider) // ======================================================================== - TEST_METHOD(InputLatency_InstrumentedInputTime_UsesAnimationSimStart_AppProvider) + TEST_METHOD(InputLatency_InstrumentedInputTime_UsesAppInputSample) { // Scenario: // - Frame 1: First AppProvider frame (appSimStartTime = 475'000) - seeds state - // - Frame 2: Has inputTime = 500'000, appSimStartTime = 575'000 - // - msInstrumentedInputTime should use Frame 2's animation sim start (575'000) + // - Frame 2: Has appInputSample = 500'000, appSimStartTime = 575'000 + // - msInstrumentedInputTime should use Frame 2's appInputSample time = 500'000 + // - Frame 2: Display time is 1'100'000 // // Expected: // - After Frame 1: animationErrorSource == AppProvider - // - Frame 2: msInstrumentedInputTime = (575'000 - 500'000) in ms - // - msInstrumentedInputTime uses same sim start as animation + // - Frame 2: msInstrumentedInputTime = (1'100'000 - 500'000) in ms QpcConverter qpc(10'000'000, 0); SwapChainCoreState state{}; @@ -6820,8 +6820,8 @@ namespace MetricsCoreTests p2.timeInPresent = 100'000; p2.appSimStartTime = 575'000; p2.pclSimStartTime = 0; - p2.inputTime = 500'000; - p2.mouseClickTime = 500'000; // Using same value for simplicity + p2.appInputSample.first = 500'000; // Using same value for simplicity + p2.appInputSample.second = InputDeviceType::Mouse; p2.finalState = PresentResult::Presented; p2.displayed.push_back({ FrameType::Application, 1'100'000 }); @@ -6861,11 +6861,10 @@ namespace MetricsCoreTests Assert::IsTrue(p2_final[0].metrics.msInstrumentedInputTime.has_value(), L"P2 should have msInstrumentedInputTime"); - // Calculate expected: input -> current sim start (AppProvider) - // currentSimStart for P2 in AppProvider mode = p2.appSimStartTime = 575'000 - double expectedInstr = qpc.DeltaUnsignedMilliSeconds(500'000, 575'000); + // Calculate expected: app input -> p2 screen + double expectedInstr = qpc.DeltaUnsignedMilliSeconds(500'000, 1'100'000); Assert::AreEqual(expectedInstr, p2_final[0].metrics.msInstrumentedInputTime.value(), 0.0001, - L"msInstrumentedInputTime should use same sim start as animation (AppProvider)"); + L"msInstrumentedInputTime should be P2 app input time to P2 screen time"); } }; } \ No newline at end of file From 720da27b40262ad4f303f0bddee98a61f19c5348 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Thu, 11 Dec 2025 13:21:19 -0800 Subject: [PATCH 043/205] PCLatency changes in place --- .../CommonUtilities/mc/MetricsCalculator.cpp | 95 ++++++++++++++++++- .../CommonUtilities/mc/MetricsCalculator.h | 2 +- .../CommonUtilities/mc/SwapChainState.h | 3 + 3 files changed, 96 insertions(+), 4 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 5c079d0b..9a666ab1 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -4,6 +4,7 @@ #include "../PresentData/PresentMonTraceConsumer.hpp" #include "../IntelPresentMon/PresentMonUtils/StreamFormat.h" +#include "../Math.h" namespace pmon::util::metrics { @@ -493,9 +494,12 @@ namespace pmon::util::metrics *d.newAccumulatedInput2FrameStart; } - // NOTE: d.newEmaInput2FrameStart is currently unused because - // SwapChainCoreState does not yet expose an EMA field. Once you - // add that, just wire it up here the same way. + // Running EMA of PC latency input to frame-start time + if (d.newInput2FrameStartEma) + { + chainState.Input2FrameStartTimeEma = + *d.newInput2FrameStartEma; + } } } @@ -834,6 +838,83 @@ namespace pmon::util::metrics stateDeltas); } + std::optional CalculatePcLatency( + const QpcConverter& qpc, + const SwapChainCoreState& chain, + const FrameData& present, + bool isDisplayed, + uint64_t screenTime, + ComputedMetrics::StateDeltas& stateDeltas) + { + if (!isDisplayed) { + if (present.pclSimStartTime != 0) { + if (present.pclInputPingTime != 0) { + // This frame was dropped but we have valid pc latency input and simulation start + // times. Calculate the initial input to sim start time + stateDeltas.newAccumulatedInput2FrameStart = qpc.DeltaUnsignedMilliSeconds( + present.pclInputPingTime, + present.pclSimStartTime); + } + else if (chain.accumulatedInput2FrameStartTime != 0.f) { + // This frame was also dropped and there is no pc latency input time. However, since we have + // accumulated time this means we have a pending input that has had multiple dropped frames + // and has not yet hit the screen. Calculate the time between the last not displayed sim start and + // this sim start and add it to our accumulated total + stateDeltas.newAccumulatedInput2FrameStart = chain.accumulatedInput2FrameStartTime + + qpc.DeltaUnsignedMilliSeconds( + chain.lastReceivedNotDisplayedPclSimStart, + present.pclSimStartTime); + } + stateDeltas.newLastReceivedPclSimStart = present.pclSimStartTime; + } + return std::nullopt; + } + + // Check to see if we have a valid PC Latency sim start time + if (present.pclSimStartTime != 0) { + if (present.pclInputPingTime != 0) { + // Both the pclSimStartTime and pclInputPingTime are valid, use them to update + // the Input to Frame Start EMA. Store in state deltas for later application. + + stateDeltas.newInput2FrameStartEma = pmon::util::CalculateEma( + chain.accumulatedInput2FrameStartTime, + qpc.DeltaUnsignedMilliSeconds(present.pclInputPingTime, present.pclSimStartTime), + 0.1); + + // Defensively clear the tracking variables for when we have a dropped frame with a pc latency input + stateDeltas.newAccumulatedInput2FrameStart = 0.0; + stateDeltas.newLastReceivedPclSimStart = 0; + } + else { + // This frame was displayed but we don't have a pc latency input time. However, there is accumulated time + // so there is a pending input that will now hit the screen. Add in the time from the last not + // displayed pc simulation start to this frame's pc simulation start. + stateDeltas.newAccumulatedInput2FrameStart = chain.accumulatedInput2FrameStartTime + + qpc.DeltaUnsignedMilliSeconds( + chain.lastReceivedNotDisplayedPclSimStart, + present.pclSimStartTime); + + stateDeltas.newInput2FrameStartEma = pmon::util::CalculateEma( + chain.Input2FrameStartTimeEma, + *stateDeltas.newAccumulatedInput2FrameStart, + 0.1); + + // Reset the tracking variables for when we have a dropped frame with a pc latency input + stateDeltas.newAccumulatedInput2FrameStart = 0.0; + stateDeltas.newLastReceivedPclSimStart = 0; + } + } + + auto simStartTime = present.pclSimStartTime != 0 ? present.pclSimStartTime : chain.lastSimStartTime; + + if (stateDeltas.newInput2FrameStartEma.has_value() && stateDeltas.newInput2FrameStartEma.value() != 0.0 && simStartTime != 0) { + return stateDeltas.newInput2FrameStartEma.value() + qpc.DeltaSignedMilliSeconds(simStartTime, screenTime); + } + else { + return std::nullopt; + } + } + ComputedMetrics ComputeFrameMetrics( const QpcConverter& qpc, const FrameData& present, @@ -887,6 +968,14 @@ namespace pmon::util::metrics metrics, result.stateDeltas); + metrics.msPcLatency = CalculatePcLatency( + qpc, + chain, + present, + isDisplayed, + screenTime, + result.stateDeltas); + metrics.cpuStartQpc = CalculateCPUStart(chain, present); return result; diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h index 26b2e320..49db79a5 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h @@ -16,7 +16,7 @@ namespace pmon::util::metrics // State changes to apply to SwapChain struct StateDeltas { - std::optional newEmaInput2FrameStart; + std::optional newInput2FrameStartEma; std::optional newAccumulatedInput2FrameStart; std::optional newLastReceivedPclSimStart; std::optional newLastReceivedPclInputTime; diff --git a/IntelPresentMon/CommonUtilities/mc/SwapChainState.h b/IntelPresentMon/CommonUtilities/mc/SwapChainState.h index 7a93a062..6e7d6957 100644 --- a/IntelPresentMon/CommonUtilities/mc/SwapChainState.h +++ b/IntelPresentMon/CommonUtilities/mc/SwapChainState.h @@ -65,6 +65,9 @@ struct SwapChainCoreState { // Accumulated PC latency input to frame start time due to dropped Present() calls double accumulatedInput2FrameStartTime = 0.0; + // Input to Frame Start EMA + double Input2FrameStartTimeEma = 0.0; + // NVIDIA Specific Tracking uint64_t lastDisplayedFlipDelay = 0; From 1d43bb66fa23aa6835c36b4ca1831d99c081c533 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Thu, 11 Dec 2025 15:39:00 -0800 Subject: [PATCH 044/205] First PCLatency test added. Passing. --- IntelPresentMon/UnitTests/MetricsCore.cpp | 237 ++++++++++++++++++++++ 1 file changed, 237 insertions(+) diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index 595efd00..221f52f7 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -6867,4 +6867,241 @@ namespace MetricsCoreTests L"msInstrumentedInputTime should be P2 app input time to P2 screen time"); } }; + TEST_CLASS(PcLatencyTests) + { + public: + + TEST_METHOD(PcLatency_PendingSequence_DroppedDroppedDisplayed_P0P1P2P3) + { + // This test mimics the real ReportMetrics pipeline for a single swapchain, + // focusing on the PC Latency accumulation across *dropped* frames, and its + // completion when the corresponding frame finally reaches the screen. + // + // We use four presents: + // + // P0: DROPPED + // - PclInputPingTime = 10'000 + // - PclSimStartTime = 20'000 + // -> initializes accumulatedInput2FrameStartTime with Δ(PING0, SIM0). + // + // P1: DROPPED + // - PclInputPingTime = 0 + // - PclSimStartTime = 30'000 + // -> extends accumulatedInput2FrameStartTime with Δ(SIM0, SIM1). + // + // P2: DISPLAYED + // - PclInputPingTime = 0 + // - PclSimStartTime = 40'000 + // - ScreenTime = 50'000 + // + // P3: DISPLAYED + // - no PCL data; used only as "nextDisplayed" for P2 to mimic ReportMetrics. + // + // QPC frequency = 10 MHz. + // + // Timing (ticks): + // PING0 = 10'000 + // SIM0 = 20'000 + // SIM1 = 30'000 + // SIM2 = 40'000 + // SCR2 = 50'000 + // + // Δ(PING0, SIM0) = 10'000 ticks = 1.0 ms + // Δ(SIM0, SIM1) = 10'000 ticks = 1.0 ms + // Δ(SIM1, SIM2) = 10'000 ticks = 1.0 ms + // => full input→frame-start for this chain = 3.0 ms + // + // Δ(SIM2, SCR2) = 10'000 ticks = 1.0 ms + // + // Legacy behavior: + // - AccumulatedInput2FrameStartTime builds to 3.0 ms over P0/P1/P2. + // - EMA seeds from that full 3.0 ms sample when P2 finally completes. + // - PC Latency for P2 ≈ 3.0 ms (input→frame-start) + 1.0 ms (sim→screen). + // + // The pipeline calls we mimic: + // + // P0 arrives (dropped): + // - ComputeMetricsForPresent(P0, nullptr, state) // Case 1, not displayed + // + // P1 arrives (dropped): + // - ComputeMetricsForPresent(P1, nullptr, state) // Case 1, not displayed + // + // P2 arrives (displayed): + // - ComputeMetricsForPresent(P2, nullptr, state) // Case 2, pending only (no metrics yet) + // + // P3 arrives (displayed): + // - ComputeMetricsForPresent(P2, &P3, state) // Case 3, finalize P2 + // - ComputeMetricsForPresent(P3, nullptr, state) // Case 2, pending = P3 + // + // We verify: + // - P0 & P1 produce no msPcLatency (dropped frames). + // - accumulatedInput2FrameStartTime grows after P0 and P1. + // - P2's first call (next=nullptr) produces no metrics and does NOT + // disturb the accumulatedInput2FrameStartTime. + // - The final P2 call (with next=P3) produces a non-null msPcLatency, + // and resets accumulatedInput2FrameStartTime and lastReceivedNotDisplayedPclSimStart + // to 0, matching the legacy PCL behavior. + // + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + + const uint32_t PROCESS_ID = 1234; + const uint64_t SWAPCHAIN = 0xABC0; + + // -------------------------------------------------------------------- + // P0: DROPPED, first PCL frame with Ping+Sim + // -------------------------------------------------------------------- + FrameData p0{}; + p0.processId = PROCESS_ID; + p0.swapChainAddress = SWAPCHAIN; + p0.presentStartTime = 0; + p0.timeInPresent = 0; + p0.readyTime = 0; + p0.appSimStartTime = 0; + + p0.pclInputPingTime = 10'000; // PING0 + p0.pclSimStartTime = 20'000; // SIM0 + + p0.setFinalState(PresentResult::Discarded); + p0.displayed.clear(); // not displayed + + // P0 arrival -> Case 1 (not displayed), process immediately. + auto p0_metrics_list = ComputeMetricsForPresent(qpc, p0, nullptr, state); + Assert::AreEqual(size_t(1), p0_metrics_list.size(), + L"P0: not-displayed present should produce a single metrics record."); + + const auto& p0_metrics = p0_metrics_list[0].metrics; + + // Dropped frames never report PC latency directly. + Assert::IsFalse(p0_metrics.msPcLatency.has_value(), + L"P0: dropped frame should not report msPcLatency."); + + // Accumulator should have been initialized from Ping0 -> Sim0. + Assert::IsTrue(state.accumulatedInput2FrameStartTime > 0.0, + L"P0: accumulatedInput2FrameStartTime should be initialized and > 0."); + Assert::AreEqual(uint64_t(20'000), state.lastReceivedNotDisplayedPclSimStart, + L"P0: lastReceivedNotDisplayedPclSimStart should match P0's pclSimStartTime (20'000)."); + + const double accumAfterP0 = state.accumulatedInput2FrameStartTime; + + // -------------------------------------------------------------------- + // P1: DROPPED, continuation of same PCL chain (Sim only) + // -------------------------------------------------------------------- + FrameData p1{}; + p1.processId = PROCESS_ID; + p1.swapChainAddress = SWAPCHAIN; + p1.presentStartTime = 0; + p1.timeInPresent = 0; + p1.readyTime = 0; + p1.appSimStartTime = 0; + + p1.pclInputPingTime = 0; // no new ping + p1.pclSimStartTime = 30'000; // SIM1 + + p1.setFinalState(PresentResult::Discarded); + p1.displayed.clear(); // not displayed + + auto p1_metrics_list = ComputeMetricsForPresent(qpc, p1, nullptr, state); + Assert::AreEqual(size_t(1), p1_metrics_list.size(), + L"P1: not-displayed present should produce a single metrics record."); + + const auto& p1_metrics = p1_metrics_list[0].metrics; + + Assert::IsFalse(p1_metrics.msPcLatency.has_value(), + L"P1: dropped frame should not report msPcLatency."); + + // Accumulator should have grown: now includes SIM0->SIM1 as well. + Assert::IsTrue(state.accumulatedInput2FrameStartTime > accumAfterP0, + L"P1: accumulatedInput2FrameStartTime should be greater than after P0."); + Assert::AreEqual(uint64_t(30'000), state.lastReceivedNotDisplayedPclSimStart, + L"P1: lastReceivedNotDisplayedPclSimStart should match P1's pclSimStartTime (30'000)."); + + const double accumAfterP1 = state.accumulatedInput2FrameStartTime; + + // -------------------------------------------------------------------- + // P2: DISPLAYED, Sim only – this is the frame where the pending input + // will finally be visible on-screen. However, the metrics for P2 are + // only finalized when we know P3 (nextDisplayed), just like in the + // ReportMetrics pipeline. + // -------------------------------------------------------------------- + FrameData p2{}; + p2.processId = PROCESS_ID; + p2.swapChainAddress = SWAPCHAIN; + p2.presentStartTime = 0; + p2.timeInPresent = 0; + p2.readyTime = 0; + p2.appSimStartTime = 0; + + p2.pclInputPingTime = 0; // no new ping + p2.pclSimStartTime = 40'000; // SIM2 + + p2.setFinalState(PresentResult::Presented); + p2.displayed.clear(); + p2.displayed.push_back({ FrameType::Application, 50'000 }); // SCR2 + + // P2 arrival: Case 2 (displayed, no nextDisplayed), becomes pending. + auto p2_phase1 = ComputeMetricsForPresent(qpc, p2, nullptr, state); + Assert::AreEqual(size_t(0), p2_phase1.size(), + L"P2 (phase 1): first call with nextDisplayed=nullptr should produce no metrics (pending only)."); + + // The pending call MUST NOT disturb the accumulated PCL chain. + Assert::AreEqual(accumAfterP1, state.accumulatedInput2FrameStartTime, 1e-9, + L"P2 (phase 1): accumulatedInput2FrameStartTime should remain unchanged while pending."); + Assert::AreEqual(uint64_t(30'000), state.lastReceivedNotDisplayedPclSimStart, + L"P2 (phase 1): lastReceivedNotDisplayedPclSimStart should remain at P1's sim start (30'000)."); + + // -------------------------------------------------------------------- + // P3: DISPLAYED, used only as nextDisplayed when finalizing P2. + // -------------------------------------------------------------------- + FrameData p3{}; + p3.processId = PROCESS_ID; + p3.swapChainAddress = SWAPCHAIN; + p3.presentStartTime = 0; + p3.timeInPresent = 0; + p3.readyTime = 0; + p3.appSimStartTime = 0; + + p3.pclInputPingTime = 0; + p3.pclSimStartTime = 0; // no PCL for P3 itself + + p3.setFinalState(PresentResult::Presented); + p3.displayed.clear(); + p3.displayed.push_back({ FrameType::Application, 60'000 }); // some later screen time + + // P3 arrival: + // 1) Flush pending P2 using P3 as nextDisplayed -> Case 3, finalize P2. + auto p2_final = ComputeMetricsForPresent(qpc, p2, &p3, state); + Assert::AreEqual(size_t(1), p2_final.size(), + L"P2 (final): expected exactly one metrics record when flushing with nextDisplayed=P3."); + const auto& p2_metrics = p2_final[0].metrics; + + // 2) Now process P3's arrival as pending -> Case 2 with next=nullptr. + auto p3_phase1 = ComputeMetricsForPresent(qpc, p3, nullptr, state); + Assert::AreEqual(size_t(0), p3_phase1.size(), + L"P3 (phase 1): first call with nextDisplayed=nullptr should produce no metrics (pending only)."); + + // -------------------------------------------------------------------- + // Assertions for the P2 finalization (this is where PC Latency must appear). + // -------------------------------------------------------------------- + + // Precondition: we had a non-zero accumulated input→frame-start before finalizing P2. + Assert::IsTrue(accumAfterP1 > 0.0, + L"Precondition: expected non-zero accumulatedInput2FrameStartTime before P2 finalization."); + + // 1) PC Latency should be populated and positive for P2 when it finally + // reaches the screen after the dropped chain. + Assert::IsTrue(p2_metrics.msPcLatency.has_value(), + L"P2 (final): msPcLatency should be populated for the displayed frame completing the dropped PCL chain."); + Assert::IsTrue(p2_metrics.msPcLatency.value() > 0.0, + L"P2 (final): msPcLatency should be positive."); + + // 2) After completion, the accumulated input→frame-start time and the + // last-not-displayed PCL sim start should be reset to zero, matching + // the legacy PCL behavior. + Assert::AreEqual(0.0, state.accumulatedInput2FrameStartTime, 1e-9, + L"P2 (final): accumulatedInput2FrameStartTime should be reset to 0 after completion."); + Assert::AreEqual(uint64_t{ 0 }, state.lastReceivedNotDisplayedPclSimStart, + L"P2 (final): lastReceivedNotDisplayedPclSimStart should be reset to 0 after completion."); + } + }; } \ No newline at end of file From 3f75b1b2a3d2b7d7abb0b869bb02c9126ac17339 Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 12 Dec 2025 12:53:38 +0900 Subject: [PATCH 045/205] tracking and untracking frame data stores --- .../Interprocess/source/DataStores.h | 4 +- .../source/FrameDataPlaceholder.h | 142 ++++++------------ .../InterimBroadcasterTests.cpp | 50 ++++++ .../PresentMonAPI2Tests/TestCommands.h | 5 +- .../ActionExecutionContext.cpp | 4 +- .../ActionExecutionContext.h | 5 +- .../PresentMonService/ActionServer.cpp | 6 +- .../PresentMonService/ActionServer.h | 4 +- .../PresentMonService/FrameBroadcaster.h | 119 +++++++++++++++ .../MockPresentMonSession.cpp | 3 +- .../PresentMonService/MockPresentMonSession.h | 4 +- .../PresentMonService/PMMainThread.cpp | 16 +- .../PresentMonService/PresentMon.cpp | 7 +- .../PresentMonService/PresentMon.h | 8 +- .../PresentMonService.vcxproj | 1 + .../PresentMonService.vcxproj.filters | 1 + .../PresentMonService/PresentMonSession.cpp | 1 + .../PresentMonService/PresentMonSession.h | 4 + .../RealtimePresentMonSession.cpp | 3 +- .../RealtimePresentMonSession.h | 2 +- .../PresentMonService/acts/OpenSession.h | 4 +- .../PresentMonService/acts/StartTracking.h | 6 +- 22 files changed, 271 insertions(+), 128 deletions(-) create mode 100644 IntelPresentMon/PresentMonService/FrameBroadcaster.h diff --git a/IntelPresentMon/Interprocess/source/DataStores.h b/IntelPresentMon/Interprocess/source/DataStores.h index ab18d750..89272908 100644 --- a/IntelPresentMon/Interprocess/source/DataStores.h +++ b/IntelPresentMon/Interprocess/source/DataStores.h @@ -19,7 +19,7 @@ namespace pmon::ipc static constexpr size_t virtualSegmentSize = 50'000'000; FrameDataStore(ShmSegmentManager& segMan, size_t cap) : - frameData{ cap, segMan.get_allocator() }, + frameData{ cap, segMan.get_allocator() }, statics{ .applicationName{ segMan.get_allocator() } } {} struct Statics @@ -27,7 +27,7 @@ namespace pmon::ipc uint32_t processId; ShmString applicationName; } statics; - ShmRing frameData; + ShmRing frameData; }; struct GpuDataStore diff --git a/IntelPresentMon/Interprocess/source/FrameDataPlaceholder.h b/IntelPresentMon/Interprocess/source/FrameDataPlaceholder.h index ee11286b..6c4bc802 100644 --- a/IntelPresentMon/Interprocess/source/FrameDataPlaceholder.h +++ b/IntelPresentMon/Interprocess/source/FrameDataPlaceholder.h @@ -1,106 +1,56 @@ #pragma once #include "../../../PresentData/PresentMonTraceConsumer.hpp" +#include +#include namespace pmon::ipc { // temporary; replace with structure dictated by new PresentMon calculation library - struct PmFrameData - { - uint64_t PresentStartTime; // QPC value of the first event related to the - // Present (D3D9, DXGI, or DXGK Present_Start) - uint32_t ProcessId; // ID of the process that presented - uint32_t ThreadId; // ID of the thread that presented - uint64_t TimeInPresent; // QPC duration between runtime present start and end - uint64_t GPUStartTime; // QPC value when the frame's first DMA packet started - uint64_t ReadyTime; // QPC value when the frame's last DMA packet completed - - uint64_t GPUDuration; // QPC duration during which a frame's DMA packet was - // running on any node - uint64_t GPUVideoDuration; // QPC duration during which a frame's DMA packet was - // running on a video node (if mTrackGPUVideo==true) - uint64_t InputTime; // Earliest QPC value for all keyboard/mouse input used by this frame - uint64_t MouseClickTime; // Earliest QPC value when the mouse was clicked and used by this frame + struct FrameData { + // Timing Data + uint64_t presentStartTime = 0; + uint64_t readyTime = 0; + uint64_t timeInPresent = 0; + uint64_t gpuStartTime = 0; + uint64_t gpuDuration = 0; + uint64_t gpuVideoDuration = 0; // Used to track the application work when Intel XeSS-FG is enabled - uint64_t AppPropagatedPresentStartTime; // Propogated QPC value of the first event related to the Present (D3D9, DXGI, or DXGK Present_Start) - uint64_t AppPropagatedTimeInPresent; // Propogated QPC duration of the Present call (only applicable for D3D9/DXGI) - uint64_t AppPropagatedGPUStartTime; // Propogated QPC value when the frame's first DMA packet started - uint64_t AppPropagatedReadyTime; // Propogated QPC value when the frame's last DMA packet completed - uint64_t AppPropagatedGPUDuration; // Propogated QPC duration during which a frame's DMA packet was running on - uint64_t AppPropagatedGPUVideoDuration; // Propogated QPC duration during which a frame's DMA packet was running on - // a video node (if mTrackGPUVideo==true) - - uint64_t AppSleepStartTime; // QPC value of app sleep start time provided by Intel App Provider - uint64_t AppSleepEndTime; // QPC value of app sleep end time provided by Intel App Provider - uint64_t AppSimStartTime; // QPC value of app sim start time provided by Intel App Provider - uint64_t AppSimEndTime; // QPC value of app sim end time provided by Intel App Provider - uint64_t AppRenderSubmitStartTime; // QPC value of app render submit start time provided by Intel App Provider - uint64_t AppRenderSubmitEndTime; // QPC value of app render submit end time provided by Intel App Provider - uint64_t AppPresentStartTime; // QPC value of app present start time provided by Intel App Provider - uint64_t AppPresentEndTime; // QPC value of app present end time provided by Intel App Provider - uint64_t AppInputTime; // QPC value of app input time provided by Intel App Provider - InputDeviceType AppInputType; // Input type provided by Intel App Provider - - uint64_t PclInputPingTime; // QPC value of input ping time provided by the PC Latency ETW event - uint64_t PclSimStartTime; // QPC value of app sim start time provided by the PC Latency ETW event - uint64_t FlipDelay; // QPC timestamp delta of FlipDelay calculated using the NV DisplayDriver FlipRequest event. - uint32_t FlipToken; // Flip token from the NV DisplayDriver FlipRequest event. - - // Extra present parameters obtained through DXGI or D3D9 present - uint64_t SwapChainAddress; - int32_t SyncInterval; - uint32_t PresentFlags; - - // (FrameType, DisplayedQPC) for each time the frame was displayed - uint64_t Displayed_ScreenTime[16]; - FrameType Displayed_FrameType[16]; - uint32_t DisplayedCount; - - // Keys used to index into PMTraceConsumer's tracking data structures: - uint64_t CompositionSurfaceLuid; // mPresentByWin32KPresentHistoryToken - uint64_t Win32KPresentCount; // mPresentByWin32KPresentHistoryToken - uint64_t Win32KBindId; // mPresentByWin32KPresentHistoryToken - uint64_t DxgkPresentHistoryToken; // mPresentByDxgkPresentHistoryToken - uint64_t - DxgkPresentHistoryTokenData; // mPresentByDxgkPresentHistoryTokenData - uint64_t DxgkContext; // mPresentByDxgkContext - uint64_t Hwnd; // mLastPresentByWindow - uint32_t QueueSubmitSequence; // mPresentBySubmitSequence - uint32_t RingIndex; // mTrackedPresents and mCompletedPresents - - // Properties deduced by watching events through present pipeline - uint32_t DestWidth; - uint32_t DestHeight; - uint32_t DriverThreadId; - - uint32_t FrameId; // ID for the logical frame that this Present is associated with. - - Runtime Runtime; - PresentMode PresentMode; - PresentResult FinalState; - InputDeviceType InputType; - FrameType FrameType; - - bool SupportsTearing; - bool WaitForFlipEvent; - bool WaitForMPOFlipEvent; - bool SeenDxgkPresent; - bool SeenWin32KEvents; - bool SeenInFrameEvent; // This present has gotten a Win32k TokenStateChanged - // event into InFrame state - bool GpuFrameCompleted; // This present has already seen an event that caused - // GpuTrace::CompleteFrame() to be called. - bool IsCompleted; // All expected events have been observed - bool IsLost; // This PresentEvent was found in an unexpected state or is too - // old - - // Whether this PresentEvent is currently stored in - // PMTraceConsumer::mPresentsWaitingForDWM - bool PresentInDwmWaitingStruct; - - // QPC time of last presented frame - uint64_t last_present_qpc; - // QPC time of the last displayed frame - uint64_t last_displayed_qpc; + uint64_t appPropagatedPresentStartTime = 0; + uint64_t appPropagatedTimeInPresent = 0; + uint64_t appPropagatedGPUStartTime = 0; + uint64_t appPropagatedReadyTime = 0; + uint64_t appPropagatedGPUDuration = 0; + uint64_t appPropagatedGPUVideoDuration = 0; + + // Instrumented Timestamps + uint64_t appSimStartTime = 0; + uint64_t appSleepStartTime = 0; + uint64_t appSleepEndTime = 0; + uint64_t appRenderSubmitStartTime = 0; + uint64_t appRenderSubmitEndTime = 0; + uint64_t appPresentStartTime = 0; + uint64_t appPresentEndTime = 0; + std::pair appInputSample; // time, input type + + // Input Device Timestamps + uint64_t inputTime = 0; // All input devices + uint64_t mouseClickTime = 0; // Mouse click specific + + boost::container::static_vector, 10> displayed; + + // PC Latency data + uint64_t pclSimStartTime = 0; + uint64_t pclInputPingTime = 0; + uint64_t flipDelay = 0; + uint32_t flipToken = 0; + + // Metadata + PresentResult finalState; + uint32_t processId = 0; + uint32_t threadId = 0; + uint64_t swapChainAddress = 0; + uint32_t frameId = 0; + uint32_t appFrameId = 0; }; } diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 640c6638..7232b59e 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -470,4 +470,54 @@ namespace InterimBroadcasterTests } } }; + + TEST_CLASS(FrameStoreTests) + { + TestFixture fixture_; + public: + TEST_METHOD_INITIALIZE(Setup) + { + fixture_.Setup(); + } + TEST_METHOD_CLEANUP(Cleanup) + { + fixture_.Cleanup(); + } + // static store + TEST_METHOD(StaticData) + { + Assert::IsTrue(false); + } + TEST_METHOD(TrackUntrack) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + + // launch target and track it + auto pres = fixture_.LaunchPresenter(); + client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pres.GetId() }); + + // verify the store exists + pComms->OpenFrameDataStore(pres.GetId()); + + // verify the service tracking, as expected + { + const auto sta = fixture_.service->QueryStatus(); + Assert::AreEqual(1ull, sta.trackedPids.size()); + Assert::IsTrue(sta.trackedPids.contains(pres.GetId())); + } + + // stop tracking + client.DispatchSync(svc::acts::StopTracking::Params{ .targetPid = pres.GetId() }); + + // close the segment + pComms->CloseFrameDataStore(pres.GetId()); + + // verify the service not tracking, as expected + Assert::AreEqual(0ull, fixture_.service->QueryStatus().trackedPids.size()); + + // verify segment can no longer be opened + Assert::ExpectException([&] {pComms->OpenFrameDataStore(pres.GetId()); }); + } + }; } diff --git a/IntelPresentMon/PresentMonAPI2Tests/TestCommands.h b/IntelPresentMon/PresentMonAPI2Tests/TestCommands.h index 7879b699..7ea92572 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/TestCommands.h +++ b/IntelPresentMon/PresentMonAPI2Tests/TestCommands.h @@ -13,7 +13,10 @@ namespace pmon::test { struct Status { + // old streamer tracking std::set nsmStreamedPids; + // new ipc tracking + std::set trackedPids; uint32_t activeAdapterId; uint32_t telemetryPeriodMs; std::optional etwFlushPeriodMs; @@ -21,7 +24,7 @@ namespace pmon::test template void serialize(Archive& ar) { - ar(nsmStreamedPids, activeAdapterId, telemetryPeriodMs, etwFlushPeriodMs); + ar(nsmStreamedPids, trackedPids, activeAdapterId, telemetryPeriodMs, etwFlushPeriodMs); } }; } diff --git a/IntelPresentMon/PresentMonService/ActionExecutionContext.cpp b/IntelPresentMon/PresentMonService/ActionExecutionContext.cpp index 44ac3ff0..d25fa2dc 100644 --- a/IntelPresentMon/PresentMonService/ActionExecutionContext.cpp +++ b/IntelPresentMon/PresentMonService/ActionExecutionContext.cpp @@ -13,8 +13,8 @@ namespace pmon::svc::acts etw.CancelLogSession(id); } } - for (auto& tracked : stx.trackedPids) { - pPmon->StopStreaming(stx.remotePid, tracked); + for (auto&&[pid, pSeg] : stx.trackedPids) { + pPmon->StopStreaming(stx.remotePid, pid); } stx.requestedTelemetryPeriodMs.reset(); UpdateTelemetryPeriod(); diff --git a/IntelPresentMon/PresentMonService/ActionExecutionContext.h b/IntelPresentMon/PresentMonService/ActionExecutionContext.h index ff9c1b55..bb099be6 100644 --- a/IntelPresentMon/PresentMonService/ActionExecutionContext.h +++ b/IntelPresentMon/PresentMonService/ActionExecutionContext.h @@ -10,6 +10,7 @@ #include #include "PresentMon.h" #include "Service.h" +#include "FrameBroadcaster.h" namespace pmon::svc::acts @@ -23,7 +24,7 @@ namespace pmon::svc::acts uint32_t remotePid = 0; uint32_t nextCommandToken = 0; // custom items - std::set trackedPids; + std::map> trackedPids; std::set etwLogSessionIds; std::optional requestedAdapterId; std::optional requestedTelemetryPeriodMs; @@ -40,8 +41,6 @@ namespace pmon::svc::acts Service* pSvc = nullptr; PresentMon* pPmon = nullptr; const std::unordered_map* pSessionMap = nullptr; - std::string shmPrefix; - std::string shmSalt; std::optional responseWriteTimeoutMs; // functions diff --git a/IntelPresentMon/PresentMonService/ActionServer.cpp b/IntelPresentMon/PresentMonService/ActionServer.cpp index 3ef78b86..81f44cd1 100644 --- a/IntelPresentMon/PresentMonService/ActionServer.cpp +++ b/IntelPresentMon/PresentMonService/ActionServer.cpp @@ -13,16 +13,14 @@ namespace pmon::svc using namespace util; using namespace ipc; - ActionServer::ActionServer(Service* pSvc, PresentMon* pPmon, std::string shmPrefix, - std::string shmSalt, std::optional pipeName) + ActionServer::ActionServer(Service* pSvc, PresentMon* pPmon, std::optional pipeName) { // if we have a pipe name override, that indicates we don't need special permissions auto sec = pipe::DuplexPipe::GetSecurityString(pipeName ? pipe::SecurityMode::Child : pipe::SecurityMode::Service); // construct (and start) the server pImpl_ = std::make_shared>( - acts::ActionExecutionContext{ .pSvc = pSvc, .pPmon = pPmon, - .shmPrefix = std::move(shmPrefix), .shmSalt = std::move(shmSalt) }, + acts::ActionExecutionContext{ .pSvc = pSvc, .pPmon = pPmon }, pipeName.value_or(gid::defaultControlPipeName), 2, std::move(sec) ); diff --git a/IntelPresentMon/PresentMonService/ActionServer.h b/IntelPresentMon/PresentMonService/ActionServer.h index b54c2568..b478d6eb 100644 --- a/IntelPresentMon/PresentMonService/ActionServer.h +++ b/IntelPresentMon/PresentMonService/ActionServer.h @@ -6,14 +6,14 @@ #include #include "PresentMon.h" #include "Service.h" +#include "FrameBroadcaster.h" namespace pmon::svc { class ActionServer { public: - ActionServer(Service* pSvc, PresentMon* pPmon, std::string shmPrefix, - std::string shmSalt, std::optional pipeName); + ActionServer(Service* pSvc, PresentMon* pPmon, std::optional pipeName); ~ActionServer() = default; ActionServer(const ActionServer&) = delete; ActionServer& operator=(const ActionServer&) = delete; diff --git a/IntelPresentMon/PresentMonService/FrameBroadcaster.h b/IntelPresentMon/PresentMonService/FrameBroadcaster.h new file mode 100644 index 00000000..1d3e7f3e --- /dev/null +++ b/IntelPresentMon/PresentMonService/FrameBroadcaster.h @@ -0,0 +1,119 @@ +#pragma once +#include "../Interprocess/source/Interprocess.h" +#include "../../PresentData/PresentMonTraceConsumer.hpp" +#include +#include + +namespace pmon::svc +{ + namespace vi = std::views; + namespace rn = std::ranges; + using ipc::FrameData; + + namespace + { + FrameData FrameDataFromPresentEvent(const PresentEvent& present) + { + using DisplayedEntry = std::pair; + using DisplayedVector = boost::container::static_vector; + + return FrameData{ + // Timing data + .presentStartTime = present.PresentStartTime, + .readyTime = present.ReadyTime, + .timeInPresent = present.TimeInPresent, + .gpuStartTime = present.GPUStartTime, + .gpuDuration = present.GPUDuration, + .gpuVideoDuration = present.GPUVideoDuration, + + // XeSS-FG propagated timing + .appPropagatedPresentStartTime = present.AppPropagatedPresentStartTime, + .appPropagatedTimeInPresent = present.AppPropagatedTimeInPresent, + .appPropagatedGPUStartTime = present.AppPropagatedGPUStartTime, + .appPropagatedReadyTime = present.AppPropagatedReadyTime, + .appPropagatedGPUDuration = present.AppPropagatedGPUDuration, + .appPropagatedGPUVideoDuration = present.AppPropagatedGPUVideoDuration, + + // Instrumented timestamps + .appSimStartTime = present.AppSimStartTime, + .appSleepStartTime = present.AppSleepStartTime, + .appSleepEndTime = present.AppSleepEndTime, + .appRenderSubmitStartTime = present.AppRenderSubmitStartTime, + .appRenderSubmitEndTime = present.AppRenderSubmitEndTime, + .appPresentStartTime = present.AppPresentStartTime, + .appPresentEndTime = present.AppPresentEndTime, + .appInputSample = present.AppInputSample, + + // Input device timestamps + .inputTime = present.InputTime, + .mouseClickTime = present.MouseClickTime, + + // Displayed history (no explicit bounds check; assumed to fit) + .displayed = DisplayedVector{ + present.Displayed.begin(), + present.Displayed.end() + }, + + // PC Latency data + .pclSimStartTime = present.PclSimStartTime, + .pclInputPingTime = present.PclInputPingTime, + .flipDelay = present.FlipDelay, + .flipToken = present.FlipToken, + + // Metadata + .finalState = present.FinalState, + .processId = present.ProcessId, + .threadId = present.ThreadId, + .swapChainAddress = present.SwapChainAddress, + .frameId = present.FrameId, + .appFrameId = present.AppFrameId, + }; + } + } + + class FrameBroadcaster + { + public: + using Segment = ipc::OwnedDataSegment; + FrameBroadcaster(ipc::ServiceComms& comms) : comms_{ comms } {} + std::shared_ptr RegisterTarget(uint32_t pid) + { + std::lock_guard lk{ mtx_ }; + // collect garbage first so it doesn't accumulate in the map + std::erase_if(segments_, [](auto&&pr){return pr.second.expired();}); + + // ipc makes the segment hosting the store, track weakly here (strongly in client session ctx) + auto pSegment = comms_.MakeFrameDataSegment(pid); + segments_[pid] = pSegment; + return pSegment; + } + void Broadcast(const PresentEvent& present) + { + std::lock_guard lk{ mtx_ }; + if (auto i = segments_.find(present.ProcessId); i != segments_.end()) { + if (auto pSegment = i->second.lock()) { + pSegment->GetStore().frameData.Push(FrameDataFromPresentEvent(present)); + } + else { + // segment expired, clean it up + segments_.erase(i); + } + } + } + std::vector GetPids() const + { + std::lock_guard lk{ mtx_ }; + return segments_ | vi::filter([](auto&& p) {return !p.second.expired(); }) | + vi::keys | rn::to(); + } + const ipc::ShmNamer& GetNamer() const + { + return comms_.GetNamer(); + } + private: + // data + ipc::ServiceComms& comms_; + std::unordered_map> segments_; + mutable std::mutex mtx_; + }; +} \ No newline at end of file diff --git a/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp b/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp index 420c3548..c736e50d 100644 --- a/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp +++ b/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp @@ -9,8 +9,9 @@ static const std::wstring kMockEtwSessionName = L"MockETWSession"; using namespace std::literals; -MockPresentMonSession::MockPresentMonSession() +MockPresentMonSession::MockPresentMonSession(svc::FrameBroadcaster& broadcaster) { + pBroadcaster = &broadcaster; ResetEtwFlushPeriod(); } diff --git a/IntelPresentMon/PresentMonService/MockPresentMonSession.h b/IntelPresentMon/PresentMonService/MockPresentMonSession.h index 5b2ffbd0..4dedfc1a 100644 --- a/IntelPresentMon/PresentMonService/MockPresentMonSession.h +++ b/IntelPresentMon/PresentMonService/MockPresentMonSession.h @@ -4,11 +4,13 @@ #include "PresentMonSession.h" #include "../CommonUtilities/win/Event.h" +using namespace pmon; + class MockPresentMonSession : public PresentMonSession { public: // functions - MockPresentMonSession(); + MockPresentMonSession(svc::FrameBroadcaster& broadcaster); MockPresentMonSession(const MockPresentMonSession& t) = delete; MockPresentMonSession& operator=(const MockPresentMonSession& t) = delete; ~MockPresentMonSession() override = default; diff --git a/IntelPresentMon/PresentMonService/PMMainThread.cpp b/IntelPresentMon/PresentMonService/PMMainThread.cpp index 1e3fea3c..1f871bf3 100644 --- a/IntelPresentMon/PresentMonService/PMMainThread.cpp +++ b/IntelPresentMon/PresentMonService/PMMainThread.cpp @@ -5,6 +5,7 @@ #include "ActionServer.h" #include "PresentMon.h" #include "PowerTelemetryContainer.h" +#include "FrameBroadcaster.h" #include "..\ControlLib\WmiCpu.h" #include "..\PresentMonUtils\StringUtils.h" #include @@ -496,10 +497,6 @@ void PresentMonMainThread(Service* const pSvc) } } - PresentMon pm{ !opt.etlTestFile }; - PowerTelemetryContainer ptc; - - // namer that coordinates naming convention of shared memory segments // create service-side comms object for transmitting introspection data to clients std::unique_ptr pComms; try { @@ -513,14 +510,19 @@ void PresentMonMainThread(Service* const pSvc) pSvc->SignalServiceStop(-1); return; } + // create comms wrapper for managing frame data segments + FrameBroadcaster frameBroadcaster{ *pComms }; + + // container for session object + PresentMon pm{ frameBroadcaster, !opt.etlTestFile }; + // container for all GPU telemetry providers + PowerTelemetryContainer ptc; // Set the created power telemetry container pm.SetPowerTelemetryContainer(&ptc); // Start named pipe action RPC server (active threaded) - auto pActionServer = std::make_unique(pSvc, &pm, - pComms->GetNamer().GetPrefix(), pComms->GetNamer().GetSalt(), - opt.controlPipe.AsOptional()); + auto pActionServer = std::make_unique(pSvc, &pm, opt.controlPipe.AsOptional()); try { gpuTelemetryThread = std::jthread{ PowerTelemetryThreadEntry_, pSvc, &pm, &ptc, pComms.get() }; diff --git a/IntelPresentMon/PresentMonService/PresentMon.cpp b/IntelPresentMon/PresentMonService/PresentMon.cpp index 2ac63632..30121d6e 100644 --- a/IntelPresentMon/PresentMonService/PresentMon.cpp +++ b/IntelPresentMon/PresentMonService/PresentMon.cpp @@ -12,15 +12,16 @@ #include #include "../CommonUtilities/win/Privileges.h" -PresentMon::PresentMon(bool isRealtime) +PresentMon::PresentMon(svc::FrameBroadcaster& broadcaster, bool isRealtime) : + broadcaster_{ broadcaster }, etwLogger_{ util::win::WeAreElevated() } { if (isRealtime) { - pSession_ = std::make_unique(); + pSession_ = std::make_unique(broadcaster); } else { - pSession_ = std::make_unique(); + pSession_ = std::make_unique(broadcaster); } } diff --git a/IntelPresentMon/PresentMonService/PresentMon.h b/IntelPresentMon/PresentMonService/PresentMon.h index c10276d8..a72eff47 100644 --- a/IntelPresentMon/PresentMonService/PresentMon.h +++ b/IntelPresentMon/PresentMonService/PresentMon.h @@ -3,6 +3,7 @@ #pragma once #include "PresentMonSession.h" #include "EtwLogger.h" +#include "FrameBroadcaster.h" #include #include @@ -11,7 +12,7 @@ using namespace pmon; class PresentMon { public: - PresentMon(bool isRealtime); + PresentMon(svc::FrameBroadcaster& broadcaster, bool isRealtime); ~PresentMon(); // Check the status of both ETW logfile and real time trace sessions. @@ -81,9 +82,14 @@ class PresentMon { return etwLogger_; } + auto& GetBroadcaster() + { + return broadcaster_; + } void StartPlayback(); void StopPlayback(); private: + svc::FrameBroadcaster& broadcaster_; svc::EtwLogger etwLogger_; std::unique_ptr pSession_; }; \ No newline at end of file diff --git a/IntelPresentMon/PresentMonService/PresentMonService.vcxproj b/IntelPresentMon/PresentMonService/PresentMonService.vcxproj index d3ee0ad4..e4e47e96 100644 --- a/IntelPresentMon/PresentMonService/PresentMonService.vcxproj +++ b/IntelPresentMon/PresentMonService/PresentMonService.vcxproj @@ -145,6 +145,7 @@ + diff --git a/IntelPresentMon/PresentMonService/PresentMonService.vcxproj.filters b/IntelPresentMon/PresentMonService/PresentMonService.vcxproj.filters index d9936bfc..e862f1d1 100644 --- a/IntelPresentMon/PresentMonService/PresentMonService.vcxproj.filters +++ b/IntelPresentMon/PresentMonService/PresentMonService.vcxproj.filters @@ -49,6 +49,7 @@ + diff --git a/IntelPresentMon/PresentMonService/PresentMonSession.cpp b/IntelPresentMon/PresentMonService/PresentMonSession.cpp index a76d7e90..86ed15e8 100644 --- a/IntelPresentMon/PresentMonService/PresentMonSession.cpp +++ b/IntelPresentMon/PresentMonService/PresentMonSession.cpp @@ -6,6 +6,7 @@ pmon::test::service::Status PresentMonSession::GetTestingStatus() const { return pmon::test::service::Status{ .nsmStreamedPids = streamer_.GetActiveStreamPids(), + .trackedPids = { std::from_range, pBroadcaster->GetPids() }, .activeAdapterId = current_telemetry_adapter_id_, .telemetryPeriodMs = gpu_telemetry_period_ms_, .etwFlushPeriodMs = etw_flush_period_ms_, diff --git a/IntelPresentMon/PresentMonService/PresentMonSession.h b/IntelPresentMon/PresentMonService/PresentMonSession.h index 1415a2f3..2b93d0f3 100644 --- a/IntelPresentMon/PresentMonService/PresentMonSession.h +++ b/IntelPresentMon/PresentMonService/PresentMonSession.h @@ -14,6 +14,7 @@ #include "../../PresentData/PresentMonTraceConsumer.hpp" #include "../../PresentData/PresentMonTraceSession.hpp" #include "PowerTelemetryContainer.h" +#include "FrameBroadcaster.h" #include "../PresentMonAPI2Tests/TestCommands.h" @@ -31,6 +32,8 @@ struct ProcessInfo { bool mTargetProcess = false; }; +using namespace pmon; + class PresentMonSession { public: virtual ~PresentMonSession() = default; @@ -75,5 +78,6 @@ class PresentMonSession { std::atomic> etw_flush_period_ms_; Streamer streamer_; + svc::FrameBroadcaster* pBroadcaster = nullptr; }; diff --git a/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp b/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp index 43ee0de7..1e98d945 100644 --- a/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp +++ b/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp @@ -13,8 +13,9 @@ using namespace pmon; using namespace std::literals; using v = util::log::V; -RealtimePresentMonSession::RealtimePresentMonSession() +RealtimePresentMonSession::RealtimePresentMonSession(svc::FrameBroadcaster& broadcaster) { + pBroadcaster = &broadcaster; ResetEtwFlushPeriod(); } diff --git a/IntelPresentMon/PresentMonService/RealtimePresentMonSession.h b/IntelPresentMon/PresentMonService/RealtimePresentMonSession.h index e02a4b61..d89abff3 100644 --- a/IntelPresentMon/PresentMonService/RealtimePresentMonSession.h +++ b/IntelPresentMon/PresentMonService/RealtimePresentMonSession.h @@ -8,7 +8,7 @@ class RealtimePresentMonSession : public PresentMonSession { public: // functions - RealtimePresentMonSession(); + RealtimePresentMonSession(svc::FrameBroadcaster& broadcaster); RealtimePresentMonSession(const RealtimePresentMonSession& t) = delete; RealtimePresentMonSession& operator=(const RealtimePresentMonSession& t) = delete; ~RealtimePresentMonSession() override = default; diff --git a/IntelPresentMon/PresentMonService/acts/OpenSession.h b/IntelPresentMon/PresentMonService/acts/OpenSession.h index cd34221e..0e2831a8 100644 --- a/IntelPresentMon/PresentMonService/acts/OpenSession.h +++ b/IntelPresentMon/PresentMonService/acts/OpenSession.h @@ -51,8 +51,8 @@ namespace pmon::svc::acts .servicePid = GetCurrentProcessId(), .serviceBuildId = bid::BuildIdShortHash(), .serviceBuildConfig = bid::BuildIdConfig(), - .shmPrefix = ctx.shmPrefix, - .shmSalt = ctx.shmSalt, + .shmPrefix = ctx.pPmon->GetBroadcaster().GetNamer().GetPrefix(), + .shmSalt = ctx.pPmon->GetBroadcaster().GetNamer().GetSalt(), }; } }; diff --git a/IntelPresentMon/PresentMonService/acts/StartTracking.h b/IntelPresentMon/PresentMonService/acts/StartTracking.h index c980fee5..70940dc8 100644 --- a/IntelPresentMon/PresentMonService/acts/StartTracking.h +++ b/IntelPresentMon/PresentMonService/acts/StartTracking.h @@ -25,6 +25,7 @@ namespace pmon::svc::acts }; struct Response { + // TODO: remove this when Streamer is gone std::string nsmFileName; template void serialize(A& ar) { @@ -36,11 +37,14 @@ namespace pmon::svc::acts static Response Execute_(const ACT_EXEC_CTX& ctx, SessionContext& stx, Params&& in) { std::string nsmFileName; + // TODO: replace PresentMon container system and directly return the segment + // from a single "StartStreaming" replacement call + auto pSegment = ctx.pPmon->GetBroadcaster().RegisterTarget(in.targetPid); if (auto sta = ctx.pPmon->StartStreaming(stx.remotePid, in.targetPid, nsmFileName); sta != PM_STATUS_SUCCESS) { pmlog_error("Start stream failed").code(sta); throw util::Except(sta); } - stx.trackedPids.insert(in.targetPid); + stx.trackedPids[in.targetPid] = std::move(pSegment); const Response out{ .nsmFileName = std::move(nsmFileName) }; pmlog_info(std::format("StartTracking action from [{}] targeting [{}] assigned nsm [{}]", stx.remotePid, in.targetPid, out.nsmFileName)); From 7e9ef90b813055df34fc869cf84c668dc7a36609 Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 12 Dec 2025 15:16:23 +0900 Subject: [PATCH 046/205] improve tracking and implement frame data statics --- .../Interprocess/source/DataStores.h | 5 +++ .../Interprocess/source/Interprocess.cpp | 28 +++++++++---- .../Interprocess/source/Interprocess.h | 3 +- .../InterimBroadcasterTests.cpp | 16 +++++++- .../PresentMonService/FrameBroadcaster.h | 41 +++++++++++-------- 5 files changed, 64 insertions(+), 29 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/DataStores.h b/IntelPresentMon/Interprocess/source/DataStores.h index 89272908..7eca4271 100644 --- a/IntelPresentMon/Interprocess/source/DataStores.h +++ b/IntelPresentMon/Interprocess/source/DataStores.h @@ -27,6 +27,11 @@ namespace pmon::ipc uint32_t processId; ShmString applicationName; } statics; + struct Bookkeeping + { + bool staticInitComplete = false; + bool targetExited = false; + } bookkeeping{}; ShmRing frameData; }; diff --git a/IntelPresentMon/Interprocess/source/Interprocess.cpp b/IntelPresentMon/Interprocess/source/Interprocess.cpp index dd862140..b45d7f82 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.cpp +++ b/IntelPresentMon/Interprocess/source/Interprocess.cpp @@ -20,6 +20,8 @@ namespace pmon::ipc { namespace bip = boost::interprocess; + namespace vi = std::views; + namespace rn = std::ranges; namespace { @@ -134,13 +136,13 @@ namespace pmon::ipc } // data store access std::shared_ptr> - MakeFrameDataSegment(uint32_t pid) override + CreateOrGetFrameDataSegment(uint32_t pid) override { // resolve out existing or new weak ptr, try and lock auto& pWeak = frameShmWeaks_[pid]; auto pFrameData = frameShmWeaks_[pid].lock(); - // if weak ptr was new (or expired), lock will not work and we need to construct if (!pFrameData) { + // if weak ptr was new (or expired), lock will not work and we need to construct // make a frame data store as shared ptr pFrameData = std::make_shared>( namer_.MakeFrameName(pid), @@ -151,18 +153,26 @@ namespace pmon::ipc pWeak = pFrameData; } // remove stale elements to keep map lean - std::erase_if(frameShmWeaks_, - [](const auto& kv) { return kv.second.expired(); }); + std::erase_if(frameShmWeaks_, [](auto&&kv){return kv.second.expired();}); return pFrameData; } - std::shared_ptr> GetFrameDataSegment(uint32_t pid) override + std::shared_ptr> + GetFrameDataSegment(uint32_t pid) override { - const auto it = frameShmWeaks_.find(pid); - if (it == frameShmWeaks_.end()) { - pmlog_error("No frame segment found").pmwatch(pid).raise(); + if (auto i = frameShmWeaks_.find(pid); i != frameShmWeaks_.end()) { + if (auto pSegment = i->second.lock()) { + return pSegment; + } + // if weak ptr has expired, garbage collect from the map + frameShmWeaks_.erase(i); } - return it->second.lock(); + return {}; + } + std::vector GetFramePids() const override + { + return frameShmWeaks_ | vi::filter([](auto&& p) {return !p.second.expired(); }) | + vi::keys | rn::to(); } GpuDataStore& GetGpuDataStore(uint32_t deviceId) override { diff --git a/IntelPresentMon/Interprocess/source/Interprocess.h b/IntelPresentMon/Interprocess/source/Interprocess.h index 1a01e66c..9c787ef6 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.h +++ b/IntelPresentMon/Interprocess/source/Interprocess.h @@ -26,8 +26,9 @@ namespace pmon::ipc virtual const ShmNamer& GetNamer() const = 0; // data store access - virtual std::shared_ptr> MakeFrameDataSegment(uint32_t pid) = 0; + virtual std::shared_ptr> CreateOrGetFrameDataSegment(uint32_t pid) = 0; virtual std::shared_ptr> GetFrameDataSegment(uint32_t pid) = 0; + virtual std::vector GetFramePids() const = 0; virtual GpuDataStore& GetGpuDataStore(uint32_t deviceId) = 0; virtual SystemDataStore& GetSystemDataStore() = 0; }; diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 7232b59e..9c5d854e 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -486,7 +486,21 @@ namespace InterimBroadcasterTests // static store TEST_METHOD(StaticData) { - Assert::IsTrue(false); + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + + // launch target and track it + auto pres = fixture_.LaunchPresenter(); + client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pres.GetId() }); + + // open the store + pComms->OpenFrameDataStore(pres.GetId()); + + // verify static data + auto& store = pComms->GetFrameDataStore(pres.GetId()); + Assert::AreEqual(pres.GetId(), store.statics.processId); + const std::string staticAppName = store.statics.applicationName.c_str(); + Assert::AreEqual("PresentBench.exe"s, staticAppName); } TEST_METHOD(TrackUntrack) { diff --git a/IntelPresentMon/PresentMonService/FrameBroadcaster.h b/IntelPresentMon/PresentMonService/FrameBroadcaster.h index 1d3e7f3e..c1d667cc 100644 --- a/IntelPresentMon/PresentMonService/FrameBroadcaster.h +++ b/IntelPresentMon/PresentMonService/FrameBroadcaster.h @@ -1,6 +1,7 @@ #pragma once #include "../Interprocess/source/Interprocess.h" #include "../../PresentData/PresentMonTraceConsumer.hpp" +#include "../CommonUtilities/win/Utilities.h" #include #include @@ -79,32 +80,37 @@ namespace pmon::svc std::shared_ptr RegisterTarget(uint32_t pid) { std::lock_guard lk{ mtx_ }; - // collect garbage first so it doesn't accumulate in the map - std::erase_if(segments_, [](auto&&pr){return pr.second.expired();}); - - // ipc makes the segment hosting the store, track weakly here (strongly in client session ctx) - auto pSegment = comms_.MakeFrameDataSegment(pid); - segments_[pid] = pSegment; - return pSegment; + auto pSegment = comms_.CreateOrGetFrameDataSegment(pid); + auto& store = pSegment->GetStore(); + // initialize name/pid statics on new store segment creation + if (!store.bookkeeping.staticInitComplete) { + store.bookkeeping.staticInitComplete = true; + store.statics.processId = pid; + try { + store.statics.applicationName = util::win::GetExecutableModulePath( + util::win::OpenProcess(pid) + ).filename().string().c_str(); + } + catch (...) { + // if we reach here a race condition has occurred where the target has exited + // so we will mark this in the bookkeeping + pmlog_warn("Process exited right as it was being initialized").pmwatch(pid); + store.bookkeeping.targetExited = true; + } + } + return pSegment; } void Broadcast(const PresentEvent& present) { std::lock_guard lk{ mtx_ }; - if (auto i = segments_.find(present.ProcessId); i != segments_.end()) { - if (auto pSegment = i->second.lock()) { - pSegment->GetStore().frameData.Push(FrameDataFromPresentEvent(present)); - } - else { - // segment expired, clean it up - segments_.erase(i); - } + if (auto pSegment = comms_.GetFrameDataSegment(present.ProcessId)) { + pSegment->GetStore().frameData.Push(FrameDataFromPresentEvent(present)); } } std::vector GetPids() const { std::lock_guard lk{ mtx_ }; - return segments_ | vi::filter([](auto&& p) {return !p.second.expired(); }) | - vi::keys | rn::to(); + return comms_.GetFramePids(); } const ipc::ShmNamer& GetNamer() const { @@ -113,7 +119,6 @@ namespace pmon::svc private: // data ipc::ServiceComms& comms_; - std::unordered_map> segments_; mutable std::mutex mtx_; }; } \ No newline at end of file From 7cb7fb6965e94f89968b16a7c5a64c7f1cea4141 Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 12 Dec 2025 16:24:15 +0900 Subject: [PATCH 047/205] frame populating and reading --- .../InterimBroadcasterTests.cpp | 29 +++++++++++++++++++ .../RealtimePresentMonSession.cpp | 3 ++ 2 files changed, 32 insertions(+) diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 9c5d854e..c0242067 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -533,5 +533,34 @@ namespace InterimBroadcasterTests // verify segment can no longer be opened Assert::ExpectException([&] {pComms->OpenFrameDataStore(pres.GetId()); }); } + TEST_METHOD(ReadFrames) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + + // launch target and track it + auto pres = fixture_.LaunchPresenter(); + client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pres.GetId() }); + client.DispatchSync(svc::acts::SetEtwFlushPeriod::Params{ .etwFlushPeriodMs = 8 }); + + // open the store + pComms->OpenFrameDataStore(pres.GetId()); + auto& frames = pComms->GetFrameDataStore(pres.GetId()).frameData; + + // verify that frames are added over time + // TODO: determine what is causing latency at service side necessitating 1000ms wait + std::this_thread::sleep_for(1000ms); + const auto range1 = frames.GetSerialRange(); + Logger::WriteMessage(std::format("range [{},{})\n", range1.first, range1.second).c_str()); + std::this_thread::sleep_for(100ms); + const auto range2 = frames.GetSerialRange(); + Logger::WriteMessage(std::format("range [{},{})\n", range2.first, range2.second).c_str()); + std::this_thread::sleep_for(100ms); + const auto range3 = frames.GetSerialRange(); + Logger::WriteMessage(std::format("range [{},{})\n", range3.first, range3.second).c_str()); + + Assert::IsTrue(range1.second - range1.first < range2.second - range2.first); + Assert::IsTrue(range2.second - range2.first < range3.second - range3.first); + } }; } diff --git a/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp b/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp index 1e98d945..76bc6707 100644 --- a/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp +++ b/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp @@ -258,6 +258,7 @@ void RealtimePresentMonSession::AddPresents( // Ignore failed and lost presents. if (presentEvent->IsLost || presentEvent->PresentFailed) { + // TODO: log these continue; } @@ -333,6 +334,8 @@ void RealtimePresentMonSession::AddPresents( processInfo->mModuleName, gpu_telemetry_cap_bits, cpu_telemetry_cap_bits); + pBroadcaster->Broadcast(*presentEvent); + chain->mLastPresentQPC = presentEvent->PresentStartTime; if (presentEvent->FinalState == PresentResult::Presented) { chain->mLastDisplayedPresentQPC = presentEvent->Displayed.empty() ? 0 : presentEvent->Displayed[0].second; From d775dd2930fdf7b11d86dc33ccc95f9ef03958ae Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Fri, 12 Dec 2025 08:27:15 -0800 Subject: [PATCH 048/205] Adding in new PC Latency tests. --- IntelPresentMon/CommonUtilities/Math.h | 2 +- IntelPresentMon/UnitTests/MetricsCore.cpp | 580 ++++++++++++++++++++++ 2 files changed, 581 insertions(+), 1 deletion(-) diff --git a/IntelPresentMon/CommonUtilities/Math.h b/IntelPresentMon/CommonUtilities/Math.h index 4730011e..06a21fb7 100644 --- a/IntelPresentMon/CommonUtilities/Math.h +++ b/IntelPresentMon/CommonUtilities/Math.h @@ -8,7 +8,7 @@ namespace pmon::util template T CommonEpsilonStrict(T a, T b) { - return std::numeric_limits::epsilon() * std::max(std::abs(a), std::abs(b)); + return std::numeric_limits::epsilon() * std::max(std::abs(a), std::abs(b)); } template diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index 221f52f7..1ad4bbd2 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include using namespace Microsoft::VisualStudio::CppUnitTestFramework; @@ -7103,5 +7104,584 @@ namespace MetricsCoreTests Assert::AreEqual(uint64_t{ 0 }, state.lastReceivedNotDisplayedPclSimStart, L"P2 (final): lastReceivedNotDisplayedPclSimStart should be reset to 0 after completion."); } + + TEST_METHOD(PcLatency_NoPclData_AllFrames_NoLatency) + { + // Scenario: + // - P0 is dropped with no PC Latency timestamps. + // - P1 and P2 are displayed app frames but likewise carry no pclSimStartTime/pclInputPingTime. + // - We run the ReportMetrics-style scheduling: dropped frames are processed immediately, + // displayed frames are first queued (Case 2) and then finalized by the arrival of the + // next displayed present (Case 3). + // QPC plan (ticks at 10 MHz): + // - Screen times: SCR1 = 100'000, SCR2 = 120'000, SCR3 = 140'000. + // Expectations: + // - Every metrics record reports msPcLatency.has_value() == false. + // - accumulatedInput2FrameStartTime remains 0.0 throughout the sequence. + // - lastReceivedNotDisplayedPclSimStart never departs from 0 since no PCL timestamps exist. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + + const uint32_t PROCESS_ID = 77; + const uint64_t SWAPCHAIN = 0x11AAu; + + // -------------------------------------------------------------------- + // P0: dropped frame without any PCL data + // -------------------------------------------------------------------- + FrameData p0{}; + p0.processId = PROCESS_ID; + p0.swapChainAddress = SWAPCHAIN; + p0.pclInputPingTime = 0; + p0.pclSimStartTime = 0; + p0.setFinalState(PresentResult::Discarded); + + auto p0_results = ComputeMetricsForPresent(qpc, p0, nullptr, state); + Assert::AreEqual(size_t(1), p0_results.size(), + L"P0 (dropped) should emit one metrics record."); + Assert::IsFalse(p0_results[0].metrics.msPcLatency.has_value(), + L"P0 should not report msPcLatency without PCL data."); + Assert::AreEqual(0.0, state.accumulatedInput2FrameStartTime, 0.0001, + L"P0 should not modify accumulatedInput2FrameStartTime when there is no PCL data."); + Assert::AreEqual(uint64_t(0), state.lastReceivedNotDisplayedPclSimStart, + L"P0 should leave lastReceivedNotDisplayedPclSimStart at 0."); + + // -------------------------------------------------------------------- + // P1: displayed frame without PCL data (pending, then finalized by P2) + // -------------------------------------------------------------------- + FrameData p1{}; + p1.processId = PROCESS_ID; + p1.swapChainAddress = SWAPCHAIN; + p1.setFinalState(PresentResult::Presented); + p1.displayed.push_back({ FrameType::Application, 100'000 }); + + auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, state); + Assert::AreEqual(size_t(0), p1_phase1.size(), + L"P1 pending pass should not emit metrics."); + Assert::AreEqual(0.0, state.accumulatedInput2FrameStartTime, 0.0001, + L"State.accumulatedInput2FrameStartTime must remain 0 after P1 pending pass."); + Assert::AreEqual(uint64_t(0), state.lastReceivedNotDisplayedPclSimStart, + L"lastReceivedNotDisplayedPclSimStart should remain 0 after P1 pending pass."); + + // -------------------------------------------------------------------- + // P2: displayed frame without PCL data (finalizes P1, then becomes pending) + // -------------------------------------------------------------------- + FrameData p2{}; + p2.processId = PROCESS_ID; + p2.swapChainAddress = SWAPCHAIN; + p2.setFinalState(PresentResult::Presented); + p2.displayed.push_back({ FrameType::Application, 120'000 }); + + auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, state); + Assert::AreEqual(size_t(1), p1_final.size(), + L"Finalizing P1 should emit exactly one metrics record."); + Assert::IsFalse(p1_final[0].metrics.msPcLatency.has_value(), + L"P1 final metrics should not report msPcLatency without PCL data."); + Assert::AreEqual(0.0, state.accumulatedInput2FrameStartTime, 0.0001, + L"Accumulated input-to-frame-start time must remain 0 after finalizing P1."); + Assert::AreEqual(uint64_t(0), state.lastReceivedNotDisplayedPclSimStart, + L"lastReceivedNotDisplayedPclSimStart should remain 0 after finalizing P1."); + + auto p2_phase1 = ComputeMetricsForPresent(qpc, p2, nullptr, state); + Assert::AreEqual(size_t(0), p2_phase1.size(), + L"P2 pending pass should not emit metrics."); + + // -------------------------------------------------------------------- + // P3: helper displayed frame to flush P2 + // -------------------------------------------------------------------- + FrameData p3{}; + p3.processId = PROCESS_ID; + p3.swapChainAddress = SWAPCHAIN; + p3.setFinalState(PresentResult::Presented); + p3.displayed.push_back({ FrameType::Application, 140'000 }); + + auto p2_final = ComputeMetricsForPresent(qpc, p2, &p3, state); + Assert::AreEqual(size_t(1), p2_final.size(), + L"Finalizing P2 should emit exactly one metrics record."); + Assert::IsFalse(p2_final[0].metrics.msPcLatency.has_value(), + L"P2 final metrics should not report msPcLatency without PCL data."); + Assert::AreEqual(0.0, state.accumulatedInput2FrameStartTime, 0.0001, + L"Accumulated input-to-frame-start time must still be 0 after P2."); + Assert::AreEqual(uint64_t(0), state.lastReceivedNotDisplayedPclSimStart, + L"lastReceivedNotDisplayedPclSimStart should remain 0 through the entire sequence."); + + auto p3_phase1 = ComputeMetricsForPresent(qpc, p3, nullptr, state); + Assert::AreEqual(size_t(0), p3_phase1.size(), + L"P3 pending pass is only for completeness and should not emit metrics."); + } + + TEST_METHOD(PcLatency_SingleDisplayed_DirectSample_FirstEma) + { + // Scenario: + // - Single displayed frame P0 provides both pclInputPingTime and pclSimStartTime. + // - There is no subsequent present, so we exercise Case 2 (nextDisplayed == nullptr) + // with two display samples on P0 to mirror the ReportMetrics behavior where the + // last display instance is deferred. + // - The first metrics record must report msPcLatency immediately and seed the EMA. + // Timing (ticks at 10 MHz): + // - Ping0 = 10'000, Sim0 = 20'000 (Δ = 1.0 ms) + // - Display samples: SCR0 = 50'000, SCR0b = 60'000 (provides nextScreenTime). + // Expectations: + // - msPcLatency.has_value() == true and > 0. + // - accumulatedInput2FrameStartTime remains 0 (no dropped chain). + // - Input2FrameStartTimeEma equals CalculateEma(0.0, Δ(PING0,SIM0), 0.1). + // - msPcLatency equals EMA + Δ(SIM0, SCR0), proving pclSimStartTime was used. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + + FrameData p0{}; + p0.pclInputPingTime = 10'000; + p0.pclSimStartTime = 20'000; + p0.setFinalState(PresentResult::Presented); + p0.displayed.push_back({ FrameType::Application, 50'000 }); + p0.displayed.push_back({ FrameType::Application, 60'000 }); + + auto p0_results = ComputeMetricsForPresent(qpc, p0, nullptr, state); + Assert::AreEqual(size_t(1), p0_results.size(), + L"P0 should emit metrics immediately when nextDisplayed == nullptr and two display samples exist."); + + const auto& p0_metrics = p0_results[0].metrics; + Assert::IsTrue(p0_metrics.msPcLatency.has_value(), + L"P0 should report msPcLatency for a direct PCL sample."); + Assert::IsTrue(p0_metrics.msPcLatency.value() > 0.0, + L"P0 msPcLatency should be positive."); + Assert::AreEqual(0.0, state.accumulatedInput2FrameStartTime, 0.0001, + L"Direct PCL sample should not touch accumulatedInput2FrameStartTime."); + Assert::AreEqual(uint64_t(0), state.lastReceivedNotDisplayedPclSimStart, + L"No dropped frames occurred, so there should be no pending pclSimStart."); + + double deltaPingSim = qpc.DeltaUnsignedMilliSeconds(10'000, 20'000); + double expectedEma = pmon::util::CalculateEma(0.0, deltaPingSim, 0.1); + Assert::AreEqual(expectedEma, state.Input2FrameStartTimeEma, 0.0001, + L"Input2FrameStartTimeEma should be seeded from the first Δ(PING,SIM)."); + + double expectedLatency = expectedEma + qpc.DeltaSignedMilliSeconds(20'000, 50'000); + Assert::AreEqual(expectedLatency, p0_metrics.msPcLatency.value(), 0.0001, + L"msPcLatency should use pclSimStartTime (not lastSimStartTime) plus the seeded EMA."); + } + + TEST_METHOD(PcLatency_TwoDisplayed_DirectSamples_UpdateEma) + { + // Scenario: + // - Two displayed frames (P0, P1) each provide direct PCL samples (Ping + Sim). + // - We mimic the ReportMetrics scheduling: + // P0 arrives -> pending only (Case 2). + // P1 arrives -> finalize P0 with nextDisplayed = P1 (Case 3), then queue P1 (Case 2). + // P2 helper -> finalize P1 (Case 3) to observe the EMA update. + // Expectations: + // - P0 final metrics report msPcLatency and seed the EMA. + // - P1 pending call emits no metrics. + // - After P1 is finalized, Input2FrameStartTimeEma changes (≠ value after P0) and stays > 0. + // - accumulatedInput2FrameStartTime stays 0 because no dropped chain exists. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + + // -------------------------------------------------------------------- + // P0: first displayed frame with direct PCL sample + // -------------------------------------------------------------------- + FrameData p0{}; + p0.pclInputPingTime = 10'000; + p0.pclSimStartTime = 20'000; + p0.setFinalState(PresentResult::Presented); + p0.displayed.push_back({ FrameType::Application, 50'000 }); + + auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, state); + Assert::AreEqual(size_t(0), p0_phase1.size(), + L"P0 pending pass should not emit metrics."); + + // -------------------------------------------------------------------- + // P1: second displayed frame with direct PCL sample + // -------------------------------------------------------------------- + FrameData p1{}; + p1.pclInputPingTime = 30'000; + p1.pclSimStartTime = 40'000; + p1.setFinalState(PresentResult::Presented); + p1.displayed.push_back({ FrameType::Application, 70'000 }); + + auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, state); + Assert::AreEqual(size_t(1), p0_final.size(), + L"Finalizing P0 with nextDisplayed=P1 should emit exactly one metrics record."); + Assert::IsTrue(p0_final[0].metrics.msPcLatency.has_value(), + L"P0 should report msPcLatency when finalized."); + double emaAfterP0 = state.Input2FrameStartTimeEma; + Assert::IsTrue(emaAfterP0 > 0.0, + L"EMA after P0 should be positive."); + Assert::AreEqual(0.0, state.accumulatedInput2FrameStartTime, 0.0001, + L"Accumulated input-to-frame-start time should remain zero after P0."); + + auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, state); + Assert::AreEqual(size_t(0), p1_phase1.size(), + L"P1 pending pass should not emit metrics."); + + // -------------------------------------------------------------------- + // P2: helper displayed frame to flush P1 + // -------------------------------------------------------------------- + FrameData p2{}; + p2.setFinalState(PresentResult::Presented); + p2.displayed.push_back({ FrameType::Application, 90'000 }); + + auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, state); + Assert::AreEqual(size_t(1), p1_final.size(), + L"Finalizing P1 should emit exactly one metrics record."); + Assert::IsTrue(p1_final[0].metrics.msPcLatency.has_value(), + L"P1 should report msPcLatency when finalized."); + double emaAfterP1 = state.Input2FrameStartTimeEma; + Assert::IsTrue(emaAfterP1 > 0.0, + L"EMA after P1 should stay positive."); + Assert::IsTrue(emaAfterP1 != emaAfterP0, + L"EMA after P1 must differ from the first-sample EMA after P0."); + Assert::AreEqual(0.0, state.accumulatedInput2FrameStartTime, 0.0001, + L"No dropped chain should mean accumulatedInput2FrameStartTime stays at 0."); + + auto p2_phase1 = ComputeMetricsForPresent(qpc, p2, nullptr, state); + Assert::AreEqual(size_t(0), p2_phase1.size(), + L"P2 pending pass is only to mirror the pipeline; it should emit no metrics."); + } + + TEST_METHOD(PcLatency_Dropped_DirectPcl_InitializesAccum) + { + // Scenario: + // - A dropped frame P0 carries both pclInputPingTime and pclSimStartTime. + // - Without any displayed frame to consume it, the PC Latency accumulator should + // be initialized to Δ(PING0, SIM0) while msPcLatency remains absent. + // Expectations: + // - P0's metrics do not expose msPcLatency (it was dropped). + // - accumulatedInput2FrameStartTime equals Δ(PING0, SIM0) and > 0. + // - lastReceivedNotDisplayedPclSimStart latches SIM0. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + + FrameData p0{}; + p0.pclInputPingTime = 10'000; + p0.pclSimStartTime = 20'000; + p0.setFinalState(PresentResult::Discarded); + + auto p0_results = ComputeMetricsForPresent(qpc, p0, nullptr, state); + Assert::AreEqual(size_t(1), p0_results.size(), + L"Dropped frames should emit one metrics record immediately."); + Assert::IsFalse(p0_results[0].metrics.msPcLatency.has_value(), + L"Dropped frames must not report msPcLatency."); + + double expectedAccum = qpc.DeltaUnsignedMilliSeconds(10'000, 20'000); + Assert::IsTrue(state.accumulatedInput2FrameStartTime > 0.0, + L"Accumulated input-to-frame-start time should be initialized."); + Assert::AreEqual(expectedAccum, state.accumulatedInput2FrameStartTime, 0.0001, + L"Accumulator should equal Δ(PING0, SIM0)."); + Assert::AreEqual(uint64_t(20'000), state.lastReceivedNotDisplayedPclSimStart, + L"lastReceivedNotDisplayedPclSimStart should track P0's pclSimStartTime."); + } + + TEST_METHOD(PcLatency_DroppedChain_SimOnly_ExtendsAccum) + { + // Scenario: + // - P0 (dropped) has both Ping and Sim, seeding the accumulator. + // - P1 (dropped) has only pclSimStartTime and should extend the accumulator by the + // delta between SIM0 and SIM1. + // Expectations: + // - P1 still reports no msPcLatency. + // - accumulatedInput2FrameStartTime after P1 > accumulated time after P0. + // - lastReceivedNotDisplayedPclSimStart equals SIM1. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + + FrameData p0{}; + p0.pclInputPingTime = 10'000; + p0.pclSimStartTime = 20'000; + p0.setFinalState(PresentResult::Discarded); + + auto p0_results = ComputeMetricsForPresent(qpc, p0, nullptr, state); + Assert::AreEqual(size_t(1), p0_results.size()); + Assert::IsFalse(p0_results[0].metrics.msPcLatency.has_value()); + double accumAfterP0 = state.accumulatedInput2FrameStartTime; + + FrameData p1{}; + p1.pclInputPingTime = 0; + p1.pclSimStartTime = 30'000; + p1.setFinalState(PresentResult::Discarded); + + auto p1_results = ComputeMetricsForPresent(qpc, p1, nullptr, state); + Assert::AreEqual(size_t(1), p1_results.size(), + L"Second dropped frame should emit one metrics record."); + Assert::IsFalse(p1_results[0].metrics.msPcLatency.has_value(), + L"Dropped frames never report msPcLatency."); + Assert::IsTrue(state.accumulatedInput2FrameStartTime > accumAfterP0, + L"Accumulator should grow when a sim-only dropped frame follows an existing chain."); + Assert::AreEqual(uint64_t(30'000), state.lastReceivedNotDisplayedPclSimStart, + L"Sim-only dropped frames still update lastReceivedNotDisplayedPclSimStart."); + } + + TEST_METHOD(PcLatency_Dropped_SimOnly_NoAccum_NoEffect) + { + // Scenario: + // - A single dropped frame P0 only provides pclSimStartTime (no ping) and there is + // no existing accumulator. + // Expectations: + // - msPcLatency remains absent. + // - accumulatedInput2FrameStartTime stays at 0 (chain not started). + // - lastReceivedNotDisplayedPclSimStart updates to SIM0 for possible future chaining. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + + FrameData p0{}; + p0.pclInputPingTime = 0; + p0.pclSimStartTime = 25'000; + p0.setFinalState(PresentResult::Discarded); + + auto p0_results = ComputeMetricsForPresent(qpc, p0, nullptr, state); + Assert::AreEqual(size_t(1), p0_results.size()); + Assert::IsFalse(p0_results[0].metrics.msPcLatency.has_value()); + Assert::AreEqual(0.0, state.accumulatedInput2FrameStartTime, 0.0001, + L"Accumulator should remain 0 when a sim-only drop has no pending chain."); + Assert::AreEqual(uint64_t(25'000), state.lastReceivedNotDisplayedPclSimStart, + L"Sim-only drop should remember its pclSimStartTime even if no accumulator exists yet."); + } + + TEST_METHOD(PcLatency_Displayed_SimOnly_NoAccum_UsesExistingEma) + { + // Scenario: + // - P0 is displayed with a direct PCL sample, seeding the EMA. + // - P1 is displayed with only pclSimStartTime (no ping) and there is no accumulated chain. + // - We follow the full pipeline: + // P0 pending, then finalized by P1 (Case 3). + // P1 pending, then finalized by helper P2. + // Expectations: + // - P1 final metrics report msPcLatency despite having no new ping. + // - accumulatedInput2FrameStartTime stays at 0 (no dropped chain was active). + // - Input2FrameStartTimeEma remains positive (not reset to 0). + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + + FrameData p0{}; + p0.pclInputPingTime = 10'000; + p0.pclSimStartTime = 20'000; + p0.setFinalState(PresentResult::Presented); + p0.displayed.push_back({ FrameType::Application, 50'000 }); + + auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, state); + Assert::AreEqual(size_t(0), p0_phase1.size()); + + FrameData p1{}; + p1.pclInputPingTime = 0; + p1.pclSimStartTime = 35'000; + p1.setFinalState(PresentResult::Presented); + p1.displayed.push_back({ FrameType::Application, 70'000 }); + + auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, state); + Assert::AreEqual(size_t(1), p0_final.size()); + Assert::IsTrue(p0_final[0].metrics.msPcLatency.has_value()); + double emaAfterP0 = state.Input2FrameStartTimeEma; + Assert::IsTrue(emaAfterP0 > 0.0); + + auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, state); + Assert::AreEqual(size_t(0), p1_phase1.size()); + + FrameData p2{}; + p2.setFinalState(PresentResult::Presented); + p2.displayed.push_back({ FrameType::Application, 90'000 }); + + auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, state); + Assert::AreEqual(size_t(1), p1_final.size()); + const auto& p1_metrics = p1_final[0].metrics; + Assert::IsTrue(p1_metrics.msPcLatency.has_value(), + L"P1 should report msPcLatency despite missing pclInputPingTime."); + Assert::IsTrue(p1_metrics.msPcLatency.value() > 0.0, + L"P1 msPcLatency should stay positive."); + Assert::AreEqual(0.0, state.accumulatedInput2FrameStartTime, 0.0001, + L"No dropped chain means the accumulator must stay zero."); + Assert::IsTrue(state.Input2FrameStartTimeEma > 0.0, + L"EMA should not be reset when a sim-only displayed frame uses existing history."); + + auto p2_phase1 = ComputeMetricsForPresent(qpc, p2, nullptr, state); + Assert::AreEqual(size_t(0), p2_phase1.size()); + } + + TEST_METHOD(PcLatency_Displayed_NoPclSim_UsesLastSimStart) + { + // Scenario: + // - P0 is displayed with a full PCL sample to seed both the EMA and lastSimStartTime. + // - P1 is displayed without any PCL timestamps (pclSimStartTime == 0, pclInputPingTime == 0). + // - P1 should still produce msPcLatency by combining the existing EMA with the fallback + // state.lastSimStartTime recorded after P0. + // Call schedule mirrors ReportMetrics: P0 pending, finalized by P1; P1 pending, finalized by P2. + // Expectations: + // - P1 final metrics report msPcLatency.has_value() == true. + // - Input2FrameStartTimeEma is unchanged from the P0 sample (no new data). + // - msPcLatency equals EMA_after_P0 + Δ(lastSimStartTime_after_P0, SCR1), proving the fallback path. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + + FrameData p0{}; + p0.pclInputPingTime = 10'000; + p0.pclSimStartTime = 30'000; + p0.setFinalState(PresentResult::Presented); + p0.displayed.push_back({ FrameType::Application, 70'000 }); + + auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, state); + Assert::AreEqual(size_t(0), p0_phase1.size()); + + FrameData p1{}; + p1.pclInputPingTime = 0; + p1.pclSimStartTime = 0; + p1.setFinalState(PresentResult::Presented); + p1.displayed.push_back({ FrameType::Application, 90'000 }); + + auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, state); + Assert::AreEqual(size_t(1), p0_final.size()); + Assert::IsTrue(p0_final[0].metrics.msPcLatency.has_value()); + double emaAfterP0 = state.Input2FrameStartTimeEma; + uint64_t fallbackSimStart = state.lastSimStartTime; + Assert::IsTrue(emaAfterP0 > 0.0, + L"EMA must be initialized after the first direct sample."); + Assert::AreEqual(uint64_t(30'000), fallbackSimStart, + L"lastSimStartTime should latch P0's pclSimStartTime when it is displayed."); + + auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, state); + Assert::AreEqual(size_t(0), p1_phase1.size()); + + FrameData p2{}; + p2.setFinalState(PresentResult::Presented); + p2.displayed.push_back({ FrameType::Application, 110'000 }); + + auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, state); + Assert::AreEqual(size_t(1), p1_final.size()); + const auto& p1_metrics = p1_final[0].metrics; + Assert::IsTrue(p1_metrics.msPcLatency.has_value(), + L"P1 should still report msPcLatency using the fallback lastSimStartTime."); + Assert::AreEqual(emaAfterP0, state.Input2FrameStartTimeEma, 0.0001, + L"EMA should remain unchanged when no new PCL sample exists."); + double expectedLatency = emaAfterP0 + qpc.DeltaSignedMilliSeconds(fallbackSimStart, 90'000); + Assert::AreEqual(expectedLatency, p1_metrics.msPcLatency.value(), 0.0001, + L"msPcLatency should use the stored EMA plus the delta from lastSimStartTime to screen time."); + Assert::AreEqual(0.0, state.accumulatedInput2FrameStartTime, 0.0001, + L"Accumulator should remain zero in this scenario."); + + auto p2_phase1 = ComputeMetricsForPresent(qpc, p2, nullptr, state); + Assert::AreEqual(size_t(0), p2_phase1.size()); + } + + TEST_METHOD(PcLatency_Dropped_DirectPcl_OverwritesOldAccum) + { + // Scenario: + // - Dropped frames P0 (Ping+Sim) and P1 (Sim only) create an accumulated chain A_old. + // - A new dropped frame P2 arrives with its own Ping+Sim and should overwrite (not extend) + // the accumulator, effectively starting a brand new chain. + // Expectations: + // - Accumulator after P2 equals Δ(PING2, SIM2) exactly (no residue from A_old). + // - lastReceivedNotDisplayedPclSimStart equals SIM2. + // - P2 still reports no msPcLatency. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + + FrameData p0{}; + p0.pclInputPingTime = 10'000; + p0.pclSimStartTime = 20'000; + p0.setFinalState(PresentResult::Discarded); + ComputeMetricsForPresent(qpc, p0, nullptr, state); + + FrameData p1{}; + p1.pclInputPingTime = 0; + p1.pclSimStartTime = 30'000; + p1.setFinalState(PresentResult::Discarded); + ComputeMetricsForPresent(qpc, p1, nullptr, state); + + double accumBeforeP2 = state.accumulatedInput2FrameStartTime; + Assert::IsTrue(accumBeforeP2 > 0.0, + L"Precondition: accumulator should already be non-zero before introducing P2."); + + FrameData p2{}; + p2.pclInputPingTime = 100'000; + p2.pclSimStartTime = 120'000; + p2.setFinalState(PresentResult::Discarded); + + auto p2_results = ComputeMetricsForPresent(qpc, p2, nullptr, state); + Assert::AreEqual(size_t(1), p2_results.size()); + Assert::IsFalse(p2_results[0].metrics.msPcLatency.has_value()); + + double expectedAccum = qpc.DeltaUnsignedMilliSeconds(100'000, 120'000); + Assert::AreEqual(expectedAccum, state.accumulatedInput2FrameStartTime, 0.0001, + L"New dropped frame with Ping+Sim should overwrite the accumulator with its own delta."); + Assert::AreEqual(uint64_t(120'000), state.lastReceivedNotDisplayedPclSimStart, + L"lastReceivedNotDisplayedPclSimStart should latch the newest sim start."); + } + + TEST_METHOD(PcLatency_IncompleteDroppedChain_DoesNotAffectDirectSample) + { + // Scenario: + // - D0 (dropped, Ping+Sim) followed by D1 (dropped, Sim-only) builds an incomplete chain. + // - No displayed frame consumes it before a new direct-sample present P0 arrives. + // - P0 should be treated as a fresh direct measurement: EMA behaves like a first sample + // from P0 alone and the stale accumulator is cleared. + // Expectations: + // - D0/D1 never report msPcLatency. + // - P0 final metrics report msPcLatency.has_value() == true. + // - Input2FrameStartTimeEma after P0 equals CalculateEma(0.0, Δ(P0.Ping, P0.Sim), 0.1). + // - accumulatedInput2FrameStartTime resets to 0 after the displayed frame completes. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + + // Dropped chain D0 -> D1 (incomplete) + FrameData d0{}; + d0.pclInputPingTime = 10'000; + d0.pclSimStartTime = 20'000; + d0.setFinalState(PresentResult::Discarded); + auto d0_results = ComputeMetricsForPresent(qpc, d0, nullptr, state); + Assert::AreEqual(size_t(1), d0_results.size()); + Assert::IsFalse(d0_results[0].metrics.msPcLatency.has_value()); + + FrameData d1{}; + d1.pclInputPingTime = 0; + d1.pclSimStartTime = 30'000; + d1.setFinalState(PresentResult::Discarded); + auto d1_results = ComputeMetricsForPresent(qpc, d1, nullptr, state); + Assert::AreEqual(size_t(1), d1_results.size()); + Assert::IsFalse(d1_results[0].metrics.msPcLatency.has_value()); + double accumBeforeDisplayed = state.accumulatedInput2FrameStartTime; + Assert::IsTrue(accumBeforeDisplayed > 0.0, + L"Incomplete chain should leave a non-zero accumulator."); + + // Displayed P0 with a brand-new direct sample + FrameData p0{}; + p0.pclInputPingTime = 100'000; + p0.pclSimStartTime = 120'000; + p0.setFinalState(PresentResult::Presented); + p0.displayed.push_back({ FrameType::Application, 150'000 }); + + auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, state); + Assert::AreEqual(size_t(0), p0_phase1.size()); + + FrameData p1{}; + p1.setFinalState(PresentResult::Presented); + p1.displayed.push_back({ FrameType::Application, 180'000 }); + + auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, state); + Assert::AreEqual(size_t(1), p0_final.size()); + const auto& p0_metrics = p0_final[0].metrics; + Assert::IsTrue(p0_metrics.msPcLatency.has_value(), + L"Displayed frame with direct PCL data must report msPcLatency."); + Assert::IsTrue(p0_metrics.msPcLatency.value() > 0.0, + L"msPcLatency should be positive for P0."); + + double expectedFirstEma = pmon::util::CalculateEma(0.0, + qpc.DeltaUnsignedMilliSeconds(100'000, 120'000), + 0.1); + Assert::AreEqual(expectedFirstEma, state.Input2FrameStartTimeEma, 0.0001, + L"EMA after P0 should match a first-sample EMA that ignores stale accumulation."); + Assert::AreEqual(0.0, state.accumulatedInput2FrameStartTime, 0.0001, + L"Accumulator must be cleared once the displayed frame consumes the chain."); + Assert::AreEqual(uint64_t(0), state.lastReceivedNotDisplayedPclSimStart, + L"Pending pclSimStart markers should be cleared once the chain completes."); + + auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, state); + Assert::AreEqual(size_t(0), p1_phase1.size()); + } }; } \ No newline at end of file From 195ff3628ddfc314252a548918320e52a7229809 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Fri, 12 Dec 2025 10:30:17 -0800 Subject: [PATCH 049/205] Fixed PC Latency ULTs. All tests passing. --- .../CommonUtilities/mc/MetricsCalculator.cpp | 45 +++++++++---------- .../CommonUtilities/mc/MetricsCalculator.h | 1 - 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 9a666ab1..7147cf12 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -481,12 +481,6 @@ namespace pmon::util::metrics *d.newLastReceivedPclSimStart; } - if (d.newLastReceivedPclInputTime) - { - chainState.lastReceivedNotDisplayedPclInputTime = - *d.newLastReceivedPclInputTime; - } - // Accumulated PC latency input→frame-start time if (d.newAccumulatedInput2FrameStart) { @@ -877,7 +871,7 @@ namespace pmon::util::metrics // the Input to Frame Start EMA. Store in state deltas for later application. stateDeltas.newInput2FrameStartEma = pmon::util::CalculateEma( - chain.accumulatedInput2FrameStartTime, + chain.Input2FrameStartTimeEma, qpc.DeltaUnsignedMilliSeconds(present.pclInputPingTime, present.pclSimStartTime), 0.1); @@ -886,29 +880,32 @@ namespace pmon::util::metrics stateDeltas.newLastReceivedPclSimStart = 0; } else { - // This frame was displayed but we don't have a pc latency input time. However, there is accumulated time - // so there is a pending input that will now hit the screen. Add in the time from the last not - // displayed pc simulation start to this frame's pc simulation start. - stateDeltas.newAccumulatedInput2FrameStart = chain.accumulatedInput2FrameStartTime + - qpc.DeltaUnsignedMilliSeconds( - chain.lastReceivedNotDisplayedPclSimStart, - present.pclSimStartTime); + if (chain.accumulatedInput2FrameStartTime != 0.0) { + // This frame was displayed but we don't have a pc latency input time. However, there is accumulated time + // so there is a pending input that will now hit the screen. Add in the time from the last not + // displayed pc simulation start to this frame's pc simulation start. + stateDeltas.newAccumulatedInput2FrameStart = chain.accumulatedInput2FrameStartTime + + qpc.DeltaUnsignedMilliSeconds( + chain.lastReceivedNotDisplayedPclSimStart, + present.pclSimStartTime); - stateDeltas.newInput2FrameStartEma = pmon::util::CalculateEma( - chain.Input2FrameStartTimeEma, - *stateDeltas.newAccumulatedInput2FrameStart, - 0.1); + stateDeltas.newInput2FrameStartEma = pmon::util::CalculateEma( + chain.Input2FrameStartTimeEma, + *stateDeltas.newAccumulatedInput2FrameStart, + 0.1); - // Reset the tracking variables for when we have a dropped frame with a pc latency input - stateDeltas.newAccumulatedInput2FrameStart = 0.0; - stateDeltas.newLastReceivedPclSimStart = 0; + // Reset the tracking variables for when we have a dropped frame with a pc latency input + stateDeltas.newAccumulatedInput2FrameStart = 0.0; + stateDeltas.newLastReceivedPclSimStart = 0; + } } } auto simStartTime = present.pclSimStartTime != 0 ? present.pclSimStartTime : chain.lastSimStartTime; - - if (stateDeltas.newInput2FrameStartEma.has_value() && stateDeltas.newInput2FrameStartEma.value() != 0.0 && simStartTime != 0) { - return stateDeltas.newInput2FrameStartEma.value() + qpc.DeltaSignedMilliSeconds(simStartTime, screenTime); + double input2FrameStartEma = stateDeltas.newInput2FrameStartEma.has_value() ? + stateDeltas.newInput2FrameStartEma.value() : chain.Input2FrameStartTimeEma; + if (input2FrameStartEma != 0.0 && simStartTime != 0) { + return input2FrameStartEma + qpc.DeltaSignedMilliSeconds(simStartTime, screenTime); } else { return std::nullopt; diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h index 49db79a5..95db2f3a 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h @@ -19,7 +19,6 @@ namespace pmon::util::metrics std::optional newInput2FrameStartEma; std::optional newAccumulatedInput2FrameStart; std::optional newLastReceivedPclSimStart; - std::optional newLastReceivedPclInputTime; std::optional lastReceivedNotDisplayedAllInputTime; std::optional lastReceivedNotDisplayedMouseClickTime; std::optional lastReceivedNotDisplayedAppProviderInputTime; From a4d399eddbaddf1818c3d7f10a5025b8eade8ffd Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Fri, 12 Dec 2025 11:36:59 -0800 Subject: [PATCH 050/205] Added in instrumented metric support. No tests yet. --- .../CommonUtilities/mc/MetricsCalculator.cpp | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 7147cf12..edc0241d 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -438,6 +438,91 @@ namespace pmon::util::metrics return qpc.DeltaUnsignedMilliSeconds(inputTime, screenTime); } + std::optional ComputeInstrumentedLatency( + const QpcConverter& qpc, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + uint64_t screenTime) + { + if (!isDisplayed || !isAppFrame) { + return std::nullopt; + } + + auto instrumentedStartTime = present.appSleepEndTime != 0 ? + present.appSleepEndTime : present.appSimStartTime; + + if (instrumentedStartTime == 0) { + // No instrumented start time: nothing to compute. + return std::nullopt; + } + + return qpc.DeltaUnsignedMilliSeconds(instrumentedStartTime, screenTime); + } + + std::optional ComputeInstrumentedRenderLatency( + const QpcConverter& qpc, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + uint64_t screenTime) + { + if (!isDisplayed || !isAppFrame) { + return std::nullopt; + } + + if (present.appRenderSubmitStartTime == 0) { + // No app provided render submit start time: nothing to compute. + return std::nullopt; + } + + return qpc.DeltaUnsignedMilliSeconds(present.appRenderSubmitStartTime, screenTime); + } + + std::optional ComputeInstrumentedSleep(const QpcConverter& qpc, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + uint64_t screenTime) + { + if (!isDisplayed || !isAppFrame) { + return std::nullopt; + } + + if (present.appSleepStartTime == 0 || present.appSleepEndTime == 0) { + // No app provided sleep times: nothing to compute. + return std::nullopt; + } + + return qpc.DeltaUnsignedMilliSeconds(present.appSleepStartTime, present.appSleepEndTime); + } + + std::optional ComputeInstrumentedGpuLatency( + const QpcConverter& qpc, + const FrameData& present, + bool isDisplayed, + bool isAppFrame) + { + if (!isDisplayed || !isAppFrame) { + return std::nullopt; + } + + auto instrumentedStartTime = present.appSleepEndTime != 0 ? + present.appSleepEndTime : present.appSimStartTime; + + if (instrumentedStartTime == 0) { + // No provider sleep end or sim start time: nothing to compute. + return std::nullopt; + } + + if (present.gpuStartTime == 0) { + // No GPU start time: nothing to compute. + return std::nullopt; + } + + return qpc.DeltaUnsignedMilliSeconds(instrumentedStartTime, present.gpuStartTime); + } + void ApplyStateDeltas( SwapChainCoreState& chainState, const ComputedMetrics::StateDeltas& d) @@ -912,6 +997,43 @@ namespace pmon::util::metrics } } + void CalculateInstrumentedMetrics( + const QpcConverter& qpc, + const SwapChainCoreState& chain, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + uint64_t screenTime, + FrameMetrics& metrics) { + + metrics.msInstrumentedLatency = ComputeInstrumentedLatency( + qpc, + present, + isDisplayed, + isAppFrame, + screenTime); + + metrics.msInstrumentedRenderLatency = ComputeInstrumentedRenderLatency( + qpc, + present, + isDisplayed, + isAppFrame, + screenTime); + + metrics.msInstrumentedSleep = ComputeInstrumentedSleep( + qpc, + present, + isDisplayed, + isAppFrame, + screenTime); + + metrics.msInstrumentedGpuLatency = ComputeInstrumentedGpuLatency( + qpc, + present, + isDisplayed, + isAppFrame); + } + ComputedMetrics ComputeFrameMetrics( const QpcConverter& qpc, const FrameData& present, @@ -973,6 +1095,15 @@ namespace pmon::util::metrics screenTime, result.stateDeltas); + CalculateInstrumentedMetrics( + qpc, + chain, + present, + isDisplayed, + isAppFrame, + screenTime, + metrics); + metrics.cpuStartQpc = CalculateCPUStart(chain, present); return result; From a8bc872094a51888dee92240180daa5d43829915 Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 15 Dec 2025 11:21:27 +0900 Subject: [PATCH 051/205] fix etw startup and verbose modules via cli --- IntelPresentMon/CommonUtilities/log/Verbose.cpp | 3 ++- .../PresentMonAPI2Tests/InterimBroadcasterTests.cpp | 9 ++++++--- IntelPresentMon/PresentMonAPI2Tests/TestProcess.h | 1 + IntelPresentMon/PresentMonService/PMMainThread.cpp | 13 ++++++++----- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/log/Verbose.cpp b/IntelPresentMon/CommonUtilities/log/Verbose.cpp index 07f92e13..6f2dab0b 100644 --- a/IntelPresentMon/CommonUtilities/log/Verbose.cpp +++ b/IntelPresentMon/CommonUtilities/log/Verbose.cpp @@ -15,7 +15,8 @@ namespace pmon::util::log std::map map; for (int n = 0; n <= (int)V::Count; n++) { const auto lvl = V(n); - auto key = ToLower(GetVerboseModuleName(lvl)); + auto name = ToLower(GetVerboseModuleName(lvl)); + map[name] = lvl; } return map; } diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index c0242067..0cf0100b 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -540,16 +540,19 @@ namespace InterimBroadcasterTests // launch target and track it auto pres = fixture_.LaunchPresenter(); - client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pres.GetId() }); client.DispatchSync(svc::acts::SetEtwFlushPeriod::Params{ .etwFlushPeriodMs = 8 }); + // make sure the flush period propagates to the flusher thread + std::this_thread::sleep_for(1ms); + client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pres.GetId() }); // open the store pComms->OpenFrameDataStore(pres.GetId()); auto& frames = pComms->GetFrameDataStore(pres.GetId()).frameData; + // sleep here to let the presenter init, etw system warm up, and frames propagate + std::this_thread::sleep_for(550ms); + // verify that frames are added over time - // TODO: determine what is causing latency at service side necessitating 1000ms wait - std::this_thread::sleep_for(1000ms); const auto range1 = frames.GetSerialRange(); Logger::WriteMessage(std::format("range [{},{})\n", range1.first, range1.second).c_str()); std::this_thread::sleep_for(100ms); diff --git a/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h b/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h index 8cc23f12..3112f193 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h +++ b/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h @@ -167,6 +167,7 @@ class ServiceProcess : public ConnectedTestProcess "--log-dir"s, common.logFolder, "--log-name-pid"s, "--log-level"s, common.logLevel, + "--enable-debugger-log"s, }; allArgs.append_range(customArgs); return allArgs; diff --git a/IntelPresentMon/PresentMonService/PMMainThread.cpp b/IntelPresentMon/PresentMonService/PMMainThread.cpp index 1f871bf3..93e2721c 100644 --- a/IntelPresentMon/PresentMonService/PMMainThread.cpp +++ b/IntelPresentMon/PresentMonService/PMMainThread.cpp @@ -41,9 +41,8 @@ void EventFlushThreadEntry_(Service* const srv, PresentMon* const pm) // this is the interval to wait when manual flush is disabled // we still want to run the inner loop to poll in case it gets enabled const uint32_t disabledIntervalMs = 250u; - double currentInterval = (double)pm->GetEtwFlushPeriod().value_or(disabledIntervalMs) / 1000.; - IntervalWaiter waiter{ currentInterval }; - + // waiter interval will be set later before it is actually used; set 0 placeholder + IntervalWaiter waiter{ 0. }; // outer dormant loop waits for either start of process tracking or service exit while (true) { pmlog_verb(v::etwq)("Begin idle ETW flush wait"); @@ -53,9 +52,12 @@ void EventFlushThreadEntry_(Service* const srv, PresentMon* const pm) return; } pmlog_verb(v::etwq)("Entering ETW flush inner active loop"); + // read flush period here right before first wait + auto currentInterval = (double)pm->GetEtwFlushPeriod().value_or(disabledIntervalMs) / 1000.; // otherwise we assume streaming has started and we begin the flushing loop, checking for stop signal while (!win::WaitAnyEventFor(0s, srv->GetServiceStopHandle())) { // use interval wait to time flushes as a fixed cadence + pmlog_verb(v::etwq)("Wait on ETW flush interval (period)").pmwatch(currentInterval); waiter.SetInterval(currentInterval); waiter.Wait(); // go dormant if there are no active streams left @@ -64,15 +66,16 @@ void EventFlushThreadEntry_(Service* const srv, PresentMon* const pm) pmlog_dbg("ETW flush loop entering dormancy due to 0 active streams"); break; } - // check to see if manual flush is enabled if (auto flushPeriodMs = pm->GetEtwFlushPeriod()) { // flush events manually to reduce latency pmlog_verb(v::etwq)("Manual ETW flush").pmwatch(*flushPeriodMs); pm->FlushEvents(); - currentInterval = double(*flushPeriodMs) / 1000.; + // update flush period + currentInterval = *flushPeriodMs / 1000.; } else { pmlog_verb(v::etwq)("Detected disabled ETW flush, using idle poll period"); + // set a period here (low rate) to check for changes to the etw flush setting currentInterval = disabledIntervalMs / 1000.; } } From 27e2fdae464648fba49028adc13508070102d208 Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 15 Dec 2025 12:32:57 +0900 Subject: [PATCH 052/205] fix race condition on presentmon session --- .../RealtimePresentMonSession.cpp | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp b/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp index 76bc6707..7909a873 100644 --- a/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp +++ b/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp @@ -105,7 +105,7 @@ void RealtimePresentMonSession::ResetEtwFlushPeriod() } PM_STATUS RealtimePresentMonSession::StartTraceSession() { - std::lock_guard lock(session_mutex_); + std::lock_guard lock(session_mutex_); if (pm_consumer_) { return PM_STATUS::PM_STATUS_SERVICE_ERROR; @@ -182,26 +182,28 @@ PM_STATUS RealtimePresentMonSession::StartTraceSession() { void RealtimePresentMonSession::StopTraceSession() { // PHASE 1: Signal shutdown and wait for threads to observe it - session_active_.store(false, std::memory_order_release); - - // Stop the trace session to stop new events from coming in - trace_session_.Stop(); - - // Wait for threads to exit their critical sections and finish - WaitForConsumerThreadToExit(); - StopOutputThread(); - - // PHASE 2: Safe cleanup after threads have finished - std::lock_guard lock(session_mutex_); - - // Stop all streams - streamer_.StopAllStreams(); - if (evtStreamingStarted_) { - evtStreamingStarted_.Reset(); - } + // this also enforces "only_once" semantics for multiple stop callers + if (session_active_.exchange(false, std::memory_order_acq_rel)) { - if (pm_consumer_) { - pm_consumer_.reset(); + // Stop the trace session to stop new events from coming in + trace_session_.Stop(); + + // Wait for threads to exit their critical sections and finish + WaitForConsumerThreadToExit(); + StopOutputThread(); + + // PHASE 2: Safe cleanup after threads have finished + std::lock_guard lock(session_mutex_); + + // Stop all streams + streamer_.StopAllStreams(); + if (evtStreamingStarted_) { + evtStreamingStarted_.Reset(); + } + + if (pm_consumer_) { + pm_consumer_.reset(); + } } } From 9ea3d70c98964fcaa76caebd0e773d3ee7e26b44 Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 15 Dec 2025 17:58:54 +0900 Subject: [PATCH 053/205] getting frames from playback session --- .../InterimBroadcasterTests.cpp | 53 ++++++++++++++++++- .../MockPresentMonSession.cpp | 29 +++++----- 2 files changed, 68 insertions(+), 14 deletions(-) diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 0cf0100b..90de5487 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -471,7 +471,7 @@ namespace InterimBroadcasterTests } }; - TEST_CLASS(FrameStoreTests) + TEST_CLASS(FrameStoreRealtimeTests) { TestFixture fixture_; public: @@ -566,4 +566,55 @@ namespace InterimBroadcasterTests Assert::IsTrue(range2.second - range2.first < range3.second - range3.first); } }; + + + TEST_CLASS(FrameStorePlaybackTests) + { + TestFixture fixture_; + public: + TEST_METHOD_INITIALIZE(Setup) + { + fixture_.Setup({ + "--etl-test-file"s, R"(..\..\Tests\AuxData\Data\P00HeaWin2080.etl)", + "--pace-playback"s, + }); + } + TEST_METHOD_CLEANUP(Cleanup) + { + fixture_.Cleanup(); + } + TEST_METHOD(ReadFrames) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + + // set up a fast flush + client.DispatchSync(svc::acts::SetEtwFlushPeriod::Params{ .etwFlushPeriodMs = 8 }); + // make sure the flush period propagates to the flusher thread + std::this_thread::sleep_for(1ms); + // we know the pid of interest in this etl file, track it + const uint32_t pid = 12820; + client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pid }); + + // open the store + pComms->OpenFrameDataStore(pid); + auto& frames = pComms->GetFrameDataStore(pid).frameData; + + // sleep here to let the etw system warm up, and frames propagate + std::this_thread::sleep_for(450ms); + + // verify that frames are added over time + const auto range1 = frames.GetSerialRange(); + Logger::WriteMessage(std::format("range [{},{})\n", range1.first, range1.second).c_str()); + std::this_thread::sleep_for(100ms); + const auto range2 = frames.GetSerialRange(); + Logger::WriteMessage(std::format("range [{},{})\n", range2.first, range2.second).c_str()); + std::this_thread::sleep_for(100ms); + const auto range3 = frames.GetSerialRange(); + Logger::WriteMessage(std::format("range [{},{})\n", range3.first, range3.second).c_str()); + + Assert::IsTrue(range1.second - range1.first < range2.second - range2.first); + Assert::IsTrue(range2.second - range2.first < range3.second - range3.first); + } + }; } diff --git a/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp b/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp index c736e50d..2a1a6230 100644 --- a/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp +++ b/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp @@ -184,24 +184,26 @@ PM_STATUS MockPresentMonSession::StartTraceSession(uint32_t processId, const std void MockPresentMonSession::StopTraceSession() { // PHASE 1: Signal shutdown and wait for threads to observe it - session_active_.store(false, std::memory_order_release); + // also enforce only_once semantics with atomic flag + if (session_active_.exchange(false, std::memory_order_acq_rel)) { - // Stop the trace session. - trace_session_.Stop(); + // Stop the trace session. + trace_session_.Stop(); - // Wait for the consumer and output threads to end (which are using the - // consumers). - WaitForConsumerThreadToExit(); - StopOutputThread(); + // Wait for the consumer and output threads to end (which are using the + // consumers). + WaitForConsumerThreadToExit(); + StopOutputThread(); - // PHASE 2: Safe cleanup after threads have finished - std::lock_guard lock(session_mutex_); + // PHASE 2: Safe cleanup after threads have finished + std::lock_guard lock(session_mutex_); - // Stop all streams - streamer_.StopAllStreams(); + // Stop all streams + streamer_.StopAllStreams(); - if (pm_consumer_) { - pm_consumer_.reset(); + if (pm_consumer_) { + pm_consumer_.reset(); + } } } @@ -322,6 +324,7 @@ void MockPresentMonSession::AddPresents( processInfo->mModuleName, gpu_telemetry_cap_bits, cpu_telemetry_cap_bits); + pBroadcaster->Broadcast(*presentEvent); chain->mLastPresentQPC = presentEvent->PresentStartTime; if (presentEvent->FinalState == PresentResult::Presented) { From eae19605407bdf19a0e650b6ebc8b138fd6f2282 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Mon, 15 Dec 2025 07:09:08 -0800 Subject: [PATCH 054/205] Added support for MsBetweenSimulationStarts. Added instrumented ULTs --- .../CommonUtilities/mc/MetricsCalculator.cpp | 21 ++ IntelPresentMon/UnitTests/MetricsCore.cpp | 270 ++++++++++++++++++ 2 files changed, 291 insertions(+) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index edc0241d..676988d6 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -523,6 +523,22 @@ namespace pmon::util::metrics return qpc.DeltaUnsignedMilliSeconds(instrumentedStartTime, present.gpuStartTime); } + std::optional ComputeMsBetweenSimulationStarts( + const QpcConverter& qpc, + const SwapChainCoreState& chain, + const FrameData& present) + { + auto currentSimStartTime = CalculateSimStartTime(chain, present, chain.animationErrorSource); + if (chain.lastSimStartTime != 0 && currentSimStartTime != 0) { + return qpc.DeltaUnsignedMilliSeconds( + chain.lastSimStartTime, + currentSimStartTime); + } + else { + return std::nullopt; + } + } + void ApplyStateDeltas( SwapChainCoreState& chainState, const ComputedMetrics::StateDeltas& d) @@ -1032,6 +1048,11 @@ namespace pmon::util::metrics present, isDisplayed, isAppFrame); + + metrics.msBetweenSimStarts = ComputeMsBetweenSimulationStarts( + qpc, + chain, + present); } ComputedMetrics ComputeFrameMetrics( diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index 1ad4bbd2..c2f05f97 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -7684,4 +7684,274 @@ namespace MetricsCoreTests Assert::AreEqual(size_t(0), p1_phase1.size()); } }; + TEST_CLASS(InstrumentedMetricsTests) + { + public: + + TEST_METHOD(InstrumentedCpuGpu_AppFrame_FullData_UsesPclSimStart) + { + // This test verifies the "instrumented CPU/GPU" metrics on an application frame: + // + // - msInstrumentedSleep + // - msInstrumentedGpuLatency + // - msBetweenSimStarts (PCL sim preferred over App sim) + // + // We construct: + // + // QPC frequency = 10 MHz + // + // Pre-state in swapChain: + // lastSimStartTime = 10'000 (this represents the previous frame's sim start) + // + // P0 (the frame under test) – APP FRAME: + // appSleepStartTime = 1'000 + // appSleepEndTime = 11'000 // Δsleep = 10'000 ticks + // appSimStartTime =100'000 // should NOT be used for between-sim-starts + // pclSimStartTime = 20'000 // PCL sim should win for between-sim-starts + // gpuStartTime = 21'000 // GPU start time used for GPU latency + // displayed = one Application entry at screenTime = 50'000 + // + // Derived deltas: + // sleep Δ: 11'000 - 1'000 = 10'000 ticks + // PCL sim Δ: 20'000 - 10'000 = 10'000 ticks + // GPU latency Δ: 21'000 - 11'000 = 10'000 ticks + // + // With QPC = 10 MHz, 10'000 ticks = 0.001 ms. + // + // Call pattern (ReportMetrics-style for a single displayed app frame): + // + // P0 arrives: + // ComputeMetricsForPresent(P0, nullptr, chain) // Case 2, pending only + // + // P1 arrives later: + // ComputeMetricsForPresent(P0, &P1, chain) // Case 3, finalize P0 + // ComputeMetricsForPresent(P1, nullptr, chain) // pending P1 (ignored in this test) + // + // We verify on P0's final metrics: + // - msInstrumentedSleep has a value and matches Δ(appSleepStart, appSleepEnd). + // - msInstrumentedGpuLatency has a value and matches Δ(appSleepEnd, gpuStartTime). + // - msBetweenSimStarts has a value and matches Δ(lastSimStartTime, P0.pclSimStartTime), + // proving PCL sim is preferred over App sim for between-sim-starts. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + // Seed lastSimStartTime to simulate a previous frame. + chain.lastSimStartTime = 10'000; + chain.animationErrorSource = AnimationErrorSource::PCLatency; + + const uint32_t PROCESS_ID = 1234; + const uint64_t SWAPCHAIN = 0xABC0; + + // -------------------------------------------------------------------- + // P0: Application frame with full instrumented CPU/GPU data + // -------------------------------------------------------------------- + FrameData p0{}; + p0.processId = PROCESS_ID; + p0.swapChainAddress = SWAPCHAIN; + + p0.presentStartTime = 0; + p0.timeInPresent = 0; + p0.readyTime = 0; + + // Instrumented CPU / sim: + p0.appSleepStartTime = 1'000; + p0.appSleepEndTime = 11'000; + p0.appSimStartTime = 100'000; // should NOT be used for between-sim-starts + p0.pclSimStartTime = 20'000; // should be used instead + + // GPU start time for GPU latency: + p0.gpuStartTime = 21'000; + + // Mark as displayed Application frame + p0.setFinalState(PresentResult::Presented); + p0.displayed.clear(); + p0.displayed.push_back({ FrameType::Application, 50'000 }); // screenTime = 50'000 + + // First call: P0 arrives, becomes pending (no metrics yet). + auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); + Assert::AreEqual(size_t(0), p0_phase1.size(), + L"P0 (phase 1): pending-only call with nextDisplayed=nullptr should produce no metrics."); + + // -------------------------------------------------------------------- + // P1: simple next displayed app frame (used only as nextDisplayed for P0) + // -------------------------------------------------------------------- + FrameData p1{}; + p1.processId = PROCESS_ID; + p1.swapChainAddress = SWAPCHAIN; + + p1.presentStartTime = 0; + p1.timeInPresent = 0; + p1.readyTime = 0; + + p1.setFinalState(PresentResult::Presented); + p1.displayed.clear(); + p1.displayed.push_back({ FrameType::Application, 60'000 }); // later display, not important + + // Second call: P1 arrives, finalize P0 using P1 as nextDisplayed (Case 3). + auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); + Assert::AreEqual(size_t(1), p0_final.size(), + L"P0 (final): expected exactly one metrics record when flushed with nextDisplayed=P1."); + + const auto& m0 = p0_final[0].metrics; + + // -------------------------------------------------------------------- + // Assertions for P0's instrumented CPU/GPU metrics + // -------------------------------------------------------------------- + // Expected values based on our chosen QPC times: + double expectedSleepMs = qpc.DeltaUnsignedMilliSeconds(1'000, 11'000); // 10'000 ticks + double expectedGpuMs = qpc.DeltaUnsignedMilliSeconds(11'000, 21'000); // 10'000 ticks + double expectedBetween = qpc.DeltaUnsignedMilliSeconds(10'000, 20'000); // 10'000 ticks + + // 1) Instrumented sleep + Assert::IsTrue(m0.msInstrumentedSleep.has_value(), + L"P0: msInstrumentedSleep should have a value for valid AppSleepStart/End."); + Assert::AreEqual(expectedSleepMs, m0.msInstrumentedSleep.value(), 1e-6, + L"P0: msInstrumentedSleep did not match expected Δ(AppSleepStart, AppSleepEnd)."); + + // 2) Instrumented GPU latency (start = AppSleepEndTime since it is non-zero) + Assert::IsTrue(m0.msInstrumentedGpuLatency.has_value(), + L"P0: msInstrumentedGpuLatency should have a value when InstrumentedStartTime and gpuStartTime are valid."); + Assert::AreEqual(expectedGpuMs, m0.msInstrumentedGpuLatency.value(), 1e-6, + L"P0: msInstrumentedGpuLatency did not match expected Δ(AppSleepEndTime, gpuStartTime)."); + + // 3) Between sim starts: PCL sim (20'000) must win over App sim (100'000) + Assert::IsTrue(m0.msBetweenSimStarts.has_value(), + L"P0: msBetweenSimStarts should have a value when lastSimStartTime and PclSimStartTime are non-zero."); + Assert::AreEqual(expectedBetween, m0.msBetweenSimStarts.value(), 1e-6, + L"P0: msBetweenSimStarts should be based on PCL sim start, not App sim start."); + } + TEST_METHOD(InstrumentedDisplay_AppFrame_FullData_ComputesAll) + { + // This test verifies the "instrumented display" metrics on a displayed + // application frame: + // + // - msInstrumentedRenderLatency + // - msReadyTimeToDisplayLatency + // - msInstrumentedLatency (total app-instrumented latency) + // + // New invariant (unified metrics): + // These metrics are only computed when: + // - the frame is an Application frame (isAppFrame == true), AND + // - the frame is displayed (isDisplayed == true). + // + // We construct: + // + // QPC frequency = 10 MHz + // + // P0 (the frame under test) – DISPLAYED APP FRAME: + // appRenderSubmitStartTime = 10'000 + // readyTime = 20'000 + // appSleepEndTime = 5'000 // used as InstrumentedStartTime + // screenTime = 30'000 (Application entry in displayed) + // + // Derived deltas: + // render latency: 30'000 - 10'000 = 20'000 ticks + // ready→display: 30'000 - 20'000 = 10'000 ticks + // total inst. latency: 30'000 - 5'000 = 25'000 ticks + // + // With QPC = 10 MHz: + // 10'000 ticks = 0.001 ms + // 20'000 ticks = 0.002 ms + // 25'000 ticks = 0.0025 ms + // + // Call pattern (mirroring ReportMetrics for a displayed app frame): + // + // P0 arrives: + // ComputeMetricsForPresent(P0, nullptr, chain) // Case 2, pending only + // + // P1 arrives: + // ComputeMetricsForPresent(P0, &P1, chain) // Case 3, finalize P0 + // ComputeMetricsForPresent(P1, nullptr, chain) // pending P1 (ignored) + // + // We verify on P0's final metrics: + // - msInstrumentedRenderLatency has a value and matches Δ(appRenderSubmitStartTime, screenTime). + // - msReadyTimeToDisplayLatency has a value and matches Δ(readyTime, screenTime). + // - msInstrumentedLatency has a value and matches Δ(appSleepEndTime, screenTime). + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + const uint32_t PROCESS_ID = 1234; + const uint64_t SWAPCHAIN = 0xABC0; + + // -------------------------------------------------------------------- + // P0: Displayed Application frame with full instrumented display data + // -------------------------------------------------------------------- + FrameData p0{}; + p0.processId = PROCESS_ID; + p0.swapChainAddress = SWAPCHAIN; + + p0.presentStartTime = 0; + p0.timeInPresent = 0; + p0.readyTime = 20'000; // ReadyTime + + // Instrumented markers + p0.appRenderSubmitStartTime = 10'000; + p0.appSleepEndTime = 5'000; + p0.appSimStartTime = 0; // not needed in this test + + // Mark as displayed Application frame with a single screen time. + p0.setFinalState(PresentResult::Presented); + p0.displayed.clear(); + p0.displayed.push_back({ FrameType::Application, 30'000 }); // screenTime = 30'000 + + // First call: P0 arrives, becomes pending. + auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); + Assert::AreEqual(size_t(0), p0_phase1.size(), + L"P0 (phase 1): pending-only call with nextDisplayed=nullptr should produce no metrics."); + + // -------------------------------------------------------------------- + // P1: Next displayed app frame (used only as nextDisplayed for P0) + // -------------------------------------------------------------------- + FrameData p1{}; + p1.processId = PROCESS_ID; + p1.swapChainAddress = SWAPCHAIN; + + p1.presentStartTime = 0; + p1.timeInPresent = 0; + p1.readyTime = 0; + + p1.setFinalState(PresentResult::Presented); + p1.displayed.clear(); + p1.displayed.push_back({ FrameType::Application, 40'000 }); // later display + + // Second call: finalize P0 with nextDisplayed=P1 + auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); + Assert::AreEqual(size_t(1), p0_final.size(), + L"P0 (final): expected exactly one metrics record when flushed with nextDisplayed=P1."); + + const auto& m0 = p0_final[0].metrics; + + // For completeness, process P1 as pending (not used in this test). + auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, chain); + Assert::AreEqual(size_t(0), p1_phase1.size(), + L"P1 (phase 1): first call with nextDisplayed=nullptr should produce no metrics (pending only)."); + + // -------------------------------------------------------------------- + // Assertions for P0's instrumented display metrics + // -------------------------------------------------------------------- + double expectedRenderMs = qpc.DeltaUnsignedMilliSeconds(10'000, 30'000); // 20'000 ticks + double expectedReadyMs = qpc.DeltaUnsignedMilliSeconds(20'000, 30'000); // 10'000 ticks + double expectedTotalMs = qpc.DeltaUnsignedMilliSeconds(5'000, 30'000); // 25'000 ticks + + // Render latency + Assert::IsTrue(m0.msInstrumentedRenderLatency.has_value(), + L"P0: msInstrumentedRenderLatency should have a value for a displayed app frame with AppRenderSubmitStartTime."); + Assert::AreEqual(expectedRenderMs, m0.msInstrumentedRenderLatency.value(), 1e-6, + L"P0: msInstrumentedRenderLatency did not match expected Δ(AppRenderSubmitStartTime, screenTime)."); + + // Ready-to-display latency + Assert::IsTrue(m0.msReadyTimeToDisplayLatency.has_value(), + L"P0: msReadyTimeToDisplayLatency should have a value when ReadyTime and screenTime are valid."); + Assert::AreEqual(expectedReadyMs, m0.msReadyTimeToDisplayLatency.value(), 1e-6, + L"P0: msReadyTimeToDisplayLatency did not match expected Δ(ReadyTime, screenTime)."); + + // Total instrumented latency: from appSleepEndTime to screenTime + Assert::IsTrue(m0.msInstrumentedLatency.has_value(), + L"P0: msInstrumentedLatency should have a value when there is a valid instrumented start time."); + Assert::AreEqual(expectedTotalMs, m0.msInstrumentedLatency.value(), 1e-6, + L"P0: msInstrumentedLatency did not match expected Δ(AppSleepEndTime, screenTime)."); + } + }; } \ No newline at end of file From 8dd66672114b1e27bcd49ebed927a573f418a285 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Mon, 15 Dec 2025 11:55:17 -0800 Subject: [PATCH 055/205] Added additional instrumented app provider tests and fixes. --- .../CommonUtilities/mc/MetricsCalculator.cpp | 22 +- .../CommonUtilities/mc/MetricsTypes.h | 2 +- IntelPresentMon/UnitTests/MetricsCore.cpp | 675 +++++++++++++++++- 3 files changed, 687 insertions(+), 12 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 676988d6..cb55e368 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -105,7 +105,7 @@ namespace pmon::util::metrics out.msReadyTimeToDisplayLatency = qpc.DeltaUnsignedMilliSeconds(present.getReadyTime(), screenTime); } else { - out.msReadyTimeToDisplayLatency = 0.0; + out.msReadyTimeToDisplayLatency = std::nullopt; } } @@ -338,6 +338,8 @@ namespace pmon::util::metrics // Not displayed: stash the click for a future displayed frame. stateDeltas.lastReceivedNotDisplayedMouseClickTime = inputTime; return std::nullopt; + } else { + stateDeltas.shouldResetInputTimes = true; } } // Case 2: No click on this frame, but frame is displayed: see if we can @@ -380,6 +382,8 @@ namespace pmon::util::metrics // Not displayed: stash the click for a future displayed frame. stateDeltas.lastReceivedNotDisplayedAllInputTime = inputTime; return std::nullopt; + } else { + stateDeltas.shouldResetInputTimes = true; } } // Case 2: No click on this frame, but frame is displayed: see if we can @@ -421,6 +425,8 @@ namespace pmon::util::metrics // Not displayed: stash the click for a future displayed frame. stateDeltas.lastReceivedNotDisplayedAppProviderInputTime = inputTime; return std::nullopt; + } else { + stateDeltas.shouldResetInputTimes = true; } } // Case 2: No click on this frame, but frame is displayed: see if we can @@ -485,7 +491,7 @@ namespace pmon::util::metrics bool isAppFrame, uint64_t screenTime) { - if (!isDisplayed || !isAppFrame) { + if (!isAppFrame) { return std::nullopt; } @@ -503,7 +509,7 @@ namespace pmon::util::metrics bool isDisplayed, bool isAppFrame) { - if (!isDisplayed || !isAppFrame) { + if (!isAppFrame) { return std::nullopt; } @@ -526,8 +532,13 @@ namespace pmon::util::metrics std::optional ComputeMsBetweenSimulationStarts( const QpcConverter& qpc, const SwapChainCoreState& chain, - const FrameData& present) + const FrameData& present, + bool isAppFrame) { + if (!isAppFrame) { + return std::nullopt; + } + auto currentSimStartTime = CalculateSimStartTime(chain, present, chain.animationErrorSource); if (chain.lastSimStartTime != 0 && currentSimStartTime != 0) { return qpc.DeltaUnsignedMilliSeconds( @@ -1052,7 +1063,8 @@ namespace pmon::util::metrics metrics.msBetweenSimStarts = ComputeMsBetweenSimulationStarts( qpc, chain, - present); + present, + isAppFrame); } ComputedMetrics ComputeFrameMetrics( diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h index efd3c65d..8d2805d4 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h @@ -143,6 +143,7 @@ namespace pmon::util::metrics { double msUntilDisplayed; double msBetweenDisplayChange; uint64_t screenTimeQpc; + std::optional msReadyTimeToDisplayLatency; // CPU Metrics (app frames only) uint64_t cpuStartQpc; @@ -170,7 +171,6 @@ namespace pmon::util::metrics { std::optional msInstrumentedRenderLatency; std::optional msInstrumentedSleep; std::optional msInstrumentedGpuLatency; - std::optional msReadyTimeToDisplayLatency; std::optional msBetweenSimStarts; // PCLatency (optional) diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index c2f05f97..7d7c082a 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -2413,14 +2413,13 @@ namespace MetricsCoreTests Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; - Assert::IsTrue(m.msReadyTimeToDisplayLatency.has_value()); - Assert::AreEqual(0.0, m.msReadyTimeToDisplayLatency.value(), 0.0001); + Assert::IsFalse(m.msReadyTimeToDisplayLatency.has_value()); } TEST_METHOD(ReadyTimeToDisplay_ReadyTimeZero) { // Scenario: Ready time not set (edge case, readyTime = 0). - // readyTime = 0, screenTime = 2'000'000 + // readyTime = 70'000, screenTime = 2'000'000 // Expected: msReadyTimeToDisplayLatency ≈ 0.2 ms (2'000'000 ticks) QpcConverter qpc(10'000'000, 0); @@ -2429,7 +2428,7 @@ namespace MetricsCoreTests FrameData frame{}; frame.presentStartTime = 1'000'000; frame.timeInPresent = 50'000; - frame.readyTime = 0; + frame.readyTime = 70'000; frame.setFinalState(PresentResult::Presented); frame.displayed.push_back({ FrameType::Application, 2'000'000 }); @@ -2441,8 +2440,8 @@ namespace MetricsCoreTests Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; - // msReadyTimeToDisplayLatency = 2'000'000 - 0 = 2'000'000 ticks = 0.2 ms - double expected = qpc.DeltaUnsignedMilliSeconds(0, 2'000'000); + // msReadyTimeToDisplayLatency = 2'000'000 - 70'000 = 1'930'000 ticks = 0.193 ms + double expected = qpc.DeltaUnsignedMilliSeconds(70'000, 2'000'000); Assert::IsTrue(m.msReadyTimeToDisplayLatency.has_value()); Assert::AreEqual(expected, m.msReadyTimeToDisplayLatency.value(), 0.0001); } @@ -7953,5 +7952,669 @@ namespace MetricsCoreTests Assert::AreEqual(expectedTotalMs, m0.msInstrumentedLatency.value(), 1e-6, L"P0: msInstrumentedLatency did not match expected Δ(AppSleepEndTime, screenTime)."); } + + TEST_METHOD(InstrumentedCpuGpu_AppFrame_NoSleep_UsesAppSimStart) + { + // Scenario: + // - Validate the instrumented CPU/GPU metrics when the application never enters an + // instrumented sleep, forcing GPU latency to fall back to appSimStart. + // - Also ensure msBetweenSimStarts uses the stored lastSimStartTime → appSimStart delta + // when no PCL sim timestamp is present. + // + // QPC frequency: 10 MHz. + // + // Pre-state: + // chain.lastSimStartTime = 40'000 (represents the previous frame's sim start). + // + // P0 (displayed Application frame): + // appSleepStartTime = 0 + // appSleepEndTime = 0 + // appSimStartTime = 70'000 (used for GPU latency + between-sim-starts) + // pclSimStartTime = 0 (forces AppSim fallback) + // gpuStartTime = 90'000 + // screenTime = 120'000 (Application entry) + // Δ(sim start vs last) = 30'000 ticks, Δ(sim start → gpu) = 20'000 ticks. + // + // Call pattern (Case 2/3): + // P0 pending → Compute(..., nullptr) + // P1 arrives → Compute(P0, &P1) to finalize P0, then Compute(P1, nullptr) to seed next pending. + // + // Expectations on P0 final metrics: + // - msInstrumentedSleep has no value (no sleep interval). + // - msInstrumentedGpuLatency uses appSimStartTime (70'000 → 90'000). + // - msBetweenSimStarts computes 40'000 → 70'000. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + chain.lastSimStartTime = 40'000; + chain.animationErrorSource = AnimationErrorSource::AppProvider; + + const uint32_t PROCESS_ID = 4321; + const uint64_t SWAPCHAIN = 0x2222; + + // P0: displayed Application frame with no sleep range but valid appSimStart + FrameData p0{}; + p0.processId = PROCESS_ID; + p0.swapChainAddress = SWAPCHAIN; + p0.appSimStartTime = 70'000; + p0.gpuStartTime = 90'000; + p0.setFinalState(PresentResult::Presented); + p0.displayed.clear(); + p0.displayed.push_back({ FrameType::Application, 120'000 }); + + auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); + Assert::AreEqual(size_t(0), p0_phase1.size(), + L"P0 (phase 1) should stay pending when nextDisplayed is unavailable."); + + // P1: minimal next displayed application frame + FrameData p1{}; + p1.processId = PROCESS_ID; + p1.swapChainAddress = SWAPCHAIN; + p1.setFinalState(PresentResult::Presented); + p1.displayed.clear(); + p1.displayed.push_back({ FrameType::Application, 150'000 }); + + auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); + Assert::AreEqual(size_t(1), p0_final.size(), + L"P0 (final) should emit exactly one metrics record once nextDisplayed is provided."); + + const auto& m0 = p0_final[0].metrics; + Assert::IsFalse(m0.msInstrumentedSleep.has_value(), + L"P0: Instrumented sleep must be absent when the app never emitted sleep markers."); + Assert::IsTrue(m0.msInstrumentedGpuLatency.has_value(), + L"P0: GPU latency should fall back to AppSimStart when no sleep end exists."); + Assert::IsTrue(m0.msBetweenSimStarts.has_value(), + L"P0: Between-sim-starts should use the stored lastSimStartTime when AppSimStart is valid."); + + double expectedGpuMs = qpc.DeltaUnsignedMilliSeconds(70'000, 90'000); + double expectedBetweenMs = qpc.DeltaUnsignedMilliSeconds(40'000, 70'000); + + Assert::AreEqual(expectedGpuMs, m0.msInstrumentedGpuLatency.value(), 1e-6, + L"P0: msInstrumentedGpuLatency should measure Δ(AppSimStartTime, gpuStartTime)."); + Assert::AreEqual(expectedBetweenMs, m0.msBetweenSimStarts.value(), 1e-6, + L"P0: msBetweenSimStarts should use AppSimStart when no PCL sim exists."); + + auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, chain); + Assert::AreEqual(size_t(0), p1_phase1.size(), + L"P1 (phase 1) remains pending for completeness."); + } + + TEST_METHOD(InstrumentedCpuGpu_AppFrame_NoSleepNoSim_NoInstrumentedCpuGpu) + { + // Scenario: + // - Displayed Application frame with no instrumented sleep markers and neither appSimStartTime + // nor pclSimStartTime populated. GPU start exists, but there is no instrumented start anchor. + // + // QPC frequency: 10 MHz. + // Pre-state: chain.lastSimStartTime = 55'000. + // P0 fields: appSleepStart=0, appSleepEnd=0, appSimStart=0, pclSimStart=0, gpuStart=80'000, + // screenTime=100'000 (Application display). + // Derived deltas: none are valid because the start markers are zero. + // + // Call pattern (Case 2/3): + // P0 pending → Compute(..., nullptr) + // P1 arrives → Compute(P0, &P1) to flush, then Compute(P1, nullptr) for completeness. + // + // Expectations: all three instrumented CPU metrics stay std::nullopt for P0. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + chain.lastSimStartTime = 55'000; + + const uint32_t PROCESS_ID = 9876; + const uint64_t SWAPCHAIN = 0xEF00; + + FrameData p0{}; + p0.processId = PROCESS_ID; + p0.swapChainAddress = SWAPCHAIN; + p0.gpuStartTime = 80'000; + p0.setFinalState(PresentResult::Presented); + p0.displayed.clear(); + p0.displayed.push_back({ FrameType::Application, 100'000 }); + + auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); + Assert::AreEqual(size_t(0), p0_phase1.size()); + + FrameData p1{}; + p1.processId = PROCESS_ID; + p1.swapChainAddress = SWAPCHAIN; + p1.setFinalState(PresentResult::Presented); + p1.displayed.push_back({ FrameType::Application, 120'000 }); + + auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); + Assert::AreEqual(size_t(1), p0_final.size()); + + const auto& m0 = p0_final[0].metrics; + Assert::IsFalse(m0.msInstrumentedSleep.has_value(), + L"P0: sleep metrics require both start and end markers."); + Assert::IsFalse(m0.msInstrumentedGpuLatency.has_value(), + L"P0: GPU latency must remain off without an instrumented start time."); + Assert::IsFalse(m0.msBetweenSimStarts.has_value(), + L"P0: between-sim-starts cannot be computed without a new sim start."); + + auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, chain); + Assert::AreEqual(size_t(0), p1_phase1.size()); + } + + TEST_METHOD(InstrumentedCpuGpu_AppFrame_NotDisplayed_StillComputed) + { + // Scenario: + // - Dropped (not displayed) Application frame with full instrumented CPU/GPU markers, including PCL sim. + // - Validates that CPU/GPU metrics still compute even when the frame never displays, while + // display-only metrics remain unset. + // + // QPC frequency: 10 MHz. + // Pre-state: chain.lastSimStartTime = 5'000. + // P0 fields: appSleepStart=10'000, appSleepEnd=25'000, appSimStart=30'000, + // gpuStart=45'000, no displayed entries (finalState = Discarded). + // Derived deltas: sleep Δ = 15'000 ticks, GPU latency Δ = 20'000 ticks, + // between-sim-starts Δ = 30'000 ticks. + // + // Call pattern: Case 1 (pure dropped) → single ComputeMetricsForPresent call with nextDisplayed == nullptr. + // + // Expectations: instrumented sleep/GPU/betweenSimStarts all have values with the deltas above, + // and display instrumented metrics remain std::nullopt. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + chain.lastSimStartTime = 5'000; + chain.animationErrorSource = AnimationErrorSource::AppProvider; + + FrameData p0{}; + p0.appSleepStartTime = 10'000; + p0.appSleepEndTime = 25'000; + p0.appSimStartTime = 30'000; + p0.gpuStartTime = 45'000; + p0.setFinalState(PresentResult::Discarded); + + auto p0_results = ComputeMetricsForPresent(qpc, p0, nullptr, chain); + Assert::AreEqual(size_t(1), p0_results.size(), + L"Dropped frames should emit their metrics immediately (Case 1)."); + + const auto& m0 = p0_results[0].metrics; + double expectedSleepMs = qpc.DeltaUnsignedMilliSeconds(10'000, 25'000); + double expectedGpuMs = qpc.DeltaUnsignedMilliSeconds(25'000, 45'000); + double expectedBetweenMs = qpc.DeltaUnsignedMilliSeconds(5'000, 30'000); + + Assert::IsTrue(m0.msInstrumentedSleep.has_value()); + Assert::AreEqual(expectedSleepMs, m0.msInstrumentedSleep.value(), 1e-6); + + Assert::IsTrue(m0.msInstrumentedGpuLatency.has_value()); + Assert::AreEqual(expectedGpuMs, m0.msInstrumentedGpuLatency.value(), 1e-6); + + Assert::IsTrue(m0.msBetweenSimStarts.has_value()); + Assert::AreEqual(expectedBetweenMs, m0.msBetweenSimStarts.value(), 1e-6); + + Assert::IsFalse(m0.msInstrumentedRenderLatency.has_value(), + L"Display-dependent metrics must stay off for non-displayed frames."); + Assert::IsFalse(m0.msReadyTimeToDisplayLatency.has_value()); + Assert::IsFalse(m0.msInstrumentedLatency.has_value()); + } + + TEST_METHOD(InstrumentedCpuGpu_NonAppFrame_Ignored) + { + // Scenario: + // - Displayed frame whose sole display entry is FrameType::Repeated, so DisplayIndexing never + // marks an Application display. + // - Even with instrumented CPU/GPU markers present, msInstrumentedSleep/GpuLatency/BetweenSimStarts + // must remain unset because the display instance is not an app frame. + // + // QPC frequency: 10 MHz. + // Pre-state: chain.lastSimStartTime = 60'000. + // P0 fields: appSleepStart=11'000, appSleepEnd=21'000, appSimStart=70'000, pclSimStart=72'000, + // gpuStart=90'000, displayed[0] = (Repeated, 120'000). + // Derived deltas (that should be ignored): sleep Δ = 10'000 ticks, GPU Δ = 69'000 ticks, + // between-sim-starts Δ = 10'000 ticks. + // + // Call pattern: Case 2/3 (P0 pending, finalized by a synthetic P1, then P1 pending). + // + // Expectations: all instrumented CPU metrics remain std::nullopt for P0. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + chain.lastSimStartTime = 60'000; + + const uint32_t PROCESS_ID = 5555; + const uint64_t SWAPCHAIN = 0xDEADBEEF; + + FrameData p0{}; + p0.processId = PROCESS_ID; + p0.swapChainAddress = SWAPCHAIN; + p0.appSleepStartTime = 11'000; + p0.appSleepEndTime = 21'000; + p0.appSimStartTime = 70'000; + p0.pclSimStartTime = 72'000; + p0.gpuStartTime = 90'000; + p0.setFinalState(PresentResult::Presented); + p0.displayed.clear(); + p0.displayed.push_back({ FrameType::Repeated, 120'000 }); + + auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); + Assert::AreEqual(size_t(0), p0_phase1.size()); + + FrameData p1{}; + p1.processId = PROCESS_ID; + p1.swapChainAddress = SWAPCHAIN; + p1.setFinalState(PresentResult::Presented); + p1.displayed.push_back({ FrameType::Application, 150'000 }); + + auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); + Assert::AreEqual(size_t(1), p0_final.size()); + const auto& m0 = p0_final[0].metrics; + + Assert::IsFalse(m0.msInstrumentedSleep.has_value(), + L"Non-app displays must not emit instrumented CPU metrics."); + Assert::IsFalse(m0.msInstrumentedGpuLatency.has_value()); + Assert::IsFalse(m0.msBetweenSimStarts.has_value()); + + auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, chain); + Assert::AreEqual(size_t(0), p1_phase1.size()); + } + + TEST_METHOD(InstrumentedDisplay_AppFrame_NoRenderSubmit_RenderLatencyOff) + { + // Scenario: + // - Displayed Application frame missing appRenderSubmitStartTime but with readyTime + sleep end. + // + // QPC frequency: 10 MHz. + // P0 fields: readyTime = 80'000, appSleepEndTime = 50'000, no render submit, screenTime = 100'000. + // Derived deltas: ready→display Δ = 20'000 ticks, total latency Δ = 50'000 ticks. + // + // Call pattern: Case 2/3 (P0 pending, finalized by P1, then P1 pending). + // + // Expectations: msInstrumentedRenderLatency = nullopt, while msReadyTimeToDisplayLatency and + // msInstrumentedLatency match the deltas above. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData p0{}; + p0.readyTime = 80'000; + p0.appSleepEndTime = 50'000; + p0.setFinalState(PresentResult::Presented); + p0.displayed.push_back({ FrameType::Application, 100'000 }); + + auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); + Assert::AreEqual(size_t(0), p0_phase1.size()); + + FrameData p1{}; + p1.setFinalState(PresentResult::Presented); + p1.displayed.push_back({ FrameType::Application, 130'000 }); + + auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); + Assert::AreEqual(size_t(1), p0_final.size()); + const auto& m0 = p0_final[0].metrics; + + double expectedReadyMs = qpc.DeltaUnsignedMilliSeconds(80'000, 100'000); + double expectedTotalMs = qpc.DeltaUnsignedMilliSeconds(50'000, 100'000); + + Assert::IsFalse(m0.msInstrumentedRenderLatency.has_value(), + L"Render latency must remain off without appRenderSubmitStartTime."); + Assert::IsTrue(m0.msReadyTimeToDisplayLatency.has_value()); + Assert::AreEqual(expectedReadyMs, m0.msReadyTimeToDisplayLatency.value(), 1e-6); + + Assert::IsTrue(m0.msInstrumentedLatency.has_value()); + Assert::AreEqual(expectedTotalMs, m0.msInstrumentedLatency.value(), 1e-6); + + auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, chain); + Assert::AreEqual(size_t(0), p1_phase1.size()); + } + + TEST_METHOD(InstrumentedDisplay_AppFrame_NoSleep_UsesAppSimStart) + { + // Scenario: + // - Displayed Application frame lacks appSleepEndTime but provides appSimStartTime. + // + // QPC timeline (10 MHz): + // P0.appRenderSubmitStartTime = 10'000 + // P0.appSimStartTime = 5'000 + // P0.readyTime = 30'000 + // P0.screenTime = 60'000 + // Derived deltas: + // - Render latency: 50'000 ticks + // - Ready→display: 30'000 ticks + // - Total instrumented latency (AppSim→screen): 55'000 ticks + // + // Call pattern: Case 2/3. + // + // Expectations: render + ready latencies computed normally; msInstrumentedLatency should fall back + // to AppSimStartTime since sleep end is missing. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData p0{}; + p0.appRenderSubmitStartTime = 10'000; + p0.appSimStartTime = 5'000; + p0.readyTime = 30'000; + p0.appSleepEndTime = 0; + p0.setFinalState(PresentResult::Presented); + p0.displayed.push_back({ FrameType::Application, 60'000 }); + + auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); + Assert::AreEqual(size_t(0), p0_phase1.size()); + + FrameData p1{}; + p1.setFinalState(PresentResult::Presented); + p1.displayed.push_back({ FrameType::Application, 90'000 }); + + auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); + Assert::AreEqual(size_t(1), p0_final.size()); + const auto& m0 = p0_final[0].metrics; + + double expectedRenderMs = qpc.DeltaUnsignedMilliSeconds(10'000, 60'000); + double expectedReadyMs = qpc.DeltaUnsignedMilliSeconds(30'000, 60'000); + double expectedTotalMs = qpc.DeltaUnsignedMilliSeconds(5'000, 60'000); + + Assert::IsTrue(m0.msInstrumentedRenderLatency.has_value()); + Assert::AreEqual(expectedRenderMs, m0.msInstrumentedRenderLatency.value(), 1e-6); + Assert::IsTrue(m0.msReadyTimeToDisplayLatency.has_value()); + Assert::AreEqual(expectedReadyMs, m0.msReadyTimeToDisplayLatency.value(), 1e-6); + Assert::IsTrue(m0.msInstrumentedLatency.has_value()); + Assert::AreEqual(expectedTotalMs, m0.msInstrumentedLatency.value(), 1e-6, + L"Total latency should fall back to AppSimStartTime when sleep end is missing."); + + auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, chain); + Assert::AreEqual(size_t(0), p1_phase1.size()); + } + + TEST_METHOD(InstrumentedDisplay_AppFrame_NoSleepNoSim_NoTotalLatency) + { + // Scenario: + // - Displayed Application frame has render submit + ready markers but neither appSleepEndTime + // nor appSimStartTime, so the total instrumented latency should be disabled. + // + // QPC values (10 MHz): appRenderSubmitStartTime = 12'000, readyTime = 32'000, screenTime = 70'000. + // Derived deltas: + // - Render latency Δ = 58'000 ticks + // - Ready→display Δ = 38'000 ticks + // + // Call pattern: Case 2/3. + // + // Expectations: render + ready metrics populated with the deltas above; msInstrumentedLatency is nullopt. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData p0{}; + p0.appRenderSubmitStartTime = 12'000; + p0.readyTime = 32'000; + p0.setFinalState(PresentResult::Presented); + p0.displayed.push_back({ FrameType::Application, 70'000 }); + + auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); + Assert::AreEqual(size_t(0), p0_phase1.size()); + + FrameData p1{}; + p1.setFinalState(PresentResult::Presented); + p1.displayed.push_back({ FrameType::Application, 90'000 }); + + auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); + Assert::AreEqual(size_t(1), p0_final.size()); + const auto& m0 = p0_final[0].metrics; + + double expectedRenderMs = qpc.DeltaUnsignedMilliSeconds(12'000, 70'000); + double expectedReadyMs = qpc.DeltaUnsignedMilliSeconds(32'000, 70'000); + + Assert::IsTrue(m0.msInstrumentedRenderLatency.has_value()); + Assert::AreEqual(expectedRenderMs, m0.msInstrumentedRenderLatency.value(), 1e-6); + Assert::IsTrue(m0.msReadyTimeToDisplayLatency.has_value()); + Assert::AreEqual(expectedReadyMs, m0.msReadyTimeToDisplayLatency.value(), 1e-6); + Assert::IsFalse(m0.msInstrumentedLatency.has_value(), + L"Total instrumented latency must stay off without an instrumented start."); + + auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, chain); + Assert::AreEqual(size_t(0), p1_phase1.size()); + } + + TEST_METHOD(InstrumentedDisplay_NonAppFrame_Ignored) + { + // Scenario: + // - Displayed frame whose first (and only) display entry is FrameType::Repeated, so the + // DisplayIndexing logic never flags an application frame for this present. + // + // QPC values (10 MHz): appRenderSubmitStartTime = 10'000, readyTime = 30'000, appSleepEndTime = 5'000, + // screenTime = 60'000. These deltas should all be ignored. + // + // Call pattern: Case 2/3. + // + // Expectations: all instrumented display metrics remain std::nullopt for P0. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData p0{}; + p0.appRenderSubmitStartTime = 10'000; + p0.readyTime = 30'000; + p0.appSleepEndTime = 5'000; + p0.setFinalState(PresentResult::Presented); + p0.displayed.push_back({ FrameType::Repeated, 60'000 }); + + auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); + Assert::AreEqual(size_t(0), p0_phase1.size()); + + FrameData p1{}; + p1.setFinalState(PresentResult::Presented); + p1.displayed.push_back({ FrameType::Application, 90'000 }); + + auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); + Assert::AreEqual(size_t(1), p0_final.size()); + const auto& m0 = p0_final[0].metrics; + + Assert::IsFalse(m0.msInstrumentedRenderLatency.has_value()); + Assert::IsFalse(m0.msInstrumentedLatency.has_value()); + + auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, chain); + Assert::AreEqual(size_t(0), p1_phase1.size()); + } + + TEST_METHOD(InstrumentedDisplay_AppFrame_NotDisplayed_NoDisplayMetrics) + { + // Scenario: + // - Application frame with appRenderSubmit/ready/sleep/appSim markers that gets discarded (not displayed). + // - Ensures display-only instrumented metrics stay unset when there is no screen time. + // + // QPC values: appRenderSubmitStart = 9'000, readyTime = 19'000, appSleepEnd = 4'000, + // appSimStart = 2'000. No displayed entries, so screenTime is undefined. + // + // Call pattern: Case 1 (single call, nextDisplayed == nullptr). + // + // Expectations: msInstrumentedRenderLatency / msReadyTimeToDisplayLatency / msInstrumentedLatency are nullopt. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + FrameData p0{}; + p0.appRenderSubmitStartTime = 9'000; + p0.readyTime = 19'000; + p0.appSleepEndTime = 4'000; + p0.appSimStartTime = 2'000; + p0.setFinalState(PresentResult::Discarded); + + auto p0_results = ComputeMetricsForPresent(qpc, p0, nullptr, chain); + Assert::AreEqual(size_t(1), p0_results.size()); + const auto& m0 = p0_results[0].metrics; + + Assert::IsFalse(m0.msInstrumentedRenderLatency.has_value()); + Assert::IsFalse(m0.msReadyTimeToDisplayLatency.has_value()); + Assert::IsFalse(m0.msInstrumentedLatency.has_value()); + } + + TEST_METHOD(InstrumentedInput_DroppedAppFrame_PendingProviderInput_ConsumedOnDisplay) + { + // Scenario: + // - P0 is a dropped Application frame that carries an App provider input sample at 20'000 ticks. + // - P1 is the next displayed Application frame (screenTime = 70'000) without its own sample. + // - P2 is a synthetic follower to flush P1, mirroring the ReportMetrics Case 2/3 pattern. + // + // QPC-derived delta: 70'000 - 20'000 = 50'000 ticks = 5 ms (with 10 MHz QPC). + // + // Call pattern: + // - P0 (Case 1) → Compute(..., nullptr) populates lastReceivedNotDisplayedAppProviderInputTime. + // - P1 pending → Compute(P1, nullptr). + // - P1 final → Compute(P1, &P2) produces metrics. + // + // Expectations: + // - Cached provider input equals 20'000 after P0. + // - P1 reports msInstrumentedInputTime = 5 ms and clears all pending caches afterward. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + const uint64_t pendingInputTime = 20'000; + + // P0: Dropped Application frame with provider input. + FrameData p0{}; + p0.appInputSample = { pendingInputTime, InputDeviceType::Mouse }; + p0.setFinalState(PresentResult::Discarded); + + auto p0_results = ComputeMetricsForPresent(qpc, p0, nullptr, chain); + Assert::AreEqual(size_t(1), p0_results.size()); + Assert::AreEqual(pendingInputTime, chain.lastReceivedNotDisplayedAppProviderInputTime, + L"Dropped provider input should be cached until a displayed frame consumes it."); + + // P1: Displayed Application frame without its own AppInputSample. + FrameData p1{}; + p1.setFinalState(PresentResult::Presented); + p1.displayed.push_back({ FrameType::Application, 70'000 }); + + auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, chain); + Assert::AreEqual(size_t(0), p1_phase1.size()); + + // P2: Simple next displayed frame to flush P1. + FrameData p2{}; + p2.setFinalState(PresentResult::Presented); + p2.displayed.push_back({ FrameType::Application, 90'000 }); + + auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, chain); + Assert::AreEqual(size_t(1), p1_final.size()); + const auto& m1 = p1_final[0].metrics; + + Assert::IsTrue(m1.msInstrumentedInputTime.has_value(), + L"P1 should consume the cached provider input time once it is displayed."); + double expectedInputMs = qpc.DeltaUnsignedMilliSeconds(pendingInputTime, 70'000); + Assert::AreEqual(expectedInputMs, m1.msInstrumentedInputTime.value(), 1e-6); + + Assert::AreEqual(uint64_t(0), chain.lastReceivedNotDisplayedAppProviderInputTime, + L"Pending provider input cache must be cleared after consumption."); + Assert::AreEqual(uint64_t(0), chain.lastReceivedNotDisplayedAllInputTime); + Assert::AreEqual(uint64_t(0), chain.lastReceivedNotDisplayedMouseClickTime); + + auto p2_phase1 = ComputeMetricsForPresent(qpc, p2, nullptr, chain); + Assert::AreEqual(size_t(0), p2_phase1.size()); + } + + TEST_METHOD(InstrumentedInput_DisplayedAppFrame_WithOwnSample_IgnoresPending) + { + // Scenario: + // - P0 (dropped) seeds pending provider input at 10'000 ticks. + // - P1 (displayed Application) carries its own sample at 15'000 ticks and displays at 60'000. + // - P2 finalizes P1. + // + // QPC-derived deltas: + // - Pending path would have produced 50'000 ticks, but we expect 45'000 ticks from P1's own sample. + // + // Call pattern: identical to Test 10 (Case 1 for P0, Case 2/3 for P1). + // + // Expectations: + // - Cached pending input updated after P0. + // - P1 final metrics use Δ(15'000, 60'000) only and clear the pending cache. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + const uint64_t pendingInputTime = 10'000; + const uint64_t directInputTime = 15'000; + + FrameData p0{}; + p0.appInputSample = { pendingInputTime, InputDeviceType::Keyboard }; + p0.setFinalState(PresentResult::Discarded); + + auto p0_results = ComputeMetricsForPresent(qpc, p0, nullptr, chain); + Assert::AreEqual(size_t(1), p0_results.size()); + Assert::AreEqual(pendingInputTime, chain.lastReceivedNotDisplayedAppProviderInputTime); + + FrameData p1{}; + p1.appInputSample = { directInputTime, InputDeviceType::Mouse }; + p1.setFinalState(PresentResult::Presented); + p1.displayed.push_back({ FrameType::Application, 60'000 }); + + auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, chain); + Assert::AreEqual(size_t(0), p1_phase1.size()); + + FrameData p2{}; + p2.setFinalState(PresentResult::Presented); + p2.displayed.push_back({ FrameType::Application, 80'000 }); + + auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, chain); + Assert::AreEqual(size_t(1), p1_final.size()); + const auto& m1 = p1_final[0].metrics; + + double expectedInputMs = qpc.DeltaUnsignedMilliSeconds(directInputTime, 60'000); + Assert::IsTrue(m1.msInstrumentedInputTime.has_value()); + Assert::AreEqual(expectedInputMs, m1.msInstrumentedInputTime.value(), 1e-6, + L"P1 must prefer its own input marker over pending values."); + + Assert::AreEqual(uint64_t(0), chain.lastReceivedNotDisplayedAppProviderInputTime); + + auto p2_phase1 = ComputeMetricsForPresent(qpc, p2, nullptr, chain); + Assert::AreEqual(size_t(0), p2_phase1.size()); + } + + TEST_METHOD(InstrumentedInput_NonAppFrame_DoesNotAffectInstrumentedInputTime) + { + // Scenario: + // - P0 is a displayed frame with FrameType::Repeated at screenTime = 50'000 and a provider + // input sample at 25'000 ticks. Because it is not an Application frame, it must NOT seed the + // pending provider cache. + // - P1 is the next displayed Application frame (screenTime = 80'000) without its own sample. + // - P2 finalizes P1. + // + // QPC expectation: since no pending sample exists, msInstrumentedInputTime for P1 must be nullopt. + // + // Call pattern: Case 2/3 for both P0 and P1 (since both are displayed frames). + // + // Expectations: + // - After P0 finalization, chain.lastReceivedNotDisplayedAppProviderInputTime remains 0. + // - P1 final metrics leave msInstrumentedInputTime unset. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + const uint64_t ignoredInputTime = 25'000; + + FrameData p0{}; + p0.appInputSample = { ignoredInputTime, InputDeviceType::Mouse }; + p0.setFinalState(PresentResult::Presented); + p0.displayed.push_back({ FrameType::Repeated, 50'000 }); + + auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); + Assert::AreEqual(size_t(0), p0_phase1.size()); + + FrameData p1{}; + p1.setFinalState(PresentResult::Presented); + p1.displayed.push_back({ FrameType::Application, 80'000 }); + + auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); + Assert::AreEqual(size_t(1), p0_final.size()); + Assert::AreEqual(uint64_t(0), chain.lastReceivedNotDisplayedAppProviderInputTime, + L"Non-app frames should not seed the pending provider input cache."); + + auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, chain); + Assert::AreEqual(size_t(0), p1_phase1.size()); + + FrameData p2{}; + p2.setFinalState(PresentResult::Presented); + p2.displayed.push_back({ FrameType::Application, 100'000 }); + + auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, chain); + Assert::AreEqual(size_t(1), p1_final.size()); + const auto& m1 = p1_final[0].metrics; + Assert::IsFalse(m1.msInstrumentedInputTime.has_value(), + L"P1 should not report instrumented input latency because no app-frame pending sample existed."); + + auto p2_phase1 = ComputeMetricsForPresent(qpc, p2, nullptr, chain); + Assert::AreEqual(size_t(0), p2_phase1.size()); + } }; } \ No newline at end of file From 6ab3c6dcbad359e64e42fe3261540876a8ee687c Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 16 Dec 2025 10:50:29 +0900 Subject: [PATCH 056/205] populate statics for playback targets --- .../Interprocess/source/DataStores.h | 2 +- .../InterimBroadcasterTests.cpp | 26 ++++++++++++++++++- .../PresentMonService/FrameBroadcaster.h | 23 +++++++++++++--- .../MockPresentMonSession.cpp | 1 + .../PresentMonService/acts/StartTracking.h | 5 ++-- 5 files changed, 49 insertions(+), 8 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/DataStores.h b/IntelPresentMon/Interprocess/source/DataStores.h index 7eca4271..aff39c10 100644 --- a/IntelPresentMon/Interprocess/source/DataStores.h +++ b/IntelPresentMon/Interprocess/source/DataStores.h @@ -30,7 +30,7 @@ namespace pmon::ipc struct Bookkeeping { bool staticInitComplete = false; - bool targetExited = false; + bool isPlayback = false; } bookkeeping{}; ShmRing frameData; }; diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 90de5487..1d554e66 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -533,6 +533,7 @@ namespace InterimBroadcasterTests // verify segment can no longer be opened Assert::ExpectException([&] {pComms->OpenFrameDataStore(pres.GetId()); }); } + // make sure we get frames over time TEST_METHOD(ReadFrames) { mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; @@ -583,6 +584,29 @@ namespace InterimBroadcasterTests { fixture_.Cleanup(); } + // static store + TEST_METHOD(StaticData) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + + // track known target + const uint32_t pid = 12820; + client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pid, .isPlayback = true }); + + // open the store + pComms->OpenFrameDataStore(pid); + + // wait for population of frame data-initialized statics + std::this_thread::sleep_for(500ms); + + // verify static data + auto& store = pComms->GetFrameDataStore(pid); + Assert::AreEqual(pid, store.statics.processId); + const std::string staticAppName = store.statics.applicationName.c_str(); + Assert::AreEqual("Heaven.exe"s, staticAppName); + } + // make sure we get frames over time TEST_METHOD(ReadFrames) { mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; @@ -594,7 +618,7 @@ namespace InterimBroadcasterTests std::this_thread::sleep_for(1ms); // we know the pid of interest in this etl file, track it const uint32_t pid = 12820; - client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pid }); + client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pid, .isPlayback = true }); // open the store pComms->OpenFrameDataStore(pid); diff --git a/IntelPresentMon/PresentMonService/FrameBroadcaster.h b/IntelPresentMon/PresentMonService/FrameBroadcaster.h index c1d667cc..d09234c9 100644 --- a/IntelPresentMon/PresentMonService/FrameBroadcaster.h +++ b/IntelPresentMon/PresentMonService/FrameBroadcaster.h @@ -2,6 +2,7 @@ #include "../Interprocess/source/Interprocess.h" #include "../../PresentData/PresentMonTraceConsumer.hpp" #include "../CommonUtilities/win/Utilities.h" +#include "../CommonUtilities/str/String.h" #include #include @@ -77,15 +78,18 @@ namespace pmon::svc public: using Segment = ipc::OwnedDataSegment; FrameBroadcaster(ipc::ServiceComms& comms) : comms_{ comms } {} - std::shared_ptr RegisterTarget(uint32_t pid) + std::shared_ptr RegisterTarget(uint32_t pid, bool isPlayback) { std::lock_guard lk{ mtx_ }; auto pSegment = comms_.CreateOrGetFrameDataSegment(pid); auto& store = pSegment->GetStore(); + // just overwrite these every time Register is called, it will always be the same + store.statics.processId = pid; + store.bookkeeping.isPlayback = isPlayback; // initialize name/pid statics on new store segment creation - if (!store.bookkeeping.staticInitComplete) { + // for playback, this needs to be done when processing 1st process event + if (!store.bookkeeping.staticInitComplete && !isPlayback) { store.bookkeeping.staticInitComplete = true; - store.statics.processId = pid; try { store.statics.applicationName = util::win::GetExecutableModulePath( util::win::OpenProcess(pid) @@ -95,7 +99,6 @@ namespace pmon::svc // if we reach here a race condition has occurred where the target has exited // so we will mark this in the bookkeeping pmlog_warn("Process exited right as it was being initialized").pmwatch(pid); - store.bookkeeping.targetExited = true; } } return pSegment; @@ -107,6 +110,18 @@ namespace pmon::svc pSegment->GetStore().frameData.Push(FrameDataFromPresentEvent(present)); } } + void HandleTargetProcessEvent(const ProcessEvent& targetProcessEvent) + { + std::lock_guard lk{ mtx_ }; + if (auto pSegment = comms_.GetFrameDataSegment(targetProcessEvent.ProcessId)) { + auto& store = pSegment->GetStore(); + if (!store.bookkeeping.staticInitComplete && store.bookkeeping.isPlayback) { + store.bookkeeping.staticInitComplete = true; + store.statics.applicationName = + util::str::ToNarrow(targetProcessEvent.ImageFileName).c_str(); + } + } + } std::vector GetPids() const { std::lock_guard lk{ mtx_ }; diff --git a/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp b/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp index 2a1a6230..03f16b2a 100644 --- a/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp +++ b/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp @@ -509,6 +509,7 @@ void MockPresentMonSession::UpdateProcesses( if (newProcess) { InitProcessInfo(processInfo, processEvent.ProcessId, NULL, processEvent.ImageFileName); + pBroadcaster->HandleTargetProcessEvent(processEvent); } } } diff --git a/IntelPresentMon/PresentMonService/acts/StartTracking.h b/IntelPresentMon/PresentMonService/acts/StartTracking.h index 70940dc8..082aae64 100644 --- a/IntelPresentMon/PresentMonService/acts/StartTracking.h +++ b/IntelPresentMon/PresentMonService/acts/StartTracking.h @@ -18,9 +18,10 @@ namespace pmon::svc::acts struct Params { uint32_t targetPid; + bool isPlayback = false; template void serialize(A& ar) { - ar(targetPid); + ar(targetPid, isPlayback); } }; struct Response @@ -39,7 +40,7 @@ namespace pmon::svc::acts std::string nsmFileName; // TODO: replace PresentMon container system and directly return the segment // from a single "StartStreaming" replacement call - auto pSegment = ctx.pPmon->GetBroadcaster().RegisterTarget(in.targetPid); + auto pSegment = ctx.pPmon->GetBroadcaster().RegisterTarget(in.targetPid, in.isPlayback); if (auto sta = ctx.pPmon->StartStreaming(stx.remotePid, in.targetPid, nsmFileName); sta != PM_STATUS_SUCCESS) { pmlog_error("Start stream failed").code(sta); throw util::Except(sta); From 48ba2cf470504187ee2d2d612b83738528411500 Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 16 Dec 2025 17:57:32 +0900 Subject: [PATCH 057/205] backpressure --- .../Interprocess/source/DataStores.h | 4 +- .../Interprocess/source/Interprocess.cpp | 9 ++- .../Interprocess/source/Interprocess.h | 4 +- .../Interprocess/source/OwnedDataSegment.h | 13 +-- IntelPresentMon/Interprocess/source/ShmRing.h | 38 +++++++-- .../InterimBroadcasterTests.cpp | 81 ++++++++++++++++++- .../PresentMonService/FrameBroadcaster.h | 7 +- .../PresentMonService/acts/StartTracking.h | 6 +- 8 files changed, 131 insertions(+), 31 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/DataStores.h b/IntelPresentMon/Interprocess/source/DataStores.h index aff39c10..0de6c861 100644 --- a/IntelPresentMon/Interprocess/source/DataStores.h +++ b/IntelPresentMon/Interprocess/source/DataStores.h @@ -17,9 +17,9 @@ namespace pmon::ipc struct FrameDataStore { static constexpr size_t virtualSegmentSize = 50'000'000; - FrameDataStore(ShmSegmentManager& segMan, size_t cap) + FrameDataStore(ShmSegmentManager& segMan, size_t cap, bool backpressured) : - frameData{ cap, segMan.get_allocator() }, + frameData{ cap, segMan.get_allocator(), backpressured }, statics{ .applicationName{ segMan.get_allocator() } } {} struct Statics diff --git a/IntelPresentMon/Interprocess/source/Interprocess.cpp b/IntelPresentMon/Interprocess/source/Interprocess.cpp index b45d7f82..b585dfbb 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.cpp +++ b/IntelPresentMon/Interprocess/source/Interprocess.cpp @@ -28,8 +28,8 @@ namespace pmon::ipc class CommsBase_ { protected: - static constexpr size_t frameRingSize_ = 5'000; - static constexpr size_t telemetryRingSize_ = 5'000; + static constexpr size_t frameRingSize_ = 1'000; + static constexpr size_t telemetryRingSize_ = 1'000; static constexpr size_t introShmSize_ = 0x10'0000; static constexpr const char* introspectionRootName_ = "in-root"; static constexpr const char* introspectionMutexName_ = "in-mtx"; @@ -136,7 +136,7 @@ namespace pmon::ipc } // data store access std::shared_ptr> - CreateOrGetFrameDataSegment(uint32_t pid) override + CreateOrGetFrameDataSegment(uint32_t pid, bool backpressured) override { // resolve out existing or new weak ptr, try and lock auto& pWeak = frameShmWeaks_[pid]; @@ -147,7 +147,8 @@ namespace pmon::ipc pFrameData = std::make_shared>( namer_.MakeFrameName(pid), static_cast(Permissions_{}), - frameRingSize_ + frameRingSize_, + backpressured ); // store a weak reference pWeak = pFrameData; diff --git a/IntelPresentMon/Interprocess/source/Interprocess.h b/IntelPresentMon/Interprocess/source/Interprocess.h index 9c787ef6..ced0508c 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.h +++ b/IntelPresentMon/Interprocess/source/Interprocess.h @@ -26,7 +26,7 @@ namespace pmon::ipc virtual const ShmNamer& GetNamer() const = 0; // data store access - virtual std::shared_ptr> CreateOrGetFrameDataSegment(uint32_t pid) = 0; + virtual std::shared_ptr> CreateOrGetFrameDataSegment(uint32_t pid, bool backpressured) = 0; virtual std::shared_ptr> GetFrameDataSegment(uint32_t pid) = 0; virtual std::vector GetFramePids() const = 0; virtual GpuDataStore& GetGpuDataStore(uint32_t deviceId) = 0; @@ -40,6 +40,8 @@ namespace pmon::ipc virtual const PM_INTROSPECTION_ROOT* GetIntrospectionRoot(uint32_t timeoutMs = 2000) = 0; // data store access + // not const because of the backpressure case + // TODO: consider more separation of backpressure and broadcast cases virtual const FrameDataStore& GetFrameDataStore(uint32_t pid) const = 0; virtual const GpuDataStore& GetGpuDataStore(uint32_t deviceId) const = 0; virtual const SystemDataStore& GetSystemDataStore() const = 0; diff --git a/IntelPresentMon/Interprocess/source/OwnedDataSegment.h b/IntelPresentMon/Interprocess/source/OwnedDataSegment.h index b338e032..560e7c6d 100644 --- a/IntelPresentMon/Interprocess/source/OwnedDataSegment.h +++ b/IntelPresentMon/Interprocess/source/OwnedDataSegment.h @@ -39,28 +39,17 @@ namespace pmon::ipc private: static constexpr const char* name_ = "seg-dat"; - // Helper to enforce a single size_t-like argument for FrameDataStore - template - static size_t GetFrameCapacity_(Arg&& arg) - { - static_assert(std::is_convertible_v, - "FrameDataStore capacity must be convertible to size_t"); - return static_cast(std::forward(arg)); - } - // Factory that constructs the store in shared memory with the right allocator template ShmUniquePtr MakeStore_(StoreArgs&&... storeArgs) { // FrameDataStore: expects (ShmAllocator&, size_t cap) if constexpr (std::is_same_v) { - static_assert(sizeof...(StoreArgs) == 1, - "OwnedStreamedSegment requires a single ring capacity argument"); return ShmMakeNamedUnique( name_, shm_.get_segment_manager(), *shm_.get_segment_manager(), - GetFrameCapacity_(std::forward(storeArgs)...) + std::forward(storeArgs)... ); } // Telemetry stores: expect TelemetryMap::AllocatorType& only diff --git a/IntelPresentMon/Interprocess/source/ShmRing.h b/IntelPresentMon/Interprocess/source/ShmRing.h index 22d1a384..57e2e5ce 100644 --- a/IntelPresentMon/Interprocess/source/ShmRing.h +++ b/IntelPresentMon/Interprocess/source/ShmRing.h @@ -2,16 +2,22 @@ #include "SharedMemoryTypes.h" #include "../../CommonUtilities/Exception.h" #include "../../CommonUtilities/log/Log.h" +#include +#include +#include namespace pmon::ipc { + using namespace std::literals; + // shared memory ring buffer for broadcast template class ShmRing { public: - ShmRing(size_t capacity, ShmVector::allocator_type alloc) + ShmRing(size_t capacity, ShmVector::allocator_type alloc, bool backpressured = false) : + backpressured_{ backpressured }, data_{ capacity, alloc } { if (capacity < ReadBufferSize * 2) { @@ -21,14 +27,16 @@ namespace pmon::ipc ShmRing(const ShmRing&) = delete; - ShmRing & operator=(const ShmRing&) = delete; + ShmRing& operator=(const ShmRing&) = delete; // we need to enable move for use inside vectors // not enabled by default because of the atomic member ShmRing(ShmRing&& other) : + backpressured_{ other.backpressured_ }, data_{ std::move(other.data_) }, nextWriteSerial_{ other.nextWriteSerial_.load() } - {} + { + } ShmRing& operator=(ShmRing&& other) { if (this != &other) { @@ -40,10 +48,22 @@ namespace pmon::ipc ~ShmRing() = default; - void Push(const T& val) + bool Push(const T& val, std::optional timeoutMs = {}) { + using clock = std::chrono::high_resolution_clock; + if (backpressured_) { + const auto start = timeoutMs ? clock::now() : decltype(clock::now()){}; + while (nextWriteSerial_ >= nextReadSerial_ + data_.size() - ReadBufferSize) { + // bail with false to signal timeout without writing the pushed value + if (timeoutMs && (clock::now() - start >= *timeoutMs * 1ms)) { + return false; + } + std::this_thread::sleep_for(10ms); + } + } data_[IndexFromSerial_(nextWriteSerial_)] = val; nextWriteSerial_++; + return true; } const T& At(size_t serial) const { @@ -72,6 +92,12 @@ namespace pmon::ipc return { nextWriteSerial_ - data_.size() + ReadBufferSize, nextWriteSerial_ }; } } + void MarkNextRead(size_t serial) const + { + if (serial > nextReadSerial_) { + nextReadSerial_ = serial; + } + } bool Empty() const { return nextWriteSerial_ == 0; @@ -83,7 +109,9 @@ namespace pmon::ipc return serial % data_.size(); } // data + const bool backpressured_; std::atomic nextWriteSerial_ = 0; + mutable std::atomic nextReadSerial_ = 0; ShmVector data_; }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 1d554e66..487506d2 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -568,8 +568,7 @@ namespace InterimBroadcasterTests } }; - - TEST_CLASS(FrameStorePlaybackTests) + TEST_CLASS(FrameStorePacedPlaybackTests) { TestFixture fixture_; public: @@ -641,4 +640,82 @@ namespace InterimBroadcasterTests Assert::IsTrue(range2.second - range2.first < range3.second - range3.first); } }; + + TEST_CLASS(FrameStoreBackpressuredPlaybackTests) + { + TestFixture fixture_; + public: + TEST_METHOD_INITIALIZE(Setup) + { + fixture_.Setup({ + "--etl-test-file"s, R"(..\..\Tests\AuxData\Data\P00HeaWin2080.etl)", + }); + } + TEST_METHOD_CLEANUP(Cleanup) + { + fixture_.Cleanup(); + } + // static store + TEST_METHOD(StaticData) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + + // track known target + const uint32_t pid = 12820; + client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pid, .isPlayback = true }); + + // open the store + pComms->OpenFrameDataStore(pid); + + // wait for population of frame data-initialized statics + std::this_thread::sleep_for(500ms); + + // verify static data + auto& store = pComms->GetFrameDataStore(pid); + Assert::AreEqual(pid, store.statics.processId); + const std::string staticAppName = store.statics.applicationName.c_str(); + Assert::AreEqual("Heaven.exe"s, staticAppName); + } + // make sure we get frames over time + TEST_METHOD(ReadFrames) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + + // set up a fast flush + client.DispatchSync(svc::acts::SetEtwFlushPeriod::Params{ .etwFlushPeriodMs = 8 }); + // make sure the flush period propagates to the flusher thread + std::this_thread::sleep_for(1ms); + // we know the pid of interest in this etl file, track it + const uint32_t pid = 12820; + client.DispatchSync(svc::acts::StartTracking::Params{ + .targetPid = pid, .isPlayback = true, .isBackpressured = true }); + + // open the store + pComms->OpenFrameDataStore(pid); + auto& frames = pComms->GetFrameDataStore(pid).frameData; + + // sleep here to let the etw system warm up, and frames propagate + std::this_thread::sleep_for(300ms); + + // verify that backpressure works correctly to ensure no frames are lost + const auto range1 = frames.GetSerialRange(); + frames.MarkNextRead(range1.second); + Logger::WriteMessage(std::format("range [{},{})\n", range1.first, range1.second).c_str()); + std::this_thread::sleep_for(300ms); + const auto range2 = frames.GetSerialRange(); + frames.MarkNextRead(range2.second); + Logger::WriteMessage(std::format("range [{},{})\n", range2.first, range2.second).c_str()); + std::this_thread::sleep_for(500ms); + const auto range3 = frames.GetSerialRange(); + frames.MarkNextRead(range3.second); + Logger::WriteMessage(std::format("range [{},{})\n", range3.first, range3.second).c_str()); + + Assert::AreEqual(0ull, range1.first); + Assert::IsTrue(range2.first <= range1.second); + Assert::IsTrue(range3.first <= range2.second); + Assert::AreEqual(1905ull, range3.second); + } + }; } diff --git a/IntelPresentMon/PresentMonService/FrameBroadcaster.h b/IntelPresentMon/PresentMonService/FrameBroadcaster.h index d09234c9..34df2163 100644 --- a/IntelPresentMon/PresentMonService/FrameBroadcaster.h +++ b/IntelPresentMon/PresentMonService/FrameBroadcaster.h @@ -11,6 +11,7 @@ namespace pmon::svc namespace vi = std::views; namespace rn = std::ranges; using ipc::FrameData; + using namespace std::literals; namespace { @@ -78,10 +79,10 @@ namespace pmon::svc public: using Segment = ipc::OwnedDataSegment; FrameBroadcaster(ipc::ServiceComms& comms) : comms_{ comms } {} - std::shared_ptr RegisterTarget(uint32_t pid, bool isPlayback) + std::shared_ptr RegisterTarget(uint32_t pid, bool isPlayback, bool isBackpressured) { std::lock_guard lk{ mtx_ }; - auto pSegment = comms_.CreateOrGetFrameDataSegment(pid); + auto pSegment = comms_.CreateOrGetFrameDataSegment(pid, isBackpressured); auto& store = pSegment->GetStore(); // just overwrite these every time Register is called, it will always be the same store.statics.processId = pid; @@ -103,7 +104,7 @@ namespace pmon::svc } return pSegment; } - void Broadcast(const PresentEvent& present) + void Broadcast(const PresentEvent& present, std::optional timeoutMs = {}) { std::lock_guard lk{ mtx_ }; if (auto pSegment = comms_.GetFrameDataSegment(present.ProcessId)) { diff --git a/IntelPresentMon/PresentMonService/acts/StartTracking.h b/IntelPresentMon/PresentMonService/acts/StartTracking.h index 082aae64..ce10a3ad 100644 --- a/IntelPresentMon/PresentMonService/acts/StartTracking.h +++ b/IntelPresentMon/PresentMonService/acts/StartTracking.h @@ -19,9 +19,10 @@ namespace pmon::svc::acts { uint32_t targetPid; bool isPlayback = false; + bool isBackpressured = false; template void serialize(A& ar) { - ar(targetPid, isPlayback); + ar(targetPid, isPlayback, isBackpressured); } }; struct Response @@ -40,7 +41,8 @@ namespace pmon::svc::acts std::string nsmFileName; // TODO: replace PresentMon container system and directly return the segment // from a single "StartStreaming" replacement call - auto pSegment = ctx.pPmon->GetBroadcaster().RegisterTarget(in.targetPid, in.isPlayback); + auto pSegment = ctx.pPmon->GetBroadcaster().RegisterTarget( + in.targetPid, in.isPlayback, in.isBackpressured); if (auto sta = ctx.pPmon->StartStreaming(stx.remotePid, in.targetPid, nsmFileName); sta != PM_STATUS_SUCCESS) { pmlog_error("Start stream failed").code(sta); throw util::Except(sta); From 9375eb609dc5bb126f7ca9ff36a9efe54ea05fc8 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Tue, 16 Dec 2025 14:43:40 -0800 Subject: [PATCH 058/205] Init changes and flip delay metric update --- .../CommonUtilities/mc/MetricsCalculator.cpp | 2 +- .../CommonUtilities/mc/MetricsTypes.cpp | 47 +++++++++++++++ .../CommonUtilities/mc/MetricsTypes.h | 57 ++++++++++--------- 3 files changed, 77 insertions(+), 29 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index cb55e368..52e63fdf 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -74,7 +74,7 @@ namespace pmon::util::metrics out.msFlipDelay = qpc.DurationMilliSeconds(present.getFlipDelay()); } else { - out.msFlipDelay = 0.0; + out.msFlipDelay = std::nullopt; } } diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp index 78db837b..2d00114c 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp @@ -107,4 +107,51 @@ namespace pmon::util::metrics { return frame; } + + FrameData FrameData::CopyFrameData(const PresentEvent& p) { + FrameData frame{}; + + frame.presentStartTime = p.PresentStartTime; + frame.readyTime = p.ReadyTime; + frame.timeInPresent = p.TimeInPresent; + frame.gpuStartTime = p.GPUStartTime; + frame.gpuDuration = p.GPUDuration; + frame.gpuVideoDuration = p.GPUVideoDuration; + + frame.appPropagatedPresentStartTime = p.AppPropagatedPresentStartTime; + frame.appPropagatedTimeInPresent = p.AppPropagatedTimeInPresent; + frame.appPropagatedGPUStartTime = p.AppPropagatedGPUStartTime; + frame.appPropagatedReadyTime = p.AppPropagatedReadyTime; + frame.appPropagatedGPUDuration = p.AppPropagatedGPUDuration; + frame.appPropagatedGPUVideoDuration = p.AppPropagatedGPUVideoDuration; + + frame.appSleepStartTime = p.AppSleepStartTime; + frame.appSleepEndTime = p.AppSleepEndTime; + frame.appSimStartTime = p.AppSimStartTime; + frame.appSleepEndTime = p.AppSleepEndTime; + frame.appRenderSubmitStartTime = p.AppRenderSubmitStartTime; + frame.appRenderSubmitEndTime = p.AppRenderSubmitEndTime; + frame.appPresentStartTime = p.AppPresentStartTime; + frame.appPresentEndTime = p.AppPresentEndTime; + frame.appInputSample = p.AppInputSample; + + frame.inputTime = p.InputTime; + frame.mouseClickTime = p.MouseClickTime; + + frame.pclSimStartTime = p.PclSimStartTime; + frame.pclInputPingTime = p.PclInputPingTime; + frame.flipDelay = p.FlipDelay; + frame.flipToken = p.FlipToken; + + frame.displayed = p.Displayed; + + frame.finalState = p.FinalState; + frame.swapChainAddress = p.SwapChainAddress; + frame.frameId = p.FrameId; + frame.processId = p.ProcessId; + frame.threadId = p.ThreadId; + frame.appFrameId = p.AppFrameId; + + return frame; + } } \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h index 8d2805d4..eda31d39 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h @@ -128,55 +128,56 @@ namespace pmon::util::metrics { // Factory Methods static FrameData CopyFrameData(const PmNsmPresentEvent& p); static FrameData CopyFrameData(const std::shared_ptr& p); + static FrameData CopyFrameData(const PresentEvent& p); }; struct FrameMetrics { // Core Timing (always computed) - uint64_t timeInSeconds; // QPC timestamp - double msBetweenPresents; - double msInPresentApi; - double msUntilRenderComplete; + uint64_t timeInSeconds = 0; + double msBetweenPresents = 0; + double msInPresentApi = 0; + double msUntilRenderComplete = 0; // Display Metrics (displayed frames only) - double msDisplayLatency; - double msDisplayedTime; - double msUntilDisplayed; - double msBetweenDisplayChange; - uint64_t screenTimeQpc; + double msDisplayLatency = 0; + double msDisplayedTime = 0; + double msUntilDisplayed = 0; + double msBetweenDisplayChange = 0; + uint64_t screenTimeQpc = 0; std::optional msReadyTimeToDisplayLatency; // CPU Metrics (app frames only) - uint64_t cpuStartQpc; - double msCPUBusy; - double msCPUWait; + uint64_t cpuStartQpc = 0; + double msCPUBusy = 0; + double msCPUWait = 0; // GPU Metrics (app frames only) - double msGPULatency; - double msGPUBusy; - double msVideoBusy; - double msGPUWait; + double msGPULatency = 0; + double msGPUBusy = 0; + double msVideoBusy = 0; + double msGPUWait = 0; // Input Latency (optional, app+displayed only) - std::optional msClickToPhotonLatency; - std::optional msAllInputPhotonLatency; + std::optional msClickToPhotonLatency = {}; + std::optional msAllInputPhotonLatency = {}; std::optional msInstrumentedInputTime; - std::optional msPcLatency; // Animation (optional, app+displayed only) - std::optional msAnimationError; - std::optional msAnimationTime; + std::optional msAnimationError = {}; + std::optional msAnimationTime = {}; // Instrumented Metrics (optional) - std::optional msInstrumentedLatency; - std::optional msInstrumentedRenderLatency; - std::optional msInstrumentedSleep; - std::optional msInstrumentedGpuLatency; - std::optional msBetweenSimStarts; + std::optional msInstrumentedLatency = {}; + std::optional msInstrumentedRenderLatency = {}; + std::optional msInstrumentedSleep = {}; + std::optional msInstrumentedGpuLatency = {}; + std::optional msPcLatency = {}; + std::optional msBetweenSimStarts = {}; // PCLatency (optional) - std::optional msFlipDelay; // NVIDIA + std::optional msFlipDelay = {}; // NVIDIA // Frame Classification - FrameType frameType; + FrameType frameType = {}; }; } \ No newline at end of file From 084d643bef30f87258827c527eacb673a3a77a70 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 17 Dec 2025 08:14:18 +0900 Subject: [PATCH 059/205] backpressure and timeout change, add legacy streamer test --- .../InterimBroadcasterTests.cpp | 58 ++++++++++++++++++- .../PresentMonService/CliOptions.h | 1 + .../MockPresentMonSession.cpp | 6 +- 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 487506d2..26547672 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -12,6 +12,8 @@ #include "../PresentMonMiddleware/ActionClient.h" #include "../Interprocess/source/Interprocess.h" #include "../PresentMonAPIWrapperCommon/EnumMap.h" +#include "../PresentMonAPIWrapper/PresentMonAPIWrapper.h" +#include "../PresentMonAPIWrapper/FixedQuery.h" #include "../PresentMonService/AllActions.h" #include @@ -648,7 +650,8 @@ namespace InterimBroadcasterTests TEST_METHOD_INITIALIZE(Setup) { fixture_.Setup({ - "--etl-test-file"s, R"(..\..\Tests\AuxData\Data\P00HeaWin2080.etl)", + "--etl-test-file"s, R"(..\..\Tests\AuxData\Data\P00HeaWin2080.etl)"s, + "--disable-legacy-backpressure"s }); } TEST_METHOD_CLEANUP(Cleanup) @@ -718,4 +721,57 @@ namespace InterimBroadcasterTests Assert::AreEqual(1905ull, range3.second); } }; + + TEST_CLASS(LegacyBackpressuredPlaybackTests) + { + TestFixture fixture_; + public: + TEST_METHOD_INITIALIZE(Setup) + { + fixture_.Setup({ + "--etl-test-file"s, R"(..\..\Tests\AuxData\Data\P00HeaWin2080.etl)"s, + }); + } + TEST_METHOD_CLEANUP(Cleanup) + { + fixture_.Cleanup(); + } + // make sure we get frames over time + TEST_METHOD(ReadFrames) + { + pmapi::Session session{ fixture_.GetCommonArgs().ctrlPipe }; + + // set up a fast flush + session.SetEtwFlushPeriod(8); + // make sure the flush period propagates to the flusher thread + std::this_thread::sleep_for(1ms); + + // setup query + PM_BEGIN_FIXED_FRAME_QUERY(FQ) + pmapi::FixedQueryElement frameTime{ this, PM_METRIC_DISPLAYED_FRAME_TIME }; + PM_END_FIXED_QUERY query{ session, 1'000 }; + + // we know the pid of interest in this etl file, track it + const uint32_t pid = 12820; + auto tracker = session.TrackProcess(pid); + + // sleep here to let the etw system warm up, and frames propagate + std::this_thread::sleep_for(300ms); + + // verify that backpressure works correctly to ensure no frames are lost + query.Consume(tracker); + const auto count1 = query.PeekBlobContainer().GetNumBlobsPopulated(); + Logger::WriteMessage(std::format("count [{}]\n", count1).c_str()); + std::this_thread::sleep_for(300ms); + query.Consume(tracker); + const auto count2 = query.PeekBlobContainer().GetNumBlobsPopulated(); + Logger::WriteMessage(std::format("count [{}]\n", count2).c_str()); + std::this_thread::sleep_for(500ms); + query.Consume(tracker); + const auto count3 = query.PeekBlobContainer().GetNumBlobsPopulated(); + Logger::WriteMessage(std::format("count [{}]\n", count3).c_str()); + + Assert::AreEqual(1903u, count1 + count2 + count3); + } + }; } diff --git a/IntelPresentMon/PresentMonService/CliOptions.h b/IntelPresentMon/PresentMonService/CliOptions.h index 8df0da7a..ed8ce4a1 100644 --- a/IntelPresentMon/PresentMonService/CliOptions.h +++ b/IntelPresentMon/PresentMonService/CliOptions.h @@ -39,6 +39,7 @@ namespace clio private: Group gt_{ this, "Testing", "Automated testing features" }; public: Flag enableTestControl{ this, "--enable-test-control", "Enable test control over stdio" }; + Flag disableLegacyBackpressure{ this, "--disable-legacy-backpressure", "Disable backpressure to enable testing of new IPC" }; static constexpr const char* description = "Intel PresentMon service for frame and system performance measurement"; static constexpr const char* name = "PresentMonService.exe"; diff --git a/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp b/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp index 03f16b2a..1493ebd8 100644 --- a/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp +++ b/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp @@ -34,7 +34,8 @@ PM_STATUS MockPresentMonSession::StartStreaming(uint32_t client_process_id, // TODO: hook up all cli options PM_STATUS status = streamer_.StartStreaming(client_process_id, - target_process_id, nsmFileName, true, opt.pacePlayback, opt.pacePlayback, !opt.pacePlayback, true); + target_process_id, nsmFileName, true, opt.pacePlayback, opt.pacePlayback, + !opt.pacePlayback && !opt.disableLegacyBackpressure, true); if (status != PM_STATUS::PM_STATUS_SUCCESS) { return status; } @@ -324,7 +325,8 @@ void MockPresentMonSession::AddPresents( processInfo->mModuleName, gpu_telemetry_cap_bits, cpu_telemetry_cap_bits); - pBroadcaster->Broadcast(*presentEvent); + // timeout set for 1000 ms + pBroadcaster->Broadcast(*presentEvent, 1000); chain->mLastPresentQPC = presentEvent->PresentStartTime; if (presentEvent->FinalState == PresentResult::Presented) { From 8be48d575f40727e53dfcf9c73c373754baecc28 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 17 Dec 2025 13:17:17 +0900 Subject: [PATCH 060/205] write test results (frame timestamps) --- .../InterimBroadcasterTests.cpp | 20 +++++++++++++++---- .../PresentMonAPI2Tests/ModuleInit.cpp | 2 ++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 26547672..fb08d964 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -6,6 +6,7 @@ #include "TestProcess.h" #include #include +#include #include "Folders.h" #include "JobManager.h" @@ -748,8 +749,9 @@ namespace InterimBroadcasterTests // setup query PM_BEGIN_FIXED_FRAME_QUERY(FQ) - pmapi::FixedQueryElement frameTime{ this, PM_METRIC_DISPLAYED_FRAME_TIME }; + pmapi::FixedQueryElement timestamp{ this, PM_METRIC_CPU_START_QPC }; PM_END_FIXED_QUERY query{ session, 1'000 }; + std::vector frames; // we know the pid of interest in this etl file, track it const uint32_t pid = 12820; @@ -759,18 +761,28 @@ namespace InterimBroadcasterTests std::this_thread::sleep_for(300ms); // verify that backpressure works correctly to ensure no frames are lost - query.Consume(tracker); + query.ForEachConsume(tracker, [&] { frames.push_back(query.timestamp); }); const auto count1 = query.PeekBlobContainer().GetNumBlobsPopulated(); Logger::WriteMessage(std::format("count [{}]\n", count1).c_str()); std::this_thread::sleep_for(300ms); - query.Consume(tracker); + query.ForEachConsume(tracker, [&] { frames.push_back(query.timestamp); }); const auto count2 = query.PeekBlobContainer().GetNumBlobsPopulated(); Logger::WriteMessage(std::format("count [{}]\n", count2).c_str()); std::this_thread::sleep_for(500ms); - query.Consume(tracker); + query.ForEachConsume(tracker, [&] { frames.push_back(query.timestamp); }); const auto count3 = query.PeekBlobContainer().GetNumBlobsPopulated(); Logger::WriteMessage(std::format("count [{}]\n", count3).c_str()); + // output timestamp of each frame + const auto outpath = fs::path{ outFolder_ } / + std::format("legacy-frames-{:%Y%m%d-%H%M%S}.csv", std::chrono::system_clock::now()); + Logger::WriteMessage(std::format("Writing output to: {}\n", + fs::absolute(outpath).string()).c_str()); + std::ofstream frameFile{ outpath }; + for (auto& f : frames) { + frameFile << f << std::endl; + } + Assert::AreEqual(1903u, count1 + count2 + count3); } }; diff --git a/IntelPresentMon/PresentMonAPI2Tests/ModuleInit.cpp b/IntelPresentMon/PresentMonAPI2Tests/ModuleInit.cpp index 2aa0045f..0cbf1e3d 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/ModuleInit.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/ModuleInit.cpp @@ -34,4 +34,6 @@ TEST_MODULE_INITIALIZE(Api2TestModuleInit) WipeAndRecreate(EtlLoggerTests::outFolder_); WipeAndRecreate(PacedPolling::logFolder_); WipeAndRecreate(PacedPolling::outFolder_); + WipeAndRecreate(InterimBroadcasterTests::logFolder_); + WipeAndRecreate(InterimBroadcasterTests::outFolder_); } \ No newline at end of file From de3226554052870ca7b92c0d5e2ecfd5593d2495 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 17 Dec 2025 16:19:54 +0900 Subject: [PATCH 061/205] proper start qpc calculation --- .../InterimBroadcasterTests.cpp | 48 +++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index fb08d964..98ceb153 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -691,6 +691,7 @@ namespace InterimBroadcasterTests client.DispatchSync(svc::acts::SetEtwFlushPeriod::Params{ .etwFlushPeriodMs = 8 }); // make sure the flush period propagates to the flusher thread std::this_thread::sleep_for(1ms); + // we know the pid of interest in this etl file, track it const uint32_t pid = 12820; client.DispatchSync(svc::acts::StartTracking::Params{ @@ -698,23 +699,54 @@ namespace InterimBroadcasterTests // open the store pComms->OpenFrameDataStore(pid); - auto& frames = pComms->GetFrameDataStore(pid).frameData; + auto& ring = pComms->GetFrameDataStore(pid).frameData; // sleep here to let the etw system warm up, and frames propagate std::this_thread::sleep_for(300ms); - // verify that backpressure works correctly to ensure no frames are lost - const auto range1 = frames.GetSerialRange(); - frames.MarkNextRead(range1.second); + std::vector frames; + uint64_t lastProcessed = 0; + + const auto range1 = ring.GetSerialRange(); + ring.MarkNextRead(range1.second); Logger::WriteMessage(std::format("range [{},{})\n", range1.first, range1.second).c_str()); + for (uint64_t s = (std::max)(lastProcessed, range1.first); s < range1.second; ++s) { + auto& p = ring.At(s); + frames.push_back(p.presentStartTime + p.timeInPresent); + } + lastProcessed = range1.second; + std::this_thread::sleep_for(300ms); - const auto range2 = frames.GetSerialRange(); - frames.MarkNextRead(range2.second); + + const auto range2 = ring.GetSerialRange(); + ring.MarkNextRead(range2.second); Logger::WriteMessage(std::format("range [{},{})\n", range2.first, range2.second).c_str()); + for (uint64_t s = (std::max)(lastProcessed, range2.first); s < range2.second; ++s) { + auto& p = ring.At(s); + frames.push_back(p.presentStartTime + p.timeInPresent); + } + lastProcessed = range2.second; + std::this_thread::sleep_for(500ms); - const auto range3 = frames.GetSerialRange(); - frames.MarkNextRead(range3.second); + + const auto range3 = ring.GetSerialRange(); + ring.MarkNextRead(range3.second); Logger::WriteMessage(std::format("range [{},{})\n", range3.first, range3.second).c_str()); + for (uint64_t s = (std::max)(lastProcessed, range3.first); s < range3.second; ++s) { + auto& p = ring.At(s); + frames.push_back(p.presentStartTime + p.timeInPresent); + } + lastProcessed = range3.second; + + // output timestamp of each frame + const auto outpath = fs::path{ outFolder_ } / + std::format("broadcaster-frames-{:%Y%m%d-%H%M%S}.csv", std::chrono::system_clock::now()); + Logger::WriteMessage(std::format("Writing output to: {}\n", + fs::absolute(outpath).string()).c_str()); + std::ofstream frameFile{ outpath }; + for (auto f : frames) { + frameFile << f << "\n"; + } Assert::AreEqual(0ull, range1.first); Assert::IsTrue(range2.first <= range1.second); From 9dfdefd298a90642e51b4be620dca27baf9e132f Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 17 Dec 2025 17:20:24 +0900 Subject: [PATCH 062/205] more frame output --- .../InterimBroadcasterTests.cpp | 77 ++++++++++++------- 1 file changed, 49 insertions(+), 28 deletions(-) diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 98ceb153..3ab75f2d 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -704,48 +704,50 @@ namespace InterimBroadcasterTests // sleep here to let the etw system warm up, and frames propagate std::this_thread::sleep_for(300ms); - std::vector frames; + struct Row { uint64_t timestamp; uint64_t timeInPresent; }; + std::vector frames; uint64_t lastProcessed = 0; + const auto appendRange = [&](std::pair range) { + for (uint64_t s = (std::max)(lastProcessed, range.first); s < range.second; ++s) { + auto& p = ring.At(s); + frames.push_back(Row{ + .timestamp = p.presentStartTime + p.timeInPresent, + .timeInPresent = p.timeInPresent, + }); + } + lastProcessed = range.second; + }; + const auto range1 = ring.GetSerialRange(); ring.MarkNextRead(range1.second); Logger::WriteMessage(std::format("range [{},{})\n", range1.first, range1.second).c_str()); - for (uint64_t s = (std::max)(lastProcessed, range1.first); s < range1.second; ++s) { - auto& p = ring.At(s); - frames.push_back(p.presentStartTime + p.timeInPresent); - } - lastProcessed = range1.second; + appendRange(range1); std::this_thread::sleep_for(300ms); const auto range2 = ring.GetSerialRange(); ring.MarkNextRead(range2.second); Logger::WriteMessage(std::format("range [{},{})\n", range2.first, range2.second).c_str()); - for (uint64_t s = (std::max)(lastProcessed, range2.first); s < range2.second; ++s) { - auto& p = ring.At(s); - frames.push_back(p.presentStartTime + p.timeInPresent); - } - lastProcessed = range2.second; + appendRange(range2); std::this_thread::sleep_for(500ms); const auto range3 = ring.GetSerialRange(); ring.MarkNextRead(range3.second); Logger::WriteMessage(std::format("range [{},{})\n", range3.first, range3.second).c_str()); - for (uint64_t s = (std::max)(lastProcessed, range3.first); s < range3.second; ++s) { - auto& p = ring.At(s); - frames.push_back(p.presentStartTime + p.timeInPresent); - } - lastProcessed = range3.second; + appendRange(range3); // output timestamp of each frame const auto outpath = fs::path{ outFolder_ } / std::format("broadcaster-frames-{:%Y%m%d-%H%M%S}.csv", std::chrono::system_clock::now()); Logger::WriteMessage(std::format("Writing output to: {}\n", fs::absolute(outpath).string()).c_str()); + std::ofstream frameFile{ outpath }; - for (auto f : frames) { - frameFile << f << "\n"; + frameFile << "timestamp,timeInPresent\n"; + for (const auto& r : frames) { + frameFile << r.timestamp << ',' << r.timeInPresent << "\n"; } Assert::AreEqual(0ull, range1.first); @@ -753,6 +755,7 @@ namespace InterimBroadcasterTests Assert::IsTrue(range3.first <= range2.second); Assert::AreEqual(1905ull, range3.second); } + }; TEST_CLASS(LegacyBackpressuredPlaybackTests) @@ -763,7 +766,7 @@ namespace InterimBroadcasterTests { fixture_.Setup({ "--etl-test-file"s, R"(..\..\Tests\AuxData\Data\P00HeaWin2080.etl)"s, - }); + }); } TEST_METHOD_CLEANUP(Cleanup) { @@ -778,13 +781,16 @@ namespace InterimBroadcasterTests session.SetEtwFlushPeriod(8); // make sure the flush period propagates to the flusher thread std::this_thread::sleep_for(1ms); - + // setup query PM_BEGIN_FIXED_FRAME_QUERY(FQ) pmapi::FixedQueryElement timestamp{ this, PM_METRIC_CPU_START_QPC }; + pmapi::FixedQueryElement timeInPres{ this, PM_METRIC_IN_PRESENT_API }; PM_END_FIXED_QUERY query{ session, 1'000 }; - std::vector frames; - + + struct Row { uint64_t timestamp; double timeInPresent; }; + std::vector frames; + // we know the pid of interest in this etl file, track it const uint32_t pid = 12820; auto tracker = session.TrackProcess(pid); @@ -792,16 +798,29 @@ namespace InterimBroadcasterTests // sleep here to let the etw system warm up, and frames propagate std::this_thread::sleep_for(300ms); + const auto consume = [&] { + query.ForEachConsume(tracker, [&] { + frames.push_back(Row{ + .timestamp = query.timestamp, + .timeInPresent = query.timeInPres, + }); + }); + }; + // verify that backpressure works correctly to ensure no frames are lost - query.ForEachConsume(tracker, [&] { frames.push_back(query.timestamp); }); + consume(); const auto count1 = query.PeekBlobContainer().GetNumBlobsPopulated(); Logger::WriteMessage(std::format("count [{}]\n", count1).c_str()); + std::this_thread::sleep_for(300ms); - query.ForEachConsume(tracker, [&] { frames.push_back(query.timestamp); }); + + consume(); const auto count2 = query.PeekBlobContainer().GetNumBlobsPopulated(); Logger::WriteMessage(std::format("count [{}]\n", count2).c_str()); + std::this_thread::sleep_for(500ms); - query.ForEachConsume(tracker, [&] { frames.push_back(query.timestamp); }); + + consume(); const auto count3 = query.PeekBlobContainer().GetNumBlobsPopulated(); Logger::WriteMessage(std::format("count [{}]\n", count3).c_str()); @@ -809,10 +828,12 @@ namespace InterimBroadcasterTests const auto outpath = fs::path{ outFolder_ } / std::format("legacy-frames-{:%Y%m%d-%H%M%S}.csv", std::chrono::system_clock::now()); Logger::WriteMessage(std::format("Writing output to: {}\n", - fs::absolute(outpath).string()).c_str()); + fs::absolute(outpath).string()).c_str()); + std::ofstream frameFile{ outpath }; - for (auto& f : frames) { - frameFile << f << std::endl; + frameFile << "timestamp,timeInPresent\n"; + for (const auto& r : frames) { + frameFile << r.timestamp << ',' << r.timeInPresent << "\n"; } Assert::AreEqual(1903u, count1 + count2 + count3); From 82fe038ac4171b8503e9135a20ac3d7669cc98c1 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Wed, 17 Dec 2025 09:04:29 -0800 Subject: [PATCH 063/205] Initial updates to use a unified swap chain --- .../CommonUtilities/CommonUtilities.vcxproj | 2 + .../CommonUtilities.vcxproj.filters | 6 ++ .../CommonUtilities/mc/UnifiedSwapChain.cpp | 93 +++++++++++++++++++ .../CommonUtilities/mc/UnifiedSwapChain.h | 32 +++++++ PresentMon/OutputThread.cpp | 18 ++++ PresentMon/PresentMon.hpp | 8 +- 6 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp create mode 100644 IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj index aff34859..10b2ecfb 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj @@ -71,6 +71,7 @@ + @@ -156,6 +157,7 @@ + diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters index 3252e88c..17d10f4a 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters @@ -303,6 +303,9 @@ Header Files + + Header Files + @@ -488,6 +491,9 @@ Source Files + + Source Files + diff --git a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp new file mode 100644 index 00000000..6636dea4 --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp @@ -0,0 +1,93 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#include "MetricsTypes.h" +#include "UnifiedSwapChain.h" +#include "../PresentData/PresentMonTraceConsumer.hpp" + + +namespace pmon::util::metrics +{ + void UnifiedSwapChain::SanitizeDisplayedRepeats(FrameData& present) + { + // Port of OutputThread.cpp::ReportMetrics() Remove Repeated flips pre-pass, + // but applied to FrameData (so we dont mutate PresentEvent). + auto& d = present.displayed; + for (size_t i = 0; i + 1 < d.size(); ) { + const auto a = d[i].first; + const auto b = d[i + 1].first; + + const bool app_then_rep = (a == FrameType::Application && b == FrameType::Repeated); + const bool rep_then_app = (a == FrameType::Repeated && b == FrameType::Application); + + if (app_then_rep) { + d.erase(d.begin() + i + 1); + } + else if (rep_then_app) { + d.erase(d.begin() + i); + } + else { + ++i; + } + } + } + + void UnifiedSwapChain::Seed(FrameData present) + { + SanitizeDisplayedRepeats(present); + + // Mirror console baseline behavior: + // first present just seeds history (no pending pipeline). + core.pendingPresents.clear(); + core.UpdateAfterPresent(present); + } + + void UnifiedSwapChain::OnPresent(const QpcConverter& qpc, FrameData present) + { + SanitizeDisplayedRepeats(present); + + // If unseeded, seed immediately and return. + // (Must not require qpc for the seed path.) + if (!core.lastPresent.has_value()) { + core.pendingPresents.clear(); + core.UpdateAfterPresent(present); + return; + } + + const bool isDisplayed = + (present.getFinalState() == PresentResult::Presented) && + (present.getDisplayedCount() > 0); + + // Match the console pending until next displayed rule: + // - a displayed present blocks subsequent not-displayed presents until the next displayed arrives. + if (isDisplayed) { + // 1) Flush pending frames using this displayed present as nextDisplayed. + // (This finalizes the previously postponed last-display instance.) + if (!core.pendingPresents.empty()) { + FrameData nextDisplayed = present; // ComputeMetricsForPresent needs a non-const pointer + for (const auto& blocked : core.pendingPresents) { + (void)ComputeMetricsForPresent(qpc, blocked, &nextDisplayed, core); + } + } + + // 2) Process this present with nextDisplayed=nullptr: + // - applies state deltas for all display instances it can resolve + // - DOES NOT call UpdateAfterPresent() yet (by design) + (void)ComputeMetricsForPresent(qpc, present, nullptr, core); + + // 3) This present becomes the new waiting for next displayed + core.pendingPresents.clear(); + core.pendingPresents.push_back(present); + return; + } + + // Not displayed: + // - If nothing is waiting, process immediately (this path calls UpdateAfterPresent()). + // - Otherwise, queue behind the waiting displayed present. + if (core.pendingPresents.empty()) { + (void)ComputeMetricsForPresent(qpc, present, nullptr, core); + } + else { + core.pendingPresents.push_back(present); + } + } +} \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h new file mode 100644 index 00000000..bd831a17 --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h @@ -0,0 +1,32 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#pragma once + +#include +#include + +#include "SwapChainState.h" +#include "MetricsCalculator.h" // ComputeMetricsForPresent() + +namespace pmon::util::metrics +{ + // Phase 1: shared unified swapchain state machine (parallel to legacy UpdateChain()). + // + // - Owns sequencing (pending until next displayed) using FrameData snapshots. + // - Advances SwapChainCoreState by calling ComputeMetricsForPresent() and discarding results. + // - Does NOT do any CSV/console/middleware output (callers can ignore it). + struct UnifiedSwapChain + { + SwapChainCoreState core; + + // Seed without needing a QPC converter (needed for console GetPresentProcessInfo() early-return). + void Seed(FrameData present); + + // Feed every present into the unified swapchain (callers may ignore results). + void OnPresent(const QpcConverter& qpc, FrameData present); + + private: + // Matches the console pre-pass that removes Application<->Repeated flip pairs. + static void SanitizeDisplayedRepeats(FrameData& present); + }; +} diff --git a/PresentMon/OutputThread.cpp b/PresentMon/OutputThread.cpp index d1657238..be311795 100644 --- a/PresentMon/OutputThread.cpp +++ b/PresentMon/OutputThread.cpp @@ -926,6 +926,12 @@ static bool GetPresentProcessInfo( auto chain = &processInfo->mSwapChain[presentEvent->SwapChainAddress]; if (chain->mLastPresent == nullptr) { + // Step 1: seed unified swapchain too (GetPresentProcessInfo() early-returns before metrics). + { + using namespace pmon::util::metrics; + FrameData fd = FrameData::CopyFrameData(presentEvent); + chain->mUnifiedSwapChain.Seed(std::move(fd)); + } UpdateChain(chain, presentEvent); return true; } @@ -979,6 +985,10 @@ static void ProcessEvents( size_t processEventCount = processEvents->size(); bool checkProcessTime = processEventCount > 0; + const uint64_t qpcFrequency = static_cast(pmSession.mTimestampFrequency.QuadPart); + const uint64_t qpcStart = static_cast(pmSession.mStartTimestamp.QuadPart); + pmon::util::QpcConverter qpc(qpcFrequency, qpcStart); + // Iterate through the processEvents, handling process events and recording toggles along the // way. uint64_t presentTime = 0; @@ -1035,6 +1045,14 @@ static void ProcessEvents( continue; } + // Step 1: always advance unified swapchain (parallel state machine), regardless of recording/stats. + // Ignore output here; this step is only to keep unified state warm. + { + using namespace pmon::util::metrics; + FrameData fd = FrameData::CopyFrameData(presentEvent); + chain->mUnifiedSwapChain.OnPresent(qpc, std::move(fd)); + } + // If we are recording or presenting metrics to console then update the metrics and pending // presents. Otherwise, just update the latest present details in the chain. // diff --git a/PresentMon/PresentMon.hpp b/PresentMon/PresentMon.hpp index 1e386e63..fde71d4f 100644 --- a/PresentMon/PresentMon.hpp +++ b/PresentMon/PresentMon.hpp @@ -29,7 +29,7 @@ which is controlled from MainThread based on user input or timer. #include "../PresentData/PresentMonTraceConsumer.hpp" #include "../PresentData/PresentMonTraceSession.hpp" -#include "../IntelPresentMon/CommonUtilities/mc/SwapChainState.h" +#include "../IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h" #include #include @@ -228,6 +228,11 @@ struct SwapChainData { // Internal NVIDIA Metrics uint64_t mLastDisplayedFlipDelay = 0; + + // Unified metrics state for computing metrics + pmon::util::metrics::SwapChainCoreState metricsState; + // Unified swap chain for unfied metrics calculations + pmon::util::metrics::UnifiedSwapChain mUnifiedSwapChain; }; struct ProcessInfo { @@ -267,6 +272,7 @@ const char* PresentModeToString(PresentMode mode); const char* RuntimeToString(Runtime rt); void UpdateCsv(PMTraceSession const& pmSession, ProcessInfo* processInfo, PresentEvent const& p, FrameMetrics const& metrics); void UpdateCsv(PMTraceSession const& pmSession, ProcessInfo* processInfo, PresentEvent const& p, FrameMetrics1 const& metrics); +void UpdateCsv(PMTraceSession const& pmSession, ProcessInfo* processInfo, PresentEvent const& p, pmon::util::metrics::FrameMetrics const& metrics); // MainThread.cpp: void ExitMainThread(); From 6c1a41c92a9cd59560288cf9bde94e5e37b55ce1 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Wed, 17 Dec 2025 14:23:15 -0800 Subject: [PATCH 064/205] Additional incremental changes for unified swap chain --- .../CommonUtilities/mc/MetricsCalculator.cpp | 2 +- PresentMon/CsvOutput.cpp | 357 ++++++++++++++++++ PresentMon/OutputThread.cpp | 248 +++++++++++- PresentMon/PresentMon.hpp | 30 ++ 4 files changed, 625 insertions(+), 12 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 52e63fdf..38e304c5 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -282,7 +282,7 @@ namespace pmon::util::metrics if (isFirstProviderSimTime) { // Seed only: no animation time yet. UpdateAfterPresent will flip us // into AppProvider/PCL and latch firstAppSimStartTime. - out.msAnimationTime = std::nullopt; + out.msAnimationTime = 0.0; return; } diff --git a/PresentMon/CsvOutput.cpp b/PresentMon/CsvOutput.cpp index 1f7efe70..650f18ce 100644 --- a/PresentMon/CsvOutput.cpp +++ b/PresentMon/CsvOutput.cpp @@ -400,6 +400,141 @@ void WriteCsvHeader(FILE* fp) } } +template<> +void WriteCsvHeader(FILE* fp) +{ + auto const& args = GetCommandLineArgs(); + + fwprintf(fp, L"Application" + L",ProcessID" + L",SwapChainAddress" + L",PresentRuntime" + L",SyncInterval" + L",PresentFlags"); + if (args.mTrackDisplay) { + fwprintf(fp, L",AllowsTearing" + L",PresentMode"); + } + if (args.mTrackFrameType) { + fwprintf(fp, L",FrameType"); + } + if (args.mTrackHybridPresent) { + fwprintf(fp, L",HybridPresent"); + } + if (args.mUseV2Metrics == false) { + switch (args.mTimeUnit) { + case TimeUnit::MilliSeconds: fwprintf(fp, L",TimeInMs"); break; + case TimeUnit::QPC: fwprintf(fp, L",TimeInQPC"); break; + case TimeUnit::DateTime: fwprintf(fp, L",TimeInDateTime"); break; + default: fwprintf(fp, L",TimeInSeconds"); break; + } + + fwprintf(fp, L",MsBetweenSimulationStart" + L",MsBetweenPresents"); + + if (args.mTrackDisplay) { + fwprintf(fp, L",MsBetweenDisplayChange"); + } + + fwprintf(fp, L",MsInPresentAPI" + L",MsRenderPresentLatency"); + + if (args.mTrackDisplay) { + fwprintf(fp, L",MsUntilDisplayed"); + if (args.mTrackPcLatency) { + fwprintf(fp, L",MsPCLatency"); + } + } + } + + if (args.mUseV2Metrics) { + switch (args.mTimeUnit) { + case TimeUnit::MilliSeconds: fwprintf(fp, L",CPUStartTime"); break; + case TimeUnit::QPC: fwprintf(fp, L",CPUStartQPC"); break; + case TimeUnit::QPCMilliSeconds: fwprintf(fp, L",CPUStartQPCTime"); break; + case TimeUnit::DateTime: fwprintf(fp, L",CPUStartDateTime"); break; + default: fwprintf(fp, L",CPUStartTime"); break; + } + fwprintf(fp, L",FrameTime" + L",CPUBusy" + L",CPUWait"); + if (args.mTrackGPU) { + fwprintf(fp, L",GPULatency" + L",GPUTime" + L",GPUBusy" + L",GPUWait"); + } + if (args.mTrackGPUVideo) { + fwprintf(fp, L",VideoBusy"); + } + if (args.mTrackDisplay) { + fwprintf(fp, + L",DisplayLatency" + L",DisplayedTime" + L",AnimationError" + L",AnimationTime" + L",MsFlipDelay"); + } + if (args.mTrackInput) { + fwprintf(fp, L",AllInputToPhotonLatency"); + fwprintf(fp, L",ClickToPhotonLatency"); + } + if (args.mTrackAppTiming) { + fwprintf(fp, L",InstrumentedLatency"); + } + } + else { + switch (args.mTimeUnit) { + case TimeUnit::MilliSeconds: fwprintf(fp, L",CPUStartTimeInMs"); break; + case TimeUnit::QPC: fwprintf(fp, L",CPUStartQPC"); break; + case TimeUnit::QPCMilliSeconds: fwprintf(fp, L",CPUStartQPCTimeInMs"); break; + case TimeUnit::DateTime: fwprintf(fp, L",CPUStartDateTime"); break; + default: fwprintf(fp, L",CPUStartTimeInSeconds"); break; + } + fwprintf(fp, L",MsBetweenAppStart" + L",MsCPUBusy" + L",MsCPUWait"); + if (args.mTrackGPU) { + fwprintf(fp, L",MsGPULatency" + L",MsGPUTime" + L",MsGPUBusy" + L",MsGPUWait"); + } + if (args.mTrackGPUVideo) { + fwprintf(fp, L",MsVideoBusy"); + } + if (args.mTrackDisplay) { + fwprintf(fp, L",MsAnimationError" + L",AnimationTime" + L",MsFlipDelay"); + } + if (args.mTrackInput) { + fwprintf(fp, L",MsAllInputToPhotonLatency"); + fwprintf(fp, L",MsClickToPhotonLatency"); + } + if (args.mTrackAppTiming) { + fwprintf(fp, L",MsInstrumentedLatency"); + } + } + if (args.mWriteDisplayTime) { + fwprintf(fp, L",DisplayTimeAbs"); + } + if (args.mWriteFrameId) { + fwprintf(fp, L",FrameId"); + if (args.mTrackAppTiming) { + fwprintf(fp, L",AppFrameId"); + } + if (args.mTrackPcLatency) { + fwprintf(fp, L",PCLFrameId"); + } + } + fwprintf(fp, L"\n"); + + if (args.mCSVOutput == CSVOutput::Stdout) { + fflush(fp); + } +} + template<> void WriteCsvRow( FILE* fp, @@ -613,6 +748,223 @@ void WriteCsvRow( } } +template<> +void WriteCsvRow( + FILE* fp, + PMTraceSession const& pmSession, + ProcessInfo const& processInfo, + PresentEvent const& p, + pmon::util::metrics::FrameMetrics const& metrics) +{ + auto const& args = GetCommandLineArgs(); + + fwprintf(fp, L"%s,%d,0x%llX,%hs,%d,%d", processInfo.mModuleName.c_str(), + p.ProcessId, + p.SwapChainAddress, + RuntimeToString(p.Runtime), + p.SyncInterval, + p.PresentFlags); + if (args.mTrackDisplay) { + fwprintf(fp, L",%d,%hs", p.SupportsTearing, + PresentModeToString(p.PresentMode)); + } + if (args.mTrackFrameType) { + fwprintf(fp, L",%hs", FrameTypeToString(metrics.frameType)); + } + if (args.mTrackHybridPresent) { + fwprintf(fp, L",%d", p.IsHybridPresent); + } + + if (args.mUseV2Metrics == false) { + // Time in Seconds + switch (args.mTimeUnit) { + case TimeUnit::DateTime: { + SYSTEMTIME st = {}; + uint64_t ns = 0; + pmSession.TimestampToLocalSystemTime(metrics.timeInSeconds, &st, &ns); + fwprintf(fp, L",%u-%u-%u %u:%02u:%02u.%09llu", st.wYear, + st.wMonth, + st.wDay, + st.wHour, + st.wMinute, + st.wSecond, + ns); + } break; + case TimeUnit::MilliSeconds: + case TimeUnit::QPCMilliSeconds: + fwprintf(fp, L",%.4lf", pmSession.TimestampToMilliSeconds(metrics.timeInSeconds)); + break; + case TimeUnit::QPC: + fwprintf(fp, L",%llu", metrics.timeInSeconds); + break; + default: + fwprintf(fp, L",%.*lf", DBL_DIG - 1, 0.001 * pmSession.TimestampToMilliSeconds(metrics.timeInSeconds)); + break; + } + + // MsBetweenSimulationStart + if (metrics.msBetweenSimStarts.has_value()) { + fwprintf(fp, L",%.4lf", metrics.msBetweenSimStarts.value()); + } + else { + fwprintf(fp, L",NA"); + } + + // MsBetweenPresents + fwprintf(fp, L",%.*lf", DBL_DIG - 1, metrics.msBetweenPresents); + + // MsBetweenDisplayChange -> Transition from DisplayedTime + if (args.mTrackDisplay) { + if (metrics.msBetweenDisplayChange == 0.0) { + fwprintf(fp, L",NA"); + } + else { + fwprintf(fp, L",%.*lf", DBL_DIG - 1, metrics.msBetweenDisplayChange); + } + } + + // MsInPresentAPI + fwprintf(fp, L",%.*lf", DBL_DIG - 1, metrics.msInPresentApi); + + // MsRenderPresentLatency + fwprintf(fp, L",%.*lf", DBL_DIG - 1, metrics.msUntilRenderComplete); + + // MsUntilDisplayed + if (args.mTrackDisplay) { + if (metrics.msUntilDisplayed == 0.0) { + fwprintf(fp, L",NA"); + } + else { + fwprintf(fp, L",%.4lf", metrics.msUntilDisplayed); + } + } + if (args.mTrackPcLatency) { + if (metrics.msPcLatency.has_value()) { + fwprintf(fp, L",%.4lf", metrics.msPcLatency.value()); + } + else { + fwprintf(fp, L",NA"); + } + } + } + + // CPUStartTime + switch (args.mTimeUnit) { + case TimeUnit::MilliSeconds: + fwprintf(fp, L",%.4lf", pmSession.TimestampToMilliSeconds(metrics.cpuStartQpc)); + break; + case TimeUnit::QPC: + fwprintf(fp, L",%llu", metrics.cpuStartQpc); + break; + case TimeUnit::QPCMilliSeconds: + fwprintf(fp, L",%.4lf", pmSession.TimestampDeltaToMilliSeconds(metrics.cpuStartQpc)); + break; + case TimeUnit::DateTime: { + SYSTEMTIME st = {}; + uint64_t ns = 0; + pmSession.TimestampToLocalSystemTime(metrics.cpuStartQpc, &st, &ns); + fwprintf(fp, L",%u-%u-%u %u:%02u:%02u.%09llu", st.wYear, + st.wMonth, + st.wDay, + st.wHour, + st.wMinute, + st.wSecond, + ns); + } + break; + default: + fwprintf(fp, L",%.4lf", 0.001 * pmSession.TimestampToMilliSeconds(metrics.cpuStartQpc)); + } + + // MsBetweenAppStart, MsCPUBusy, MsCPUWait + fwprintf(fp, L",%.4lf,%.4lf,%.4lf", metrics.msCPUBusy + metrics.msCPUWait, + metrics.msCPUBusy, + metrics.msCPUWait); + + if (args.mTrackGPU) { + fwprintf(fp, L",%.4lf,%.4lf,%.4lf,%.4lf", metrics.msGPULatency, + metrics.msGPUBusy + metrics.msGPUWait, + metrics.msGPUBusy, + metrics.msGPUWait); + } + if (args.mTrackGPUVideo) { + fwprintf(fp, L",%.4lf", metrics.msVideoBusy); + } + if (args.mTrackDisplay) { + if (args.mUseV2Metrics) { + if (metrics.msDisplayedTime == 0.0) { + fwprintf(fp, L",NA,NA"); + } + else { + fwprintf(fp, L",%.4lf,%.4lf", metrics.msDisplayLatency, + metrics.msDisplayedTime); + } + } + if (metrics.msAnimationError.has_value()) { + fwprintf(fp, L",%.4lf", metrics.msAnimationError.value()); + } + else { + fwprintf(fp, L",NA"); + } + if (metrics.msAnimationTime.has_value()) { + fwprintf(fp, L",%.4lf", metrics.msAnimationTime.value()); + } + else { + fwprintf(fp, L",NA"); + } + if (metrics.msFlipDelay.has_value()) { + fwprintf(fp, L",%.4lf", metrics.msFlipDelay.value()); + } + else { + fwprintf(fp, L",NA"); + } + } + if (args.mTrackInput) { + if (metrics.msAllInputPhotonLatency.has_value()) { + fwprintf(fp, L",%.4lf", metrics.msAllInputPhotonLatency.value()); + } + else { + fwprintf(fp, L",NA"); + } + if (metrics.msClickToPhotonLatency.has_value()) { + fwprintf(fp, L",%.4lf", metrics.msClickToPhotonLatency.value()); + } + else { + fwprintf(fp, L",NA"); + } + } + if (args.mTrackAppTiming) { + if (metrics.msInstrumentedLatency.has_value()) { + fwprintf(fp, L",%.4lf", metrics.msInstrumentedLatency.value()); + } + else { + fwprintf(fp, L",NA"); + } + } + if (args.mWriteDisplayTime) { + if (metrics.screenTimeQpc == 0) { + fwprintf(fp, L",NA"); + } + else { + fwprintf(fp, L",%.4lf", pmSession.TimestampToMilliSeconds(metrics.screenTimeQpc)); + } + } + if (args.mWriteFrameId) { + fwprintf(fp, L",%u", p.FrameId); + if (args.mTrackAppTiming) { + fwprintf(fp, L",%u", p.AppFrameId); + } + if (args.mTrackPcLatency) { + fwprintf(fp, L",%u", p.PclFrameId); + } + } + fwprintf(fp, L"\n"); + + if (args.mCSVOutput == CSVOutput::Stdout) { + fflush(fp); + } +} + template void UpdateCsvT( PMTraceSession const& pmSession, @@ -666,6 +1018,11 @@ void UpdateCsv(PMTraceSession const& pmSession, ProcessInfo* processInfo, Presen UpdateCsvT(pmSession, processInfo, p, metrics); } +void UpdateCsv(PMTraceSession const& pmSession, ProcessInfo* processInfo, PresentEvent const& p, pmon::util::metrics::FrameMetrics const& metrics) +{ + UpdateCsvT(pmSession, processInfo, p, metrics); +} + static void CloseCsv(FILE** fp) { if (*fp != nullptr) { diff --git a/PresentMon/OutputThread.cpp b/PresentMon/OutputThread.cpp index be311795..2a76a654 100644 --- a/PresentMon/OutputThread.cpp +++ b/PresentMon/OutputThread.cpp @@ -5,6 +5,8 @@ #include "PresentMon.hpp" #include "../IntelPresentMon/CommonUtilities/Math.h" #include "../IntelPresentMon/CommonUtilities/str/String.h" +#include "../IntelPresentMon/CommonUtilities/Mc/MetricsTypes.h" +#include "../IntelPresentMon/CommonUtilities/Mc/MetricsCalculator.h" #include #include @@ -353,6 +355,7 @@ static void AdjustScreenTimeForCollapsedPresentNV1( } } +PM_UNUSED_FN static void ReportMetrics1( PMTraceSession const& pmSession, ProcessInfo* processInfo, @@ -771,6 +774,7 @@ static void ReportMetricsHelper( UpdateChain(chain, p); } +PM_UNUSED_FN static void ReportMetrics( PMTraceSession const& pmSession, ProcessInfo* processInfo, @@ -926,13 +930,8 @@ static bool GetPresentProcessInfo( auto chain = &processInfo->mSwapChain[presentEvent->SwapChainAddress]; if (chain->mLastPresent == nullptr) { - // Step 1: seed unified swapchain too (GetPresentProcessInfo() early-returns before metrics). - { - using namespace pmon::util::metrics; - FrameData fd = FrameData::CopyFrameData(presentEvent); - chain->mUnifiedSwapChain.Seed(std::move(fd)); - } UpdateChain(chain, presentEvent); + chain->mUnified.SeedFromFirstPresent(presentEvent); return true; } @@ -964,6 +963,124 @@ static void ProcessRecordingToggle( } } +static void SanitizeRepeatedFlips(std::shared_ptr const& p) +{ + // Exact legacy behavior: remove Application<->Repeated flip pairs. + for (size_t i = 0; i + 1 < p->Displayed.size(); ) { + auto a = p->Displayed[i].first; + auto b = p->Displayed[i + 1].first; + + if (a == FrameType::Application && b == FrameType::Repeated) { + p->Displayed.erase(p->Displayed.begin() + i + 1); + } + else if (a == FrameType::Repeated && b == FrameType::Application) { + p->Displayed.erase(p->Displayed.begin() + i); + } + else { + ++i; + } + } +} + +static bool ShouldUpdateLegacyChain( + std::shared_ptr const& p, + std::shared_ptr const& nextDisplayed) +{ + // Mirrors legacy UpdateChain timing and the unified calculators update rules: + // - Not presented: update immediately + // - Presented + has nextDisplayed: update (Case 3 finalizes) + // - Presented + no nextDisplayed: update only if DisplayedCount > 1 (Case 2 processed an instance) + if (p->FinalState != PresentResult::Presented) return true; + if (nextDisplayed != nullptr) return true; + return p->Displayed.size() > 1; +} + +static void AdjustScreenTimeForCollapsedPresentNV1_Unified( + pmon::util::metrics::SwapChainCoreState const& s, + std::shared_ptr const& p, + uint64_t& screenTime, + uint64_t& flipDelayQpc) +{ + // Port of AdjustScreenTimeForCollapsedPresentNV1(), but without mutating PresentEvent. + // Uses unified state instead of legacy chain fields. + if (s.lastDisplayedFlipDelay > 0 && (s.lastDisplayedScreenTime > screenTime)) { + if (!p->Displayed.empty()) { + flipDelayQpc += (s.lastDisplayedScreenTime - screenTime); + screenTime = s.lastDisplayedScreenTime; + } + } +} + +static void ReportMetrics1Unified( + PMTraceSession const& pmSession, + ProcessInfo* processInfo, + SwapChainData* chain, + std::shared_ptr const& p, + bool isRecording, + bool computeAvg) +{ + auto const& s = chain->mUnified.core; + + const bool displayed = (p->FinalState == PresentResult::Presented); + uint64_t screenTime = p->Displayed.empty() ? 0 : p->Displayed[0].second; + + uint64_t flipDelayQpc = p->FlipDelay; + AdjustScreenTimeForCollapsedPresentNV1_Unified(s, p, screenTime, flipDelayQpc); + + FrameMetrics1 metrics{}; + + // Between presents (use unified lastPresent) + if (s.lastPresent.has_value()) { + metrics.msBetweenPresents = pmSession.TimestampDeltaToUnsignedMilliSeconds( + s.lastPresent->presentStartTime, + p->PresentStartTime); + } + else { + metrics.msBetweenPresents = 0; + } + + metrics.msInPresentApi = pmSession.TimestampDeltaToMilliSeconds(p->TimeInPresent); + metrics.msUntilRenderComplete = pmSession.TimestampDeltaToMilliSeconds(p->PresentStartTime, p->ReadyTime); + + metrics.msUntilDisplayed = (!displayed || screenTime == 0) + ? 0 + : pmSession.TimestampDeltaToUnsignedMilliSeconds(p->PresentStartTime, screenTime); + + metrics.msBetweenDisplayChange = (!displayed || s.lastDisplayedScreenTime == 0 || screenTime == 0) + ? 0 + : pmSession.TimestampDeltaToUnsignedMilliSeconds(s.lastDisplayedScreenTime, screenTime); + + metrics.msUntilRenderStart = pmSession.TimestampDeltaToMilliSeconds(p->PresentStartTime, p->GPUStartTime); + metrics.msGPUDuration = pmSession.TimestampDeltaToMilliSeconds(p->GPUDuration); + metrics.msVideoDuration = pmSession.TimestampDeltaToMilliSeconds(p->GPUVideoDuration); + + metrics.msSinceInput = (p->InputTime == 0) + ? 0 + : pmSession.TimestampDeltaToMilliSeconds(p->PresentStartTime - p->InputTime); + + metrics.qpcScreenTime = screenTime; + + metrics.msFlipDelay = (flipDelayQpc != 0) + ? pmSession.TimestampDeltaToMilliSeconds(flipDelayQpc) + : 0; + + if (isRecording) { + UpdateCsv(pmSession, processInfo, *p, metrics); + } + + if (computeAvg) { + UpdateAverage(&chain->mAvgCPUDuration, metrics.msBetweenPresents); + UpdateAverage(&chain->mAvgGPUDuration, metrics.msGPUDuration); + + if (metrics.msUntilDisplayed > 0) { + UpdateAverage(&chain->mAvgDisplayLatency, metrics.msUntilDisplayed); + if (metrics.msBetweenDisplayChange > 0) { + UpdateAverage(&chain->mAvgDisplayedTime, metrics.msBetweenDisplayChange); + } + } + } +} + static void ProcessEvents( PMTraceSession const& pmSession, std::vector> const& presentEvents, @@ -1058,13 +1175,68 @@ static void ProcessEvents( // // If there are more than one pending PresentEvents, then the first one is displayed and the // rest aren't. Otherwise, there will only be one (or zero) pending presents. - if (isRecording || computeAvg) { + // Always feed the unified sequencer first. + auto ready = chain->mUnified.Enqueue(presentEvent); + + // If we are not outputting anything, we still must advance unified state. + const bool emit = (isRecording || computeAvg); + + for (auto const& it : ready) { + // Build FrameData copies for the unified calculator state-advance (and V2 metrics). + using namespace pmon::util::metrics; + + FrameData frame = FrameData::CopyFrameData(it.present); + + FrameData nextFrame{}; + FrameData* nextPtr = nullptr; + if (it.nextDisplayed != nullptr) { + nextFrame = FrameData::CopyFrameData(it.nextDisplayed); + nextPtr = &nextFrame; + } + if (args.mUseV1Metrics) { - ReportMetrics1(pmSession, processInfo, chain, presentEvent, isRecording, computeAvg); - } else { - ReportMetrics(pmSession, processInfo, chain, presentEvent, isRecording, computeAvg); + // V1 emitter reads unified state BEFORE it advances. + if (emit) { + ReportMetrics1Unified(pmSession, processInfo, chain, it.present, isRecording, computeAvg); + } + + // Advance unified swapchain state using the canonical unified calculator. + (void)ComputeMetricsForPresent(qpc, frame, nextPtr, chain->mUnified.core); } - } else { + else { + // V2 unified metrics: compute + advance together + auto computed = ComputeMetricsForPresent(qpc, frame, nextPtr, chain->mUnified.core); + + if (emit) { + for (auto const& cm : computed) { + auto const& m = cm.metrics; + + if (isRecording) { + UpdateCsv(pmSession, processInfo, *it.present, m); + } + + if (computeAvg) { + UpdateAverage(&chain->mAvgCPUDuration, m.msCPUBusy + m.msCPUWait); + if (m.msUntilDisplayed > 0) { + UpdateAverage(&chain->mAvgDisplayLatency, m.msDisplayLatency); + UpdateAverage(&chain->mAvgDisplayedTime, m.msDisplayedTime); + UpdateAverage(&chain->mAvgMsUntilDisplayed, m.msUntilDisplayed); + UpdateAverage(&chain->mAvgMsBetweenDisplayChange, m.msBetweenDisplayChange); + } + } + } + } + } + + // Temporary compatibility shim: keep legacy swapchain fields in sync enough for pruning/etc. + if (ShouldUpdateLegacyChain(it.present, it.nextDisplayed)) { + UpdateChain(chain, it.present); + } + } + + // If not emitting and nothing became ready (e.g., queued behind displayed), legacy behavior still + // updated the chain with the raw present. Keep that until Step 3 removes legacy dependencies. + if (!emit && ready.empty()) { UpdateChain(chain, presentEvent); } } @@ -1169,6 +1341,7 @@ void Output(PMTraceSession const* pmSession) gRecordingToggleHistory.shrink_to_fit(); } + void StartOutputThread(PMTraceSession const& pmSession) { InitializeCriticalSection(&gRecordingToggleCS); @@ -1186,3 +1359,56 @@ void StopOutputThread() } } +void SwapChainData::UnifiedConsoleSwapChain::SeedFromFirstPresent(std::shared_ptr const& p) +{ + using namespace pmon::util::metrics; + + pending.clear(); + + FrameData fd = FrameData::CopyFrameData(p); + core.pendingPresents.clear(); + core.UpdateAfterPresent(fd); +} + +std::vector +SwapChainData::UnifiedConsoleSwapChain::Enqueue(std::shared_ptr const& p) +{ + std::vector out; + + // Keep legacy displayed[] normalization centralized + SanitizeRepeatedFlips(p); + + // Seed baseline if needed (matches legacy early-init behavior) + if (!core.lastPresent.has_value()) { + SeedFromFirstPresent(p); + return out; + } + + const bool isDisplayed = (p->FinalState == PresentResult::Presented) && !p->Displayed.empty(); + + if (isDisplayed) { + // Flush everything pending using this displayed present as nextDisplayed + for (auto const& pe : pending) { + out.push_back(ReadyItem{ pe, p }); + } + + // Current displayed present is ready too (but with no nextDisplayed yet) + out.push_back(ReadyItem{ p, nullptr }); + + // Reset pending: now waiting on the *next* displayed present + pending.clear(); + pending.push_back(p); + return out; + } + + // Not displayed: + // If nothing is waiting, it is ready now. Otherwise queue behind the displayed present. + if (pending.empty()) { + out.push_back(ReadyItem{ p, nullptr }); + } + else { + pending.push_back(p); + } + + return out; +} \ No newline at end of file diff --git a/PresentMon/PresentMon.hpp b/PresentMon/PresentMon.hpp index fde71d4f..ece7b109 100644 --- a/PresentMon/PresentMon.hpp +++ b/PresentMon/PresentMon.hpp @@ -30,10 +30,19 @@ which is controlled from MainThread based on user input or timer. #include "../PresentData/PresentMonTraceConsumer.hpp" #include "../PresentData/PresentMonTraceSession.hpp" #include "../IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h" +#include "../IntelPresentMon/CommonUtilities/mc/SwapChainState.h" +#include "../IntelPresentMon/CommonUtilities/Qpc.h" #include #include #include +#include + +#if defined(_MSC_VER) +#define PM_UNUSED_FN __pragma(warning(suppress : 4505)) // unreferenced local function removed +#else +#define PM_UNUSED_FN +#endif // Verbosity of console output for normal operation: enum class ConsoleOutput { @@ -233,6 +242,27 @@ struct SwapChainData { pmon::util::metrics::SwapChainCoreState metricsState; // Unified swap chain for unfied metrics calculations pmon::util::metrics::UnifiedSwapChain mUnifiedSwapChain; + + struct UnifiedConsoleSwapChain + { + // Unified swapchain state (single source of truth for both V1 and V2). + pmon::util::metrics::SwapChainCoreState core; + + // Console sequencing rule: once a displayed present arrives, subsequent presents are queued + // until the next displayed present arrives. + std::deque> pending; + + struct ReadyItem + { + std::shared_ptr present; + std::shared_ptr nextDisplayed; // null unless flushing pending + }; + + void SeedFromFirstPresent(std::shared_ptr const& p); + std::vector Enqueue(std::shared_ptr const& p); + }; + UnifiedConsoleSwapChain mUnified; + }; struct ProcessInfo { From 6c34ccef720f765a31e0eb70d9ee39a2e5c879f0 Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 18 Dec 2025 10:27:31 +0900 Subject: [PATCH 065/205] add 3dm test in interim --- .../InterimBroadcasterTests.cpp | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 3ab75f2d..0ccb1906 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -755,7 +755,88 @@ namespace InterimBroadcasterTests Assert::IsTrue(range3.first <= range2.second); Assert::AreEqual(1905ull, range3.second); } + }; + + TEST_CLASS(FrameStoreBackpressuredPlayback3DMTests) + { + TestFixture fixture_; + public: + TEST_METHOD_INITIALIZE(Setup) + { + fixture_.Setup({ + "--etl-test-file"s, R"(..\..\Tests\AuxData\Data\P01TimeSpyDemoFS2080.etl)"s, + "--disable-legacy-backpressure"s + }); + } + TEST_METHOD_CLEANUP(Cleanup) + { + fixture_.Cleanup(); + } + TEST_METHOD(ReadFrames) + { + pmapi::Session session{ fixture_.GetCommonArgs().ctrlPipe }; + + // set up a fast flush + session.SetEtwFlushPeriod(8); + // make sure the flush period propagates to the flusher thread + std::this_thread::sleep_for(1ms); + + // setup query + PM_BEGIN_FIXED_FRAME_QUERY(FQ) + pmapi::FixedQueryElement timestamp{ this, PM_METRIC_CPU_START_QPC }; + pmapi::FixedQueryElement timeInPres{ this, PM_METRIC_IN_PRESENT_API }; + PM_END_FIXED_QUERY query{ session, 1'000 }; + struct Row { uint64_t timestamp; double timeInPresent; }; + std::vector frames; + + // we know the pid of interest in this etl file, track it + const uint32_t pid = 19736; + auto tracker = session.TrackProcess(pid); + + // sleep here to let the etw system warm up, and frames propagate + std::this_thread::sleep_for(300ms); + + const auto consume = [&] { + query.ForEachConsume(tracker, [&] { + frames.push_back(Row{ + .timestamp = query.timestamp, + .timeInPresent = query.timeInPres, + }); + }); + }; + + // verify that backpressure works correctly to ensure no frames are lost + consume(); + const auto count1 = query.PeekBlobContainer().GetNumBlobsPopulated(); + Logger::WriteMessage(std::format("count [{}]\n", count1).c_str()); + + std::this_thread::sleep_for(300ms); + + consume(); + const auto count2 = query.PeekBlobContainer().GetNumBlobsPopulated(); + Logger::WriteMessage(std::format("count [{}]\n", count2).c_str()); + + std::this_thread::sleep_for(500ms); + + consume(); + const auto count3 = query.PeekBlobContainer().GetNumBlobsPopulated(); + Logger::WriteMessage(std::format("count [{}]\n", count3).c_str()); + + // output timestamp of each frame + const auto outpath = fs::path{ outFolder_ } / + std::format("legacy-frames-32m-{:%Y%m%d-%H%M%S}.csv", std::chrono::system_clock::now()); + Logger::WriteMessage(std::format("Writing output to: {}\n", + fs::absolute(outpath).string()).c_str()); + + std::ofstream frameFile{ outpath }; + frameFile << "timestamp,timeInPresent\n"; + for (const auto& r : frames) { + frameFile << r.timestamp << ',' << r.timeInPresent << "\n"; + } + + Assert::AreEqual(2037u, count1 + count2 + count3); + } }; TEST_CLASS(LegacyBackpressuredPlaybackTests) From 72b9b85e19e560925f8e48a2dcb4604c18911109 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Thu, 18 Dec 2025 08:19:37 -0800 Subject: [PATCH 066/205] WIP changes for updating to updated unified swap chain --- .../CommonUtilities/mc/UnifiedSwapChain.cpp | 74 +++++++++---------- .../CommonUtilities/mc/UnifiedSwapChain.h | 25 ++++--- 2 files changed, 49 insertions(+), 50 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp index 6636dea4..ac6be17c 100644 --- a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp +++ b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp @@ -7,7 +7,7 @@ namespace pmon::util::metrics { - void UnifiedSwapChain::SanitizeDisplayedRepeats(FrameData& present) + void UnifiedSwapChain::SanitizeDisplayedRepeatedPresents(FrameData& present) { // Port of OutputThread.cpp::ReportMetrics() Remove Repeated flips pre-pass, // but applied to FrameData (so we dont mutate PresentEvent). @@ -31,63 +31,59 @@ namespace pmon::util::metrics } } - void UnifiedSwapChain::Seed(FrameData present) + void UnifiedSwapChain::SeedFromFirstPresent(FrameData present) { - SanitizeDisplayedRepeats(present); // Mirror console baseline behavior: // first present just seeds history (no pending pipeline). - core.pendingPresents.clear(); - core.UpdateAfterPresent(present); + swapChain.pendingPresents.clear(); + swapChain.UpdateAfterPresent(present); } - void UnifiedSwapChain::OnPresent(const QpcConverter& qpc, FrameData present) + // UnifiedSwapChain.cpp + std::vector + UnifiedSwapChain::Enqueue(FrameData present) { - SanitizeDisplayedRepeats(present); + SanitizeDisplayedRepeatedPresents(present); - // If unseeded, seed immediately and return. - // (Must not require qpc for the seed path.) - if (!core.lastPresent.has_value()) { - core.pendingPresents.clear(); - core.UpdateAfterPresent(present); - return; + std::vector out; + + // Seed baseline + if (!swapChain.lastPresent.has_value()) { + SeedFromFirstPresent(std::move(present)); + return out; } const bool isDisplayed = (present.getFinalState() == PresentResult::Presented) && (present.getDisplayedCount() > 0); - // Match the console pending until next displayed rule: - // - a displayed present blocks subsequent not-displayed presents until the next displayed arrives. if (isDisplayed) { - // 1) Flush pending frames using this displayed present as nextDisplayed. - // (This finalizes the previously postponed last-display instance.) - if (!core.pendingPresents.empty()) { - FrameData nextDisplayed = present; // ComputeMetricsForPresent needs a non-const pointer - for (const auto& blocked : core.pendingPresents) { - (void)ComputeMetricsForPresent(qpc, blocked, &nextDisplayed, core); - } + // 1) Finalize previously waiting displayed + if (waitingDisplayed_.has_value()) { + out.push_back(ReadyItem{ std::move(*waitingDisplayed_), present /* nextDisplayed */ }); + waitingDisplayed_.reset(); + } + + // 2) Release blocked not-displayed frames + while (!blocked_.empty()) { + out.push_back(ReadyItem{ std::move(blocked_.front()), std::nullopt }); + blocked_.pop_front(); } - // 2) Process this present with nextDisplayed=nullptr: - // - applies state deltas for all display instances it can resolve - // - DOES NOT call UpdateAfterPresent() yet (by design) - (void)ComputeMetricsForPresent(qpc, present, nullptr, core); - - // 3) This present becomes the new waiting for next displayed - core.pendingPresents.clear(); - core.pendingPresents.push_back(present); - return; + // 3) Current displayed is ready (all-but-last); becomes the new waitingDisplayed + out.push_back(ReadyItem{ present /* copy */, std::nullopt }); + waitingDisplayed_ = std::move(present); + return out; } - // Not displayed: - // - If nothing is waiting, process immediately (this path calls UpdateAfterPresent()). - // - Otherwise, queue behind the waiting displayed present. - if (core.pendingPresents.empty()) { - (void)ComputeMetricsForPresent(qpc, present, nullptr, core); - } - else { - core.pendingPresents.push_back(present); + // Not displayed + if (waitingDisplayed_.has_value()) { + blocked_.push_back(std::move(present)); + return out; // nothing ready yet } + + out.push_back(ReadyItem{ std::move(present), std::nullopt }); + return out; } } \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h index bd831a17..b2009d18 100644 --- a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h +++ b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h @@ -3,30 +3,33 @@ #pragma once #include +#include #include +#include #include "SwapChainState.h" #include "MetricsCalculator.h" // ComputeMetricsForPresent() namespace pmon::util::metrics { - // Phase 1: shared unified swapchain state machine (parallel to legacy UpdateChain()). - // - // - Owns sequencing (pending until next displayed) using FrameData snapshots. - // - Advances SwapChainCoreState by calling ComputeMetricsForPresent() and discarding results. - // - Does NOT do any CSV/console/middleware output (callers can ignore it). struct UnifiedSwapChain { - SwapChainCoreState core; + struct ReadyItem + { + FrameData present; + std::optional nextDisplayed; // populated when flushing pending + }; + + SwapChainCoreState swapChain; // Seed without needing a QPC converter (needed for console GetPresentProcessInfo() early-return). - void Seed(FrameData present); + void SeedFromFirstPresent(FrameData present); - // Feed every present into the unified swapchain (callers may ignore results). - void OnPresent(const QpcConverter& qpc, FrameData present); + std::vector Enqueue(FrameData present); private: - // Matches the console pre-pass that removes Application<->Repeated flip pairs. - static void SanitizeDisplayedRepeats(FrameData& present); + static void SanitizeDisplayedRepeatedPresents(FrameData& present); + std::optional waitingDisplayed_; + std::deque blocked_; }; } From 179d6b1f309c1a3173439c9198c8d03f653d29ba Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Thu, 18 Dec 2025 13:16:30 -0800 Subject: [PATCH 067/205] Continued changes for unified swap chain --- .../CommonUtilities/mc/MetricsTypes.cpp | 28 +- .../CommonUtilities/mc/MetricsTypes.h | 16 +- .../PresentMonUtils/StreamFormat.h | 1 + PresentMon/CsvOutput.cpp | 354 ++++++++++++++++++ PresentMon/OutputThread.cpp | 156 ++------ PresentMon/PresentMon.hpp | 25 +- 6 files changed, 431 insertions(+), 149 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp index 2d00114c..6e6d68b1 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp @@ -10,6 +10,9 @@ namespace pmon::util::metrics { FrameData FrameData::CopyFrameData(const PmNsmPresentEvent& p) { FrameData frame{}; + frame.runtime = p.Runtime; + frame.presentMode = p.PresentMode; + frame.presentStartTime = p.PresentStartTime; frame.readyTime = p.ReadyTime; frame.timeInPresent = p.TimeInPresent; @@ -51,12 +54,17 @@ namespace pmon::util::metrics { }); } - frame.finalState = p.FinalState; frame.swapChainAddress = p.SwapChainAddress; + frame.syncInterval = p.SyncInterval; + frame.presentFlags = p.PresentFlags; + + frame.finalState = p.FinalState; + frame.supportsTearing = p.SupportsTearing; frame.frameId = p.FrameId; frame.processId = p.ProcessId; frame.threadId = p.ThreadId; frame.appFrameId = p.AppFrameId; + frame.pclFrameId = p.PclFrameId; return frame; } @@ -64,6 +72,8 @@ namespace pmon::util::metrics { FrameData FrameData::CopyFrameData(const std::shared_ptr& p) { FrameData frame{}; + frame.runtime = p->Runtime; + frame.presentMode = p->PresentMode; frame.presentStartTime = p->PresentStartTime; frame.readyTime = p->ReadyTime; frame.timeInPresent = p->TimeInPresent; @@ -98,12 +108,17 @@ namespace pmon::util::metrics { frame.displayed = p->Displayed; - frame.finalState = p->FinalState; frame.swapChainAddress = p->SwapChainAddress; + frame.syncInterval = p->SyncInterval; + frame.presentFlags = p->PresentFlags; + + frame.finalState = p->FinalState; + frame.supportsTearing = p->SupportsTearing; frame.frameId = p->FrameId; frame.processId = p->ProcessId; frame.threadId = p->ThreadId; frame.appFrameId = p->AppFrameId; + frame.pclFrameId = p->PclFrameId; return frame; } @@ -111,6 +126,8 @@ namespace pmon::util::metrics { FrameData FrameData::CopyFrameData(const PresentEvent& p) { FrameData frame{}; + frame.runtime = p.Runtime; + frame.presentMode = p.PresentMode; frame.presentStartTime = p.PresentStartTime; frame.readyTime = p.ReadyTime; frame.timeInPresent = p.TimeInPresent; @@ -145,12 +162,17 @@ namespace pmon::util::metrics { frame.displayed = p.Displayed; - frame.finalState = p.FinalState; frame.swapChainAddress = p.SwapChainAddress; + frame.syncInterval = p.SyncInterval; + frame.presentFlags = p.PresentFlags; + + frame.finalState = p.FinalState; + frame.supportsTearing = p.SupportsTearing; frame.frameId = p.FrameId; frame.processId = p.ProcessId; frame.threadId = p.ThreadId; frame.appFrameId = p.AppFrameId; + frame.pclFrameId = p.PclFrameId; return frame; } diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h index eda31d39..26e84bc3 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h @@ -7,6 +7,8 @@ #include // Forward declarations for external types +enum class Runtime; +enum class PresentMode; enum class FrameType; // From PresentData enum class PresentResult; // From PresentData enum class InputDeviceType; // From PresentData @@ -24,6 +26,9 @@ namespace pmon::util::metrics { // Immutable snapshot - safe for both ownership models struct FrameData { + Runtime runtime = {}; + PresentMode presentMode = {}; + // Timing Data uint64_t presentStartTime = 0; uint64_t readyTime = 0; @@ -63,13 +68,20 @@ namespace pmon::util::metrics { uint64_t flipDelay = 0; uint32_t flipToken = 0; + // Extra present parameters obtained through DXGI or D3D9 present + uint64_t swapChainAddress = 0; + int32_t syncInterval = 0; + uint32_t presentFlags = 0; + // Metadata - PresentResult finalState; + PresentResult finalState = {}; + bool supportsTearing = 0; + bool isHybridPresent = false; uint32_t processId = 0; uint32_t threadId = 0; - uint64_t swapChainAddress = 0; uint32_t frameId = 0; uint32_t appFrameId = 0; + uint32_t pclFrameId = 0; // Setters for test setup void setFinalState(PresentResult state) { finalState = state; } diff --git a/IntelPresentMon/PresentMonUtils/StreamFormat.h b/IntelPresentMon/PresentMonUtils/StreamFormat.h index 26a31919..7c53e1f8 100644 --- a/IntelPresentMon/PresentMonUtils/StreamFormat.h +++ b/IntelPresentMon/PresentMonUtils/StreamFormat.h @@ -130,6 +130,7 @@ struct PmNsmPresentEvent uint32_t FrameId; // ID for the logical frame that this Present is associated with. uint32_t AppFrameId; // Application provided frame ID + uint32_t PclFrameId; // PC Latency provided frame ID Runtime Runtime; PresentMode PresentMode; diff --git a/PresentMon/CsvOutput.cpp b/PresentMon/CsvOutput.cpp index 650f18ce..023f60ec 100644 --- a/PresentMon/CsvOutput.cpp +++ b/PresentMon/CsvOutput.cpp @@ -137,6 +137,9 @@ void WriteCsvHeader(FILE* fp); template void WriteCsvRow(FILE* fp, PMTraceSession const& pmSession, ProcessInfo const& processInfo, PresentEvent const& p, FrameMetricsT const& metrics); +template +void WriteCsvRow(FILE* fp, PMTraceSession const& pmSession, ProcessInfo const& processInfo, pmon::util::metrics::FrameData const& p, FrameMetricsT const& metrics); + template<> void WriteCsvHeader(FILE* fp) { @@ -266,6 +269,86 @@ void WriteCsvRow( } } +template<> +void WriteCsvRow( + FILE* fp, + PMTraceSession const& pmSession, + ProcessInfo const& processInfo, + pmon::util::metrics::FrameData const& p, + FrameMetrics1 const& metrics) +{ + auto const& args = GetCommandLineArgs(); + + fwprintf(fp, L"%s,%d,0x%016llX,%hs,%d,%d,%hs", processInfo.mModuleName.c_str(), + p.processId, + p.swapChainAddress, + RuntimeToString(p.runtime), + p.syncInterval, + p.presentFlags, + FinalStateToDroppedString(p.finalState)); + switch (args.mTimeUnit) { + case TimeUnit::DateTime: { + SYSTEMTIME st = {}; + uint64_t ns = 0; + pmSession.TimestampToLocalSystemTime(p.presentStartTime, &st, &ns); + fwprintf(fp, L",%u-%u-%u %u:%02u:%02u.%09llu", st.wYear, + st.wMonth, + st.wDay, + st.wHour, + st.wMinute, + st.wSecond, + ns); + } break; + default: + fwprintf(fp, L",%.*lf", DBL_DIG - 1, 0.001 * pmSession.TimestampToMilliSeconds(p.presentStartTime)); + break; + } + fwprintf(fp, L",%.*lf,%.*lf", DBL_DIG - 1, metrics.msInPresentApi, + DBL_DIG - 1, metrics.msBetweenPresents); + if (args.mTrackDisplay) { + fwprintf(fp, L",%d,%hs,%.*lf,%.*lf,%.*lf,%.*lf", p.supportsTearing, + PresentModeToString(p.presentMode), + DBL_DIG - 1, metrics.msUntilRenderComplete, + DBL_DIG - 1, metrics.msUntilDisplayed, + DBL_DIG - 1, metrics.msBetweenDisplayChange, + DBL_DIG - 1, metrics.msFlipDelay); + } + if (args.mTrackGPU) { + fwprintf(fp, L",%.*lf,%.*lf", DBL_DIG - 1, metrics.msUntilRenderStart, + DBL_DIG - 1, metrics.msGPUDuration); + } + if (args.mTrackGPUVideo) { + fwprintf(fp, L",%.*lf", DBL_DIG - 1, metrics.msVideoDuration); + } + if (args.mTrackInput) { + fwprintf(fp, L",%.*lf", DBL_DIG - 1, metrics.msSinceInput); + } + switch (args.mTimeUnit) { + case TimeUnit::QPC: + fwprintf(fp, L",%llu", p.presentStartTime); + break; + case TimeUnit::QPCMilliSeconds: + fwprintf(fp, L",%.*lf", DBL_DIG - 1, 0.001 * pmSession.TimestampDeltaToMilliSeconds(p.presentStartTime)); + break; + } + if (args.mWriteDisplayTime) { + if (metrics.qpcScreenTime == 0) { + fwprintf(fp, L",NA"); + } + else { + fwprintf(fp, L",%.*lf", DBL_DIG - 1, 0.001 * pmSession.TimestampToMilliSeconds(metrics.qpcScreenTime)); + } + } + if (args.mWriteFrameId) { + fwprintf(fp, L",%u", p.frameId); + } + fwprintf(fp, L"\n"); + + if (args.mCSVOutput == CSVOutput::Stdout) { + fflush(fp); + } +} + template<> void WriteCsvHeader(FILE* fp) { @@ -965,6 +1048,223 @@ void WriteCsvRow( } } +template<> +void WriteCsvRow( + FILE* fp, + PMTraceSession const& pmSession, + ProcessInfo const& processInfo, + pmon::util::metrics::FrameData const& p, + pmon::util::metrics::FrameMetrics const& metrics) +{ + auto const& args = GetCommandLineArgs(); + + fwprintf(fp, L"%s,%d,0x%llX,%hs,%d,%d", processInfo.mModuleName.c_str(), + p.processId, + p.swapChainAddress, + RuntimeToString(p.runtime), + p.syncInterval, + p.presentFlags); + if (args.mTrackDisplay) { + fwprintf(fp, L",%d,%hs", p.supportsTearing, + PresentModeToString(p.presentMode)); + } + if (args.mTrackFrameType) { + fwprintf(fp, L",%hs", FrameTypeToString(metrics.frameType)); + } + if (args.mTrackHybridPresent) { + fwprintf(fp, L",%d", p.isHybridPresent); + } + + if (args.mUseV2Metrics == false) { + // Time in Seconds + switch (args.mTimeUnit) { + case TimeUnit::DateTime: { + SYSTEMTIME st = {}; + uint64_t ns = 0; + pmSession.TimestampToLocalSystemTime(metrics.timeInSeconds, &st, &ns); + fwprintf(fp, L",%u-%u-%u %u:%02u:%02u.%09llu", st.wYear, + st.wMonth, + st.wDay, + st.wHour, + st.wMinute, + st.wSecond, + ns); + } break; + case TimeUnit::MilliSeconds: + case TimeUnit::QPCMilliSeconds: + fwprintf(fp, L",%.4lf", pmSession.TimestampToMilliSeconds(metrics.timeInSeconds)); + break; + case TimeUnit::QPC: + fwprintf(fp, L",%llu", metrics.timeInSeconds); + break; + default: + fwprintf(fp, L",%.*lf", DBL_DIG - 1, 0.001 * pmSession.TimestampToMilliSeconds(metrics.timeInSeconds)); + break; + } + + // MsBetweenSimulationStart + if (metrics.msBetweenSimStarts.has_value()) { + fwprintf(fp, L",%.4lf", metrics.msBetweenSimStarts.value()); + } + else { + fwprintf(fp, L",NA"); + } + + // MsBetweenPresents + fwprintf(fp, L",%.*lf", DBL_DIG - 1, metrics.msBetweenPresents); + + // MsBetweenDisplayChange -> Transition from DisplayedTime + if (args.mTrackDisplay) { + if (metrics.msBetweenDisplayChange == 0.0) { + fwprintf(fp, L",NA"); + } + else { + fwprintf(fp, L",%.*lf", DBL_DIG - 1, metrics.msBetweenDisplayChange); + } + } + + // MsInPresentAPI + fwprintf(fp, L",%.*lf", DBL_DIG - 1, metrics.msInPresentApi); + + // MsRenderPresentLatency + fwprintf(fp, L",%.*lf", DBL_DIG - 1, metrics.msUntilRenderComplete); + + // MsUntilDisplayed + if (args.mTrackDisplay) { + if (metrics.msUntilDisplayed == 0.0) { + fwprintf(fp, L",NA"); + } + else { + fwprintf(fp, L",%.4lf", metrics.msUntilDisplayed); + } + } + if (args.mTrackPcLatency) { + if (metrics.msPcLatency.has_value()) { + fwprintf(fp, L",%.4lf", metrics.msPcLatency.value()); + } + else { + fwprintf(fp, L",NA"); + } + } + } + + // CPUStartTime + switch (args.mTimeUnit) { + case TimeUnit::MilliSeconds: + fwprintf(fp, L",%.4lf", pmSession.TimestampToMilliSeconds(metrics.cpuStartQpc)); + break; + case TimeUnit::QPC: + fwprintf(fp, L",%llu", metrics.cpuStartQpc); + break; + case TimeUnit::QPCMilliSeconds: + fwprintf(fp, L",%.4lf", pmSession.TimestampDeltaToMilliSeconds(metrics.cpuStartQpc)); + break; + case TimeUnit::DateTime: { + SYSTEMTIME st = {}; + uint64_t ns = 0; + pmSession.TimestampToLocalSystemTime(metrics.cpuStartQpc, &st, &ns); + fwprintf(fp, L",%u-%u-%u %u:%02u:%02u.%09llu", st.wYear, + st.wMonth, + st.wDay, + st.wHour, + st.wMinute, + st.wSecond, + ns); + } + break; + default: + fwprintf(fp, L",%.4lf", 0.001 * pmSession.TimestampToMilliSeconds(metrics.cpuStartQpc)); + } + + // MsBetweenAppStart, MsCPUBusy, MsCPUWait + fwprintf(fp, L",%.4lf,%.4lf,%.4lf", metrics.msCPUBusy + metrics.msCPUWait, + metrics.msCPUBusy, + metrics.msCPUWait); + + if (args.mTrackGPU) { + fwprintf(fp, L",%.4lf,%.4lf,%.4lf,%.4lf", metrics.msGPULatency, + metrics.msGPUBusy + metrics.msGPUWait, + metrics.msGPUBusy, + metrics.msGPUWait); + } + if (args.mTrackGPUVideo) { + fwprintf(fp, L",%.4lf", metrics.msVideoBusy); + } + if (args.mTrackDisplay) { + if (args.mUseV2Metrics) { + if (metrics.msDisplayedTime == 0.0) { + fwprintf(fp, L",NA,NA"); + } + else { + fwprintf(fp, L",%.4lf,%.4lf", metrics.msDisplayLatency, + metrics.msDisplayedTime); + } + } + if (metrics.msAnimationError.has_value()) { + fwprintf(fp, L",%.4lf", metrics.msAnimationError.value()); + } + else { + fwprintf(fp, L",NA"); + } + if (metrics.msAnimationTime.has_value()) { + fwprintf(fp, L",%.4lf", metrics.msAnimationTime.value()); + } + else { + fwprintf(fp, L",NA"); + } + if (metrics.msFlipDelay.has_value()) { + fwprintf(fp, L",%.4lf", metrics.msFlipDelay.value()); + } + else { + fwprintf(fp, L",NA"); + } + } + if (args.mTrackInput) { + if (metrics.msAllInputPhotonLatency.has_value()) { + fwprintf(fp, L",%.4lf", metrics.msAllInputPhotonLatency.value()); + } + else { + fwprintf(fp, L",NA"); + } + if (metrics.msClickToPhotonLatency.has_value()) { + fwprintf(fp, L",%.4lf", metrics.msClickToPhotonLatency.value()); + } + else { + fwprintf(fp, L",NA"); + } + } + if (args.mTrackAppTiming) { + if (metrics.msInstrumentedLatency.has_value()) { + fwprintf(fp, L",%.4lf", metrics.msInstrumentedLatency.value()); + } + else { + fwprintf(fp, L",NA"); + } + } + if (args.mWriteDisplayTime) { + if (metrics.screenTimeQpc == 0) { + fwprintf(fp, L",NA"); + } + else { + fwprintf(fp, L",%.4lf", pmSession.TimestampToMilliSeconds(metrics.screenTimeQpc)); + } + } + if (args.mWriteFrameId) { + fwprintf(fp, L",%u", p.frameId); + if (args.mTrackAppTiming) { + fwprintf(fp, L",%u", p.appFrameId); + } + if (args.mTrackPcLatency) { + fwprintf(fp, L",%u", p.pclFrameId); + } + } + fwprintf(fp, L"\n"); + + if (args.mCSVOutput == CSVOutput::Stdout) { + fflush(fp); + } +} + template void UpdateCsvT( PMTraceSession const& pmSession, @@ -1008,6 +1308,50 @@ void UpdateCsvT( WriteCsvRow(*fp, pmSession, *processInfo, p, metrics); } +template +void UpdateCsvT( + PMTraceSession const& pmSession, + ProcessInfo* processInfo, + pmon::util::metrics::FrameData const& p, + FrameMetricsT const& metrics) +{ + auto const& args = GetCommandLineArgs(); + + // Early return if not outputing to CSV. + if (args.mCSVOutput == CSVOutput::None) { + return; + } + + // Don't output dropped frames (if requested). + auto presented = p.finalState == PresentResult::Presented; + if (args.mExcludeDropped && !presented) { + return; + } + + // Get/create file + FILE** fp = args.mMultiCsv + ? &processInfo->mOutputCsv + : &gGlobalOutputCsv; + + if (*fp == nullptr) { + if (args.mCSVOutput == CSVOutput::File) { + wchar_t path[MAX_PATH]; + GenerateFilename(path, processInfo->mModuleName, p.processId); + if (_wfopen_s(fp, path, L"w,ccs=UTF-8")) { + return; + } + } + else { + *fp = stdout; + } + + WriteCsvHeader(*fp); + } + + // Output in CSV format + WriteCsvRow(*fp, pmSession, *processInfo, p, metrics); +} + void UpdateCsv(PMTraceSession const& pmSession, ProcessInfo* processInfo, PresentEvent const& p, FrameMetrics1 const& metrics) { UpdateCsvT(pmSession, processInfo, p, metrics); @@ -1023,6 +1367,16 @@ void UpdateCsv(PMTraceSession const& pmSession, ProcessInfo* processInfo, Presen UpdateCsvT(pmSession, processInfo, p, metrics); } +void UpdateCsv(PMTraceSession const& pmSession, ProcessInfo* processInfo, pmon::util::metrics::FrameData const& p, pmon::util::metrics::FrameMetrics const& metrics) +{ + UpdateCsvT(pmSession, processInfo, p, metrics); +} + +void UpdateCsv(PMTraceSession const& pmSession, ProcessInfo* processInfo, pmon::util::metrics::FrameData const& p, FrameMetrics1 const& metrics) +{ + UpdateCsvT(pmSession, processInfo, p, metrics); +} + static void CloseCsv(FILE** fp) { if (*fp != nullptr) { diff --git a/PresentMon/OutputThread.cpp b/PresentMon/OutputThread.cpp index 2a76a654..387c388b 100644 --- a/PresentMon/OutputThread.cpp +++ b/PresentMon/OutputThread.cpp @@ -931,7 +931,8 @@ static bool GetPresentProcessInfo( auto chain = &processInfo->mSwapChain[presentEvent->SwapChainAddress]; if (chain->mLastPresent == nullptr) { UpdateChain(chain, presentEvent); - chain->mUnified.SeedFromFirstPresent(presentEvent); + using namespace pmon::util::metrics; + chain->mUnifiedSwapChain.SeedFromFirstPresent(FrameData::CopyFrameData(presentEvent)); return true; } @@ -963,48 +964,29 @@ static void ProcessRecordingToggle( } } -static void SanitizeRepeatedFlips(std::shared_ptr const& p) -{ - // Exact legacy behavior: remove Application<->Repeated flip pairs. - for (size_t i = 0; i + 1 < p->Displayed.size(); ) { - auto a = p->Displayed[i].first; - auto b = p->Displayed[i + 1].first; - - if (a == FrameType::Application && b == FrameType::Repeated) { - p->Displayed.erase(p->Displayed.begin() + i + 1); - } - else if (a == FrameType::Repeated && b == FrameType::Application) { - p->Displayed.erase(p->Displayed.begin() + i); - } - else { - ++i; - } - } -} - static bool ShouldUpdateLegacyChain( - std::shared_ptr const& p, - std::shared_ptr const& nextDisplayed) + pmon::util::metrics::FrameData const& p, + std::optional const& nextDisplayed) { // Mirrors legacy UpdateChain timing and the unified calculators update rules: // - Not presented: update immediately // - Presented + has nextDisplayed: update (Case 3 finalizes) // - Presented + no nextDisplayed: update only if DisplayedCount > 1 (Case 2 processed an instance) - if (p->FinalState != PresentResult::Presented) return true; - if (nextDisplayed != nullptr) return true; - return p->Displayed.size() > 1; + if (p.finalState != PresentResult::Presented) return true; + if (nextDisplayed.has_value()) return true; + return p.displayed.size() > 1; } static void AdjustScreenTimeForCollapsedPresentNV1_Unified( pmon::util::metrics::SwapChainCoreState const& s, - std::shared_ptr const& p, + pmon::util::metrics::FrameData const& p, uint64_t& screenTime, uint64_t& flipDelayQpc) { // Port of AdjustScreenTimeForCollapsedPresentNV1(), but without mutating PresentEvent. // Uses unified state instead of legacy chain fields. if (s.lastDisplayedFlipDelay > 0 && (s.lastDisplayedScreenTime > screenTime)) { - if (!p->Displayed.empty()) { + if (!p.displayed.empty()) { flipDelayQpc += (s.lastDisplayedScreenTime - screenTime); screenTime = s.lastDisplayedScreenTime; } @@ -1015,16 +997,16 @@ static void ReportMetrics1Unified( PMTraceSession const& pmSession, ProcessInfo* processInfo, SwapChainData* chain, - std::shared_ptr const& p, + pmon::util::metrics::FrameData const& p, bool isRecording, bool computeAvg) { - auto const& s = chain->mUnified.core; + auto const& s = chain->mUnifiedSwapChain.swapChain; + + const bool displayed = (p.finalState == PresentResult::Presented); + uint64_t screenTime = p.displayed.empty() ? 0 : p.displayed[0].second; - const bool displayed = (p->FinalState == PresentResult::Presented); - uint64_t screenTime = p->Displayed.empty() ? 0 : p->Displayed[0].second; - - uint64_t flipDelayQpc = p->FlipDelay; + uint64_t flipDelayQpc = p.flipDelay; AdjustScreenTimeForCollapsedPresentNV1_Unified(s, p, screenTime, flipDelayQpc); FrameMetrics1 metrics{}; @@ -1033,30 +1015,30 @@ static void ReportMetrics1Unified( if (s.lastPresent.has_value()) { metrics.msBetweenPresents = pmSession.TimestampDeltaToUnsignedMilliSeconds( s.lastPresent->presentStartTime, - p->PresentStartTime); + p.presentStartTime); } else { metrics.msBetweenPresents = 0; } - metrics.msInPresentApi = pmSession.TimestampDeltaToMilliSeconds(p->TimeInPresent); - metrics.msUntilRenderComplete = pmSession.TimestampDeltaToMilliSeconds(p->PresentStartTime, p->ReadyTime); + metrics.msInPresentApi = pmSession.TimestampDeltaToMilliSeconds(p.timeInPresent); + metrics.msUntilRenderComplete = pmSession.TimestampDeltaToMilliSeconds(p.presentStartTime, p.readyTime); metrics.msUntilDisplayed = (!displayed || screenTime == 0) ? 0 - : pmSession.TimestampDeltaToUnsignedMilliSeconds(p->PresentStartTime, screenTime); + : pmSession.TimestampDeltaToUnsignedMilliSeconds(p.presentStartTime, screenTime); metrics.msBetweenDisplayChange = (!displayed || s.lastDisplayedScreenTime == 0 || screenTime == 0) ? 0 : pmSession.TimestampDeltaToUnsignedMilliSeconds(s.lastDisplayedScreenTime, screenTime); - metrics.msUntilRenderStart = pmSession.TimestampDeltaToMilliSeconds(p->PresentStartTime, p->GPUStartTime); - metrics.msGPUDuration = pmSession.TimestampDeltaToMilliSeconds(p->GPUDuration); - metrics.msVideoDuration = pmSession.TimestampDeltaToMilliSeconds(p->GPUVideoDuration); + metrics.msUntilRenderStart = pmSession.TimestampDeltaToMilliSeconds(p.presentStartTime, p.gpuStartTime); + metrics.msGPUDuration = pmSession.TimestampDeltaToMilliSeconds(p.gpuDuration); + metrics.msVideoDuration = pmSession.TimestampDeltaToMilliSeconds(p.gpuVideoDuration); - metrics.msSinceInput = (p->InputTime == 0) + metrics.msSinceInput = (p.inputTime == 0) ? 0 - : pmSession.TimestampDeltaToMilliSeconds(p->PresentStartTime - p->InputTime); + : pmSession.TimestampDeltaToMilliSeconds(p.presentStartTime - p.inputTime); metrics.qpcScreenTime = screenTime; @@ -1065,7 +1047,7 @@ static void ReportMetrics1Unified( : 0; if (isRecording) { - UpdateCsv(pmSession, processInfo, *p, metrics); + UpdateCsv(pmSession, processInfo, p, metrics); } if (computeAvg) { @@ -1162,57 +1144,43 @@ static void ProcessEvents( continue; } - // Step 1: always advance unified swapchain (parallel state machine), regardless of recording/stats. - // Ignore output here; this step is only to keep unified state warm. - { - using namespace pmon::util::metrics; - FrameData fd = FrameData::CopyFrameData(presentEvent); - chain->mUnifiedSwapChain.OnPresent(qpc, std::move(fd)); - } + auto ready = chain->mUnifiedSwapChain.Enqueue(pmon::util::metrics::FrameData::CopyFrameData(presentEvent)); - // If we are recording or presenting metrics to console then update the metrics and pending - // presents. Otherwise, just update the latest present details in the chain. - // - // If there are more than one pending PresentEvents, then the first one is displayed and the - // rest aren't. Otherwise, there will only be one (or zero) pending presents. - // Always feed the unified sequencer first. - auto ready = chain->mUnified.Enqueue(presentEvent); - - // If we are not outputting anything, we still must advance unified state. + // Do we need to emit metrics for this present? const bool emit = (isRecording || computeAvg); for (auto const& it : ready) { // Build FrameData copies for the unified calculator state-advance (and V2 metrics). using namespace pmon::util::metrics; - FrameData frame = FrameData::CopyFrameData(it.present); + FrameData frame = std::move(it.present); FrameData nextFrame{}; FrameData* nextPtr = nullptr; - if (it.nextDisplayed != nullptr) { - nextFrame = FrameData::CopyFrameData(it.nextDisplayed); + if (it.nextDisplayed.has_value()) { + nextFrame = std::move(*it.nextDisplayed); nextPtr = &nextFrame; } if (args.mUseV1Metrics) { // V1 emitter reads unified state BEFORE it advances. if (emit) { - ReportMetrics1Unified(pmSession, processInfo, chain, it.present, isRecording, computeAvg); + ReportMetrics1Unified(pmSession, processInfo, chain, frame, isRecording, computeAvg); } // Advance unified swapchain state using the canonical unified calculator. - (void)ComputeMetricsForPresent(qpc, frame, nextPtr, chain->mUnified.core); + (void)ComputeMetricsForPresent(qpc, frame, nextPtr, chain->mUnifiedSwapChain.swapChain); } else { // V2 unified metrics: compute + advance together - auto computed = ComputeMetricsForPresent(qpc, frame, nextPtr, chain->mUnified.core); + auto computed = ComputeMetricsForPresent(qpc, frame, nextPtr, chain->mUnifiedSwapChain.swapChain); if (emit) { for (auto const& cm : computed) { auto const& m = cm.metrics; if (isRecording) { - UpdateCsv(pmSession, processInfo, *it.present, m); + UpdateCsv(pmSession, processInfo, frame, m); } if (computeAvg) { @@ -1230,7 +1198,7 @@ static void ProcessEvents( // Temporary compatibility shim: keep legacy swapchain fields in sync enough for pruning/etc. if (ShouldUpdateLegacyChain(it.present, it.nextDisplayed)) { - UpdateChain(chain, it.present); + UpdateChain(chain, presentEvent); } } @@ -1357,58 +1325,4 @@ void StopOutputThread() DeleteCriticalSection(&gRecordingToggleCS); } -} - -void SwapChainData::UnifiedConsoleSwapChain::SeedFromFirstPresent(std::shared_ptr const& p) -{ - using namespace pmon::util::metrics; - - pending.clear(); - - FrameData fd = FrameData::CopyFrameData(p); - core.pendingPresents.clear(); - core.UpdateAfterPresent(fd); -} - -std::vector -SwapChainData::UnifiedConsoleSwapChain::Enqueue(std::shared_ptr const& p) -{ - std::vector out; - - // Keep legacy displayed[] normalization centralized - SanitizeRepeatedFlips(p); - - // Seed baseline if needed (matches legacy early-init behavior) - if (!core.lastPresent.has_value()) { - SeedFromFirstPresent(p); - return out; - } - - const bool isDisplayed = (p->FinalState == PresentResult::Presented) && !p->Displayed.empty(); - - if (isDisplayed) { - // Flush everything pending using this displayed present as nextDisplayed - for (auto const& pe : pending) { - out.push_back(ReadyItem{ pe, p }); - } - - // Current displayed present is ready too (but with no nextDisplayed yet) - out.push_back(ReadyItem{ p, nullptr }); - - // Reset pending: now waiting on the *next* displayed present - pending.clear(); - pending.push_back(p); - return out; - } - - // Not displayed: - // If nothing is waiting, it is ready now. Otherwise queue behind the displayed present. - if (pending.empty()) { - out.push_back(ReadyItem{ p, nullptr }); - } - else { - pending.push_back(p); - } - - return out; } \ No newline at end of file diff --git a/PresentMon/PresentMon.hpp b/PresentMon/PresentMon.hpp index ece7b109..908f75e6 100644 --- a/PresentMon/PresentMon.hpp +++ b/PresentMon/PresentMon.hpp @@ -238,31 +238,8 @@ struct SwapChainData { // Internal NVIDIA Metrics uint64_t mLastDisplayedFlipDelay = 0; - // Unified metrics state for computing metrics - pmon::util::metrics::SwapChainCoreState metricsState; // Unified swap chain for unfied metrics calculations pmon::util::metrics::UnifiedSwapChain mUnifiedSwapChain; - - struct UnifiedConsoleSwapChain - { - // Unified swapchain state (single source of truth for both V1 and V2). - pmon::util::metrics::SwapChainCoreState core; - - // Console sequencing rule: once a displayed present arrives, subsequent presents are queued - // until the next displayed present arrives. - std::deque> pending; - - struct ReadyItem - { - std::shared_ptr present; - std::shared_ptr nextDisplayed; // null unless flushing pending - }; - - void SeedFromFirstPresent(std::shared_ptr const& p); - std::vector Enqueue(std::shared_ptr const& p); - }; - UnifiedConsoleSwapChain mUnified; - }; struct ProcessInfo { @@ -302,7 +279,9 @@ const char* PresentModeToString(PresentMode mode); const char* RuntimeToString(Runtime rt); void UpdateCsv(PMTraceSession const& pmSession, ProcessInfo* processInfo, PresentEvent const& p, FrameMetrics const& metrics); void UpdateCsv(PMTraceSession const& pmSession, ProcessInfo* processInfo, PresentEvent const& p, FrameMetrics1 const& metrics); +void UpdateCsv(PMTraceSession const& pmSession, ProcessInfo* processInfo, pmon::util::metrics::FrameData const& p, FrameMetrics1 const& metrics); void UpdateCsv(PMTraceSession const& pmSession, ProcessInfo* processInfo, PresentEvent const& p, pmon::util::metrics::FrameMetrics const& metrics); +void UpdateCsv(PMTraceSession const& pmSession, ProcessInfo* processInfo, pmon::util::metrics::FrameData const& p, pmon::util::metrics::FrameMetrics const& metrics); // MainThread.cpp: void ExitMainThread(); From e0d43761a0e5f29f72dce07057e2d717603aee08 Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 19 Dec 2025 09:22:19 +0900 Subject: [PATCH 068/205] tracking metric usage --- .../Interprocess/source/act/ActionHelper.h | 1 + .../InterimBroadcasterTests.cpp | 8 ++- .../ActionExecutionContext.cpp | 13 +++++ .../ActionExecutionContext.h | 58 ++++++++++++++++++- .../PresentMonService/AllActions.h | 1 + .../PresentMonService/PresentMon.cpp | 3 +- .../PresentMonService/PresentMon.h | 14 +++++ .../PresentMonService.vcxproj | 1 + .../PresentMonService.vcxproj.filters | 1 + .../PresentMonService/acts/ReportMetricUse.h | 49 ++++++++++++++++ 10 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 IntelPresentMon/PresentMonService/acts/ReportMetricUse.h diff --git a/IntelPresentMon/Interprocess/source/act/ActionHelper.h b/IntelPresentMon/Interprocess/source/act/ActionHelper.h index 711d8eb5..bd4a1594 100644 --- a/IntelPresentMon/Interprocess/source/act/ActionHelper.h +++ b/IntelPresentMon/Interprocess/source/act/ActionHelper.h @@ -5,6 +5,7 @@ #include #include #include +#include #ifndef PM_ASYNC_ACTION_CUSTOM_REG_ #define PM_ASYNC_ACTION_CUSTOM_REG_(name) diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 0ccb1906..be0fc439 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -754,6 +754,9 @@ namespace InterimBroadcasterTests Assert::IsTrue(range2.first <= range1.second); Assert::IsTrue(range3.first <= range2.second); Assert::AreEqual(1905ull, range3.second); + // known issue with PresentData is that it sometimes outputs 24 rogue frames at + // the end for P00; we can ignore these for the time being, issue added to board + Assert::IsTrue(range3.second == 1905ull || range3.second == 1929ull); } }; @@ -917,7 +920,10 @@ namespace InterimBroadcasterTests frameFile << r.timestamp << ',' << r.timeInPresent << "\n"; } - Assert::AreEqual(1903u, count1 + count2 + count3); + const auto total = count1 + count2 + count3; + // known issue with PresentData is that it sometimes outputs 24 rogue frames at + // the end for P00; we can ignore these for the time being, issue added to board + Assert::IsTrue(total == 1903u || total == 1927u); } }; } diff --git a/IntelPresentMon/PresentMonService/ActionExecutionContext.cpp b/IntelPresentMon/PresentMonService/ActionExecutionContext.cpp index d25fa2dc..26b3b3af 100644 --- a/IntelPresentMon/PresentMonService/ActionExecutionContext.cpp +++ b/IntelPresentMon/PresentMonService/ActionExecutionContext.cpp @@ -20,6 +20,8 @@ namespace pmon::svc::acts UpdateTelemetryPeriod(); stx.requestedEtwFlushPeriodMs.reset(); UpdateEtwFlushPeriod(); + stx.metricUsage.clear(); + UpdateMetricUsage(); } void ActionExecutionContext::UpdateTelemetryPeriod() const { @@ -45,4 +47,15 @@ namespace pmon::svc::acts throw util::Except(sta); } } + void ActionExecutionContext::UpdateMetricUsage() const + { + std::unordered_set deviceMetricUsage; + auto&& allUsageSets = util::rng::MemberSlice(*pSessionMap, &SessionContextType::metricUsage); + for (auto&& clientUsageSet : allUsageSets) { + for (auto&& usage : clientUsageSet) { + deviceMetricUsage.insert(usage.deviceId); + } + } + pPmon->SetDeviceMetricUsage(std::move(deviceMetricUsage)); + } } \ No newline at end of file diff --git a/IntelPresentMon/PresentMonService/ActionExecutionContext.h b/IntelPresentMon/PresentMonService/ActionExecutionContext.h index bb099be6..0637068c 100644 --- a/IntelPresentMon/PresentMonService/ActionExecutionContext.h +++ b/IntelPresentMon/PresentMonService/ActionExecutionContext.h @@ -1,28 +1,77 @@ #pragma once #include "../Interprocess/source/act/SymmetricActionConnector.h" #include "../Interprocess/source/ShmNamer.h" +#include "../CommonUtilities/Hash.h" #include #include #include +#include #include #include #include #include +#include +#include +#include + #include "PresentMon.h" #include "Service.h" #include "FrameBroadcaster.h" - namespace pmon::svc::acts { struct ActionExecutionContext; + struct MetricUse + { + PM_METRIC metricId; + uint32_t deviceId; + uint32_t arrayIdx; + + template + void serialize(A& ar) + { + ar(metricId, deviceId, arrayIdx); + } + + bool operator==(const MetricUse& rhs) const + { + return metricId == rhs.metricId && + deviceId == rhs.deviceId && + arrayIdx == rhs.arrayIdx; + } + }; +} + +// Must be visible before any std::unordered_set instantiation +namespace std +{ + template<> + struct hash + { + size_t operator()(const pmon::svc::acts::MetricUse& mu) const noexcept + { + const uint64_t devIdx = + (uint64_t(mu.deviceId) << 32) | uint64_t(mu.arrayIdx); + + // Avoid depending on std::hash existing: + using Under = std::underlying_type_t; + const Under mid = static_cast(mu.metricId); + + return pmon::util::hash::DualHash(mid, devIdx); + } + }; +} + +namespace pmon::svc::acts +{ struct ActionSessionContext { // common session context items std::unique_ptr> pConn; uint32_t remotePid = 0; uint32_t nextCommandToken = 0; + // custom items std::map> trackedPids; std::set etwLogSessionIds; @@ -30,11 +79,11 @@ namespace pmon::svc::acts std::optional requestedTelemetryPeriodMs; std::optional requestedEtwFlushPeriodMs; std::string clientBuildId; + std::unordered_set metricUsage; }; struct ActionExecutionContext { - // types using SessionContextType = ActionSessionContext; // data @@ -45,7 +94,10 @@ namespace pmon::svc::acts // functions void Dispose(SessionContextType& stx); + + // TODO: refactor so that these functions need not be const void UpdateTelemetryPeriod() const; void UpdateEtwFlushPeriod() const; + void UpdateMetricUsage() const; }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonService/AllActions.h b/IntelPresentMon/PresentMonService/AllActions.h index 912a3baa..49b90922 100644 --- a/IntelPresentMon/PresentMonService/AllActions.h +++ b/IntelPresentMon/PresentMonService/AllActions.h @@ -4,6 +4,7 @@ #include "acts/FinishEtlLogging.h" #include "acts/GetStaticCpuMetrics.h" #include "acts/OpenSession.h" +#include "acts/ReportMetricUse.h" #include "acts/SelectAdapter.h" #include "acts/SetEtwFlushPeriod.h" #include "acts/SetTelemetryPeriod.h" diff --git a/IntelPresentMon/PresentMonService/PresentMon.cpp b/IntelPresentMon/PresentMonService/PresentMon.cpp index 30121d6e..46ff1826 100644 --- a/IntelPresentMon/PresentMonService/PresentMon.cpp +++ b/IntelPresentMon/PresentMonService/PresentMon.cpp @@ -12,7 +12,8 @@ #include #include "../CommonUtilities/win/Privileges.h" -PresentMon::PresentMon(svc::FrameBroadcaster& broadcaster, bool isRealtime) +PresentMon::PresentMon(svc::FrameBroadcaster& broadcaster, + bool isRealtime) : broadcaster_{ broadcaster }, etwLogger_{ util::win::WeAreElevated() } diff --git a/IntelPresentMon/PresentMonService/PresentMon.h b/IntelPresentMon/PresentMonService/PresentMon.h index a72eff47..309011ba 100644 --- a/IntelPresentMon/PresentMonService/PresentMon.h +++ b/IntelPresentMon/PresentMonService/PresentMon.h @@ -6,6 +6,8 @@ #include "FrameBroadcaster.h" #include #include +#include +#include using namespace pmon; @@ -86,10 +88,22 @@ class PresentMon { return broadcaster_; } + bool CheckDeviceMetricUsage(uint32_t deviceId) const + { + std::shared_lock lk{ metricDeviceUsageMtx_ }; + return metricDeviceUsage_.contains(deviceId); + } + void SetDeviceMetricUsage(std::unordered_set usage) + { + std::lock_guard lk{ metricDeviceUsageMtx_ }; + metricDeviceUsage_ = std::move(usage); + } void StartPlayback(); void StopPlayback(); private: svc::FrameBroadcaster& broadcaster_; svc::EtwLogger etwLogger_; std::unique_ptr pSession_; + mutable std::shared_mutex metricDeviceUsageMtx_; + std::unordered_set metricDeviceUsage_; }; \ No newline at end of file diff --git a/IntelPresentMon/PresentMonService/PresentMonService.vcxproj b/IntelPresentMon/PresentMonService/PresentMonService.vcxproj index e4e47e96..c5bf29da 100644 --- a/IntelPresentMon/PresentMonService/PresentMonService.vcxproj +++ b/IntelPresentMon/PresentMonService/PresentMonService.vcxproj @@ -134,6 +134,7 @@ + diff --git a/IntelPresentMon/PresentMonService/PresentMonService.vcxproj.filters b/IntelPresentMon/PresentMonService/PresentMonService.vcxproj.filters index e862f1d1..b500859e 100644 --- a/IntelPresentMon/PresentMonService/PresentMonService.vcxproj.filters +++ b/IntelPresentMon/PresentMonService/PresentMonService.vcxproj.filters @@ -50,6 +50,7 @@ + diff --git a/IntelPresentMon/PresentMonService/acts/ReportMetricUse.h b/IntelPresentMon/PresentMonService/acts/ReportMetricUse.h new file mode 100644 index 00000000..b7a3608b --- /dev/null +++ b/IntelPresentMon/PresentMonService/acts/ReportMetricUse.h @@ -0,0 +1,49 @@ +#pragma once +#include "../../Interprocess/source/act/ActionHelper.h" +#include + +#define ACT_NAME ReportMetricUse +#define ACT_EXEC_CTX ActionExecutionContext +#define ACT_NS ::pmon::svc::acts +#define ACT_TYPE AsyncActionBase_ + +namespace pmon::svc::acts +{ + using namespace ipc::act; + + class ACT_NAME : public ACT_TYPE + { + public: + static constexpr const char* Identifier = STRINGIFY(ACT_NAME); + struct Params + { + std::unordered_set metricUsage; + template void serialize(A& ar) { + ar(metricUsage); + } + }; + struct Response + { + template void serialize(A& ar) {} + }; + private: + friend class ACT_TYPE; + static Response Execute_(const ACT_EXEC_CTX& ctx, SessionContext& stx, Params&& in) + { + stx.metricUsage = std::move(in.metricUsage); + ctx.UpdateMetricUsage(); + return {}; + } + }; + +#ifdef PM_ASYNC_ACTION_REGISTRATION_ + ACTION_REG(); +#endif +} + +ACTION_TRAITS_DEF(); + +#undef ACT_NAME +#undef ACT_EXEC_CTX +#undef ACT_NS +#undef ACT_TYPE \ No newline at end of file From c56912f253985828d44b705e3bcc3e84cb238ab1 Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 22 Dec 2025 13:22:23 +0900 Subject: [PATCH 069/205] changing system metrics to special device 65536 --- .../AppCef/ipm-ui-vue/src/core/metric.ts | 4 +- .../ipm-ui-vue/src/stores/preferences.ts | 21 +- IntelPresentMon/CommonUtilities/log/Verbose.h | 1 + .../Interprocess/source/Interprocess.cpp | 4 +- .../source/IntrospectionPopulators.cpp | 7 +- .../source/IntrospectionPopulators.h | 1 + .../KernelProcess/KernelProcess.args.json | 2 +- .../KernelProcess/kact/Introspect.h | 194 +++++++++++------ .../KernelProcess/kact/PushSpecification.h | 197 ++++++++++++++---- 9 files changed, 312 insertions(+), 119 deletions(-) diff --git a/IntelPresentMon/AppCef/ipm-ui-vue/src/core/metric.ts b/IntelPresentMon/AppCef/ipm-ui-vue/src/core/metric.ts index 2135a1b4..fe588c30 100644 --- a/IntelPresentMon/AppCef/ipm-ui-vue/src/core/metric.ts +++ b/IntelPresentMon/AppCef/ipm-ui-vue/src/core/metric.ts @@ -1,5 +1,7 @@ // Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT +export const SYSTEM_DEVICE_ID = 65536; + export interface Metric { id: number, name: string, @@ -12,4 +14,4 @@ export interface Metric { // not used yet in frontend -> backend fills in selected adapter id IF metric is not universal availableDeviceIds: number[], numeric: boolean, -} \ No newline at end of file +} diff --git a/IntelPresentMon/AppCef/ipm-ui-vue/src/stores/preferences.ts b/IntelPresentMon/AppCef/ipm-ui-vue/src/stores/preferences.ts index 1a98033c..a8a82a26 100644 --- a/IntelPresentMon/AppCef/ipm-ui-vue/src/stores/preferences.ts +++ b/IntelPresentMon/AppCef/ipm-ui-vue/src/stores/preferences.ts @@ -14,6 +14,7 @@ import { useLoadoutStore } from './loadout'; import { useIntrospectionStore } from './introspection'; import { deepToRaw } from '@/core/vue-utils'; import { useNotificationsStore } from './notifications'; +import { SYSTEM_DEVICE_ID } from '@/core/metric'; export const usePreferencesStore = defineStore('preferences', () => { // === Dependent Stores === @@ -128,13 +129,19 @@ export const usePreferencesStore = defineStore('preferences', () => { widgetMetric.metric.deviceId = 0; // establish universal device id // Check whether metric is a gpu metric, then we need non-universal device id if (!metric.availableDeviceIds.includes(0)) { - // if no specific adapter id set, assume adapter id = 1 is active - const adapterId = preferences.value.adapterId !== null ? preferences.value.adapterId : 1; - // Set adapter id for this query element to the active one if available - if (metric.availableDeviceIds.includes(adapterId)) { - widgetMetric.metric.deviceId = adapterId; - } else { // if active adapter id is not available drop this widgetMetric - return false; + // if this is a system metric, deviceId needs to be set to fixed id + if (metric.availableDeviceIds.includes(SYSTEM_DEVICE_ID)) { + widgetMetric.metric.deviceId = SYSTEM_DEVICE_ID; + } + else { + // if no specific adapter id set, assume adapter id = 1 is active + const adapterId = preferences.value.adapterId !== null ? preferences.value.adapterId : 1; + // Set adapter id for this query element to the active one if available + if (metric.availableDeviceIds.includes(adapterId)) { + widgetMetric.metric.deviceId = adapterId; + } else { // if active adapter id is not available drop this widgetMetric + return false; + } } } // Fill out the unit diff --git a/IntelPresentMon/CommonUtilities/log/Verbose.h b/IntelPresentMon/CommonUtilities/log/Verbose.h index e472423f..63f3a0cc 100644 --- a/IntelPresentMon/CommonUtilities/log/Verbose.h +++ b/IntelPresentMon/CommonUtilities/log/Verbose.h @@ -13,6 +13,7 @@ namespace pmon::util::log core_hotkey, core_window, etwq, + kact, Count }; diff --git a/IntelPresentMon/Interprocess/source/Interprocess.cpp b/IntelPresentMon/Interprocess/source/Interprocess.cpp index b585dfbb..3846805d 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.cpp +++ b/IntelPresentMon/Interprocess/source/Interprocess.cpp @@ -354,8 +354,8 @@ namespace pmon::ipc } std::vector ids; for (auto& p : result.first->GetDevices()) { - // skip the 0 id - if (auto id = p->GetId()) { + // GPU device IDs live in the range (0, kSystemDeviceId) + if (auto id = p->GetId(); id > 0 && id < intro::kSystemDeviceId) { ids.push_back(id); } } diff --git a/IntelPresentMon/Interprocess/source/IntrospectionPopulators.cpp b/IntelPresentMon/Interprocess/source/IntrospectionPopulators.cpp index da4c26eb..cc8beb4d 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionPopulators.cpp +++ b/IntelPresentMon/Interprocess/source/IntrospectionPopulators.cpp @@ -3,6 +3,7 @@ #include "IntrospectionTransfer.h" #include "IntrospectionCapsLookup.h" #include "MetricCapabilities.h" +#include "IntrospectionPopulators.h" #include "../../CommonUtilities/log/Log.h" #include #include @@ -109,7 +110,11 @@ namespace pmon::ipc::intro void PopulateCpu(ShmSegmentManager* pSegmentManager, IntrospectionRoot& root, PM_DEVICE_VENDOR vendor, const std::string& deviceName, const MetricCapabilities& caps) { - PopulateDeviceMetrics_(root, caps, 0); + // add the device + auto charAlloc = pSegmentManager->get_allocator(); + root.AddDevice(ShmMakeUnique(pSegmentManager, kSystemDeviceId, + PM_DEVICE_TYPE_SYSTEM, vendor, ShmString{ "System", charAlloc})); + PopulateDeviceMetrics_(root, caps, kSystemDeviceId); } } \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/source/IntrospectionPopulators.h b/IntelPresentMon/Interprocess/source/IntrospectionPopulators.h index 56ac7487..972801d4 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionPopulators.h +++ b/IntelPresentMon/Interprocess/source/IntrospectionPopulators.h @@ -7,6 +7,7 @@ namespace pmon::ipc::intro { + constexpr uint32_t kSystemDeviceId = 0x1'0000; void PopulateEnums(ShmSegmentManager* pSegmentManager, struct IntrospectionRoot& root); void PopulateMetrics(ShmSegmentManager* pSegmentManager, struct IntrospectionRoot& root); void PopulateUnits(ShmSegmentManager* pSegmentManager, struct IntrospectionRoot& root); diff --git a/IntelPresentMon/KernelProcess/KernelProcess.args.json b/IntelPresentMon/KernelProcess/KernelProcess.args.json index 09312be6..7ff821c5 100644 --- a/IntelPresentMon/KernelProcess/KernelProcess.args.json +++ b/IntelPresentMon/KernelProcess/KernelProcess.args.json @@ -34,7 +34,7 @@ }, { "Id": "49b6fc1f-b969-4280-b0d8-17a213e80f16", - "Command": "--log-verbose-modules core_window v8async etwq" + "Command": "--log-verbose-modules v8async core_metric kact" }, { "Id": "bab48d6d-3a48-4b3b-9ed9-903e381824c6", diff --git a/IntelPresentMon/KernelProcess/kact/Introspect.h b/IntelPresentMon/KernelProcess/kact/Introspect.h index 0b8cb07d..c18da60b 100644 --- a/IntelPresentMon/KernelProcess/kact/Introspect.h +++ b/IntelPresentMon/KernelProcess/kact/Introspect.h @@ -7,95 +7,133 @@ #include #include +// cereal JSON dump + NVP macro +#include +#include +#include +#include + #define ACT_NAME Introspect #define ACT_EXEC_CTX KernelExecutionContext #define ACT_TYPE AsyncActionBase_ #define ACT_NS kproc::kact - namespace ACT_NS { - using namespace ::pmon::ipc::act; - - class ACT_NAME : public ACT_TYPE - { - public: - static constexpr const char* Identifier = STRINGIFY(ACT_NAME); - struct Params - { - template void serialize(A& ar) { - } - }; - struct Metric { - PM_METRIC id; - std::string name; - std::string description; - std::vector availableDeviceIds; - PM_UNIT preferredUnitId; - int arraySize; - std::vector availableStatIds; - bool numeric; - - template void serialize(A& ar) { - ar(id, name, description, availableDeviceIds, preferredUnitId, - arraySize, availableStatIds, numeric); - } - }; - struct Stat { - PM_STAT id; - std::string name; - std::string shortName; - std::string description; - - template void serialize(A& ar) { - ar(id, name, shortName, description); - } - }; - struct Unit { - template void serialize(A& ar) { - } - }; - struct Response { + using namespace ::pmon::ipc::act; + + class ACT_NAME : public ACT_TYPE + { + public: + static constexpr const char* Identifier = STRINGIFY(ACT_NAME); + + struct Params + { + template void serialize(A& ar) { + // no params + } + }; + + struct Metric + { + PM_METRIC id; + std::string name; + std::string description; + std::vector availableDeviceIds; + PM_UNIT preferredUnitId; + int arraySize; + std::vector availableStatIds; + bool numeric; + + template void serialize(A& ar) { + ar(CEREAL_NVP(id), + CEREAL_NVP(name), + CEREAL_NVP(description), + CEREAL_NVP(availableDeviceIds), + CEREAL_NVP(preferredUnitId), + CEREAL_NVP(arraySize), + CEREAL_NVP(availableStatIds), + CEREAL_NVP(numeric)); + } + }; + + struct Stat + { + PM_STAT id; + std::string name; + std::string shortName; + std::string description; + + template void serialize(A& ar) { + ar(CEREAL_NVP(id), + CEREAL_NVP(name), + CEREAL_NVP(shortName), + CEREAL_NVP(description)); + } + }; + + struct Unit + { + template void serialize(A& ar) { + // TODO: populate when units are implemented + } + }; + + struct Response + { std::vector metrics; std::vector stats; std::vector units; - template void serialize(A& ar) { - ar(metrics, stats, units); - } - }; - private: - friend class ACT_TYPE; - static Response Execute_(const ACT_EXEC_CTX& ctx, SessionContext& stx, Params&& in) - { + template void serialize(A& ar) { + ar(CEREAL_NVP(metrics), + CEREAL_NVP(stats), + CEREAL_NVP(units)); + } + }; + + private: + friend class ACT_TYPE; + + static Response Execute_(const ACT_EXEC_CTX& ctx, SessionContext& stx, Params&& in) + { using namespace std::string_literals; namespace vi = std::views; namespace rn = std::ranges; + using v = pmon::util::log::V; auto& intro = (*ctx.ppKernel)->GetIntrospectionRoot(); - // --- metrics --- + // --- metrics --- // set of types that are numeric, used to generate numeric flag that the frontend uses const std::array numericTypes{ PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_UINT32, PM_DATA_TYPE_INT32, PM_DATA_TYPE_UINT64 }; + // filter predicate to only pick up metrics usable in dynamic queries (plus hardcoded blacklist) - const auto filterPred = [](const pmapi::intro::MetricView& m) { const auto type = m.GetType(); - return - ( m.GetId() != PM_METRIC_GPU_LATENCY) - && - ( type == PM_METRIC_TYPE_DYNAMIC || - type == PM_METRIC_TYPE_DYNAMIC_FRAME || - type == PM_METRIC_TYPE_STATIC); + const auto filterPred = [](const pmapi::intro::MetricView& m) { + const auto type = m.GetType(); + return + (m.GetId() != PM_METRIC_GPU_LATENCY) + && + ( + type == PM_METRIC_TYPE_DYNAMIC || + type == PM_METRIC_TYPE_DYNAMIC_FRAME || + type == PM_METRIC_TYPE_STATIC + ); }; + // generate the response Response res; + // reserve space for the actual number of metrics res.metrics.reserve(rn::distance(intro.GetMetrics() | vi::filter(filterPred))); + // now process each applicable metric, filtering ones not usable for dynamic queries for (auto&& [i, m] : intro.GetMetrics() | vi::filter(filterPred) | vi::enumerate) { // array size: stopgap measure to use largest among all available devices // will replace this with per-device size when loadout per-line device selection // and per-line array index selection is implemented int arraySize = 0; + // generate device list std::vector devices; for (auto&& d : m.GetDeviceMetricInfo()) { @@ -103,10 +141,12 @@ namespace ACT_NS arraySize = std::max(arraySize, (int)d.GetArraySize()); devices.push_back(d.GetDevice().GetId()); } + // generate stat list auto stats = m.GetStatInfo() - | vi::transform([](auto&& s) {return s.GetStat(); }) + | vi::transform([](auto&& s) { return s.GetStat(); }) | rn::to(); + // add metric res.metrics.push_back(Metric{ .id = m.GetId(), @@ -118,8 +158,9 @@ namespace ACT_NS .arraySize = arraySize, .availableStatIds = std::move(stats), .numeric = rn::contains(numericTypes, m.GetDataTypeInfo().GetPolledType()), - }); + }); } + // --- stats --- auto&& statRange = intro.FindEnum(PM_ENUM_STAT).GetKeys(); for (auto&& s : statRange) { @@ -128,14 +169,37 @@ namespace ACT_NS .name = s.GetName(), .shortName = s.GetShortName(), .description = s.GetDescription(), - }); + }); + } + + // --- dump response as JSON to log (best-effort; never fail the action due to logging) --- + std::string responseJson; + try + { + std::ostringstream os; + + // Pretty JSON by default; use NoIndent() if you want compact output. + auto opts = cereal::JSONOutputArchive::Options::Default(); + cereal::JSONOutputArchive ar(os, opts); + + // Use macro-driven naming for the root object too + auto& introspect = res; + ar(CEREAL_NVP(introspect)); + + responseJson = os.str(); } + catch (const std::exception& e) + { + responseJson = std::string("Introspect JSON serialization failed: ") + e.what(); + } + + pmlog_verb(v::kact)("Introspect action").pmwatch(responseJson); return res; - } - }; + } + }; - ACTION_REG(); + ACTION_REG(); } ACTION_TRAITS_DEF(); @@ -143,4 +207,4 @@ ACTION_TRAITS_DEF(); #undef ACT_NAME #undef ACT_EXEC_CTX #undef ACT_NS -#undef ACT_TYPE \ No newline at end of file +#undef ACT_TYPE diff --git a/IntelPresentMon/KernelProcess/kact/PushSpecification.h b/IntelPresentMon/KernelProcess/kact/PushSpecification.h index 83a00973..aef681e8 100644 --- a/IntelPresentMon/KernelProcess/kact/PushSpecification.h +++ b/IntelPresentMon/KernelProcess/kact/PushSpecification.h @@ -9,6 +9,14 @@ #include #include +// cereal JSON dump + NVP macro +#include +#include +#include +#include +#include +#include + #define ACT_NAME PushSpecification #define ACT_EXEC_CTX KernelExecutionContext #define ACT_TYPE AsyncActionBase_ @@ -16,9 +24,9 @@ namespace ACT_NS { - using namespace ::pmon::ipc::act; + using namespace ::pmon::ipc::act; using ::p2c::gfx::Color; - + namespace push_spec_impl { struct Metric @@ -30,7 +38,11 @@ namespace ACT_NS PM_UNIT desiredUnitId; template void serialize(A& ar) { - ar(metricId, arrayIndex, deviceId, statId, desiredUnitId); + ar(CEREAL_NVP(metricId), + CEREAL_NVP(arrayIndex), + CEREAL_NVP(deviceId), + CEREAL_NVP(statId), + CEREAL_NVP(desiredUnitId)); } }; @@ -42,19 +54,25 @@ namespace ACT_NS ::p2c::gfx::lay::AxisAffinity axisAffinity; template void serialize(A& ar) { - ar(metric, lineColor, fillColor, axisAffinity); + ar(CEREAL_NVP(metric), + CEREAL_NVP(lineColor), + CEREAL_NVP(fillColor), + CEREAL_NVP(axisAffinity)); } }; // Graph widget type. - struct Graph { + struct Graph + { std::vector metrics; uint32_t height; uint32_t vDivs; uint32_t hDivs; bool showBottomAxis; - struct GraphType { + + struct GraphType + { std::string name; std::array range; std::array rangeRight; @@ -65,9 +83,17 @@ namespace ACT_NS bool autoCount; template void serialize(A& ar) { - ar(name, range, rangeRight, binCount, countRange, autoLeft, autoRight, autoCount); + ar(CEREAL_NVP(name), + CEREAL_NVP(range), + CEREAL_NVP(rangeRight), + CEREAL_NVP(binCount), + CEREAL_NVP(countRange), + CEREAL_NVP(autoLeft), + CEREAL_NVP(autoRight), + CEREAL_NVP(autoCount)); } } graphType; + Color gridColor; Color dividerColor; Color backgroundColor; @@ -76,12 +102,23 @@ namespace ACT_NS float textSize; template void serialize(A& ar) { - ar(metrics, height, vDivs, hDivs, showBottomAxis, graphType, - gridColor, dividerColor, backgroundColor, borderColor, textColor, textSize); + ar(CEREAL_NVP(metrics), + CEREAL_NVP(height), + CEREAL_NVP(vDivs), + CEREAL_NVP(hDivs), + CEREAL_NVP(showBottomAxis), + CEREAL_NVP(graphType), + CEREAL_NVP(gridColor), + CEREAL_NVP(dividerColor), + CEREAL_NVP(backgroundColor), + CEREAL_NVP(borderColor), + CEREAL_NVP(textColor), + CEREAL_NVP(textSize)); } }; - struct Readout { + struct Readout + { std::vector metrics; bool showLabel; @@ -90,16 +127,22 @@ namespace ACT_NS Color backgroundColor; template void serialize(A& ar) { - ar(metrics, showLabel, fontSize, fontColor, backgroundColor); + ar(CEREAL_NVP(metrics), + CEREAL_NVP(showLabel), + CEREAL_NVP(fontSize), + CEREAL_NVP(fontColor), + CEREAL_NVP(backgroundColor)); } }; using Widget = std::variant; - struct Params { + struct Params + { std::optional pid; - struct Preferences { + struct Preferences + { std::string capturePath; uint32_t captureDelay; bool enableCaptureDelay; @@ -126,11 +169,14 @@ namespace ACT_NS Color overlayBorderColor; Color overlayBackgroundColor; - struct GraphFont { + struct GraphFont + { std::string name; float axisSize; + template void serialize(A& ar) { - ar(name, axisSize); + ar(CEREAL_NVP(name), + CEREAL_NVP(axisSize)); } } graphFont; @@ -155,18 +201,50 @@ namespace ACT_NS float flashInjectionBackgroundSize; template void serialize(A& ar) { - ar(capturePath, captureDelay, enableCaptureDelay, - captureDuration, enableCaptureDuration, hideDuringCapture, hideAlways, - independentWindow, metricPollRate, overlayDrawRate, telemetrySamplingPeriodMs, - etwFlushPeriod, manualEtwFlush, metricsOffset, metricsWindow, overlayPosition, - timeRange, overlayMargin, overlayBorder, overlayPadding, graphMargin, - graphBorder, graphPadding, overlayBorderColor, overlayBackgroundColor, - graphFont, overlayWidth, upscale, generateStats, enableTargetBlocklist, - enableAutotargetting, upscaleFactor, adapterId, enableFlashInjection, - flashInjectionSize, flashInjectionEnableTargetOverride, flashInjectionTargetOverride, - flashInjectionColor, flashInjectionBackgroundEnable, flashInjectionBackgroundColor, - flashInjectionRightShift, flashInjectionFlashDuration, flashInjectionUseRainbow, - flashInjectionBackgroundSize); + ar(CEREAL_NVP(capturePath), + CEREAL_NVP(captureDelay), + CEREAL_NVP(enableCaptureDelay), + CEREAL_NVP(captureDuration), + CEREAL_NVP(enableCaptureDuration), + CEREAL_NVP(hideDuringCapture), + CEREAL_NVP(hideAlways), + CEREAL_NVP(independentWindow), + CEREAL_NVP(metricPollRate), + CEREAL_NVP(overlayDrawRate), + CEREAL_NVP(telemetrySamplingPeriodMs), + CEREAL_NVP(etwFlushPeriod), + CEREAL_NVP(manualEtwFlush), + CEREAL_NVP(metricsOffset), + CEREAL_NVP(metricsWindow), + CEREAL_NVP(overlayPosition), + CEREAL_NVP(timeRange), + CEREAL_NVP(overlayMargin), + CEREAL_NVP(overlayBorder), + CEREAL_NVP(overlayPadding), + CEREAL_NVP(graphMargin), + CEREAL_NVP(graphBorder), + CEREAL_NVP(graphPadding), + CEREAL_NVP(overlayBorderColor), + CEREAL_NVP(overlayBackgroundColor), + CEREAL_NVP(graphFont), + CEREAL_NVP(overlayWidth), + CEREAL_NVP(upscale), + CEREAL_NVP(generateStats), + CEREAL_NVP(enableTargetBlocklist), + CEREAL_NVP(enableAutotargetting), + CEREAL_NVP(upscaleFactor), + CEREAL_NVP(adapterId), + CEREAL_NVP(enableFlashInjection), + CEREAL_NVP(flashInjectionEnableTargetOverride), + CEREAL_NVP(flashInjectionTargetOverride), + CEREAL_NVP(flashInjectionSize), + CEREAL_NVP(flashInjectionColor), + CEREAL_NVP(flashInjectionBackgroundEnable), + CEREAL_NVP(flashInjectionBackgroundColor), + CEREAL_NVP(flashInjectionRightShift), + CEREAL_NVP(flashInjectionFlashDuration), + CEREAL_NVP(flashInjectionUseRainbow), + CEREAL_NVP(flashInjectionBackgroundSize)); } } preferences; @@ -174,22 +252,28 @@ namespace ACT_NS std::vector widgets; template void serialize(A& ar) { - ar(pid, preferences, widgets); + ar(CEREAL_NVP(pid), + CEREAL_NVP(preferences), + CEREAL_NVP(widgets)); } }; } - class ACT_NAME : public ACT_TYPE - { - public: - static constexpr const char* Identifier = STRINGIFY(ACT_NAME); + class ACT_NAME : public ACT_TYPE + { + public: + static constexpr const char* Identifier = STRINGIFY(ACT_NAME); using Params = push_spec_impl::Params; - struct Response { - template void serialize(A& ar) { - } - }; - private: - friend class ACT_TYPE; + + struct Response { + template void serialize(A& ar) { + // no response fields + } + }; + + private: + friend class ACT_TYPE; + static Response Execute_(const ACT_EXEC_CTX& ctx, SessionContext& stx, Params&& in) { const GfxLayer::Extension::OverlayConfig cfg{ @@ -202,20 +286,46 @@ namespace ACT_NS .UseRainbow = in.preferences.flashInjectionUseRainbow, .BackgroundSize = in.preferences.flashInjectionBackgroundSize, }; + const auto flashTgtOverride = in.preferences.flashInjectionEnableTargetOverride ? std::optional{ in.preferences.flashInjectionTargetOverride } : std::nullopt; + (*ctx.ppKernel)->UpdateInjection(in.preferences.enableFlashInjection, in.pid, flashTgtOverride, cfg); + if (!in.pid) { (*ctx.ppKernel)->ClearOverlay(); } else { (*ctx.ppKernel)->PushSpec(MakeOverlaySpec(in)); } + + // --- dump params as JSON to log (best-effort; never fail the action due to logging) --- + // (No useful response fields; logging the request is typically what you want here.) + std::string paramsJson; + try + { + std::ostringstream os; + auto opts = cereal::JSONOutputArchive::Options::Default(); + cereal::JSONOutputArchive ar(os, opts); + + auto& pushSpecification = in; + ar(CEREAL_NVP(pushSpecification)); + + paramsJson = os.str(); + } + catch (const std::exception& e) + { + paramsJson = std::string("PushSpecification JSON serialization failed: ") + e.what(); + } + + using v = pmon::util::log::V; + pmlog_verb(v::kact)("PushSpecification action").pmwatch(paramsJson); + return {}; } - }; + }; - ACTION_REG(); + ACTION_REG(); } namespace cereal @@ -223,7 +333,10 @@ namespace cereal template void serialize(Archive& archive, p2c::gfx::Color& s) { - archive(s.r, s.g, s.b, s.a); + archive(CEREAL_NVP(s.r), + CEREAL_NVP(s.g), + CEREAL_NVP(s.b), + CEREAL_NVP(s.a)); } } @@ -232,4 +345,4 @@ ACTION_TRAITS_DEF(); #undef ACT_NAME #undef ACT_EXEC_CTX #undef ACT_NS -#undef ACT_TYPE \ No newline at end of file +#undef ACT_TYPE From 63a7b0053d0fa737d3b6bf40bf925b784b36e62c Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 22 Dec 2025 14:34:09 +0900 Subject: [PATCH 070/205] support system device in ipm client --- IntelPresentMon/CommonUtilities/log/Level.cpp | 4 ++-- IntelPresentMon/CommonUtilities/log/Verbose.cpp | 2 +- IntelPresentMon/Core/source/cli/CliOptions.h | 2 +- IntelPresentMon/Core/source/pmon/RawFrameDataMetricList.h | 8 ++++---- .../Interprocess/source/IntrospectionPopulators.h | 2 +- IntelPresentMon/KernelProcess/KernelProcess.args.json | 4 ++++ IntelPresentMon/KernelProcess/winmain.cpp | 8 +++++--- IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp | 3 ++- .../PresentMonService/acts/EnumerateAdapters.h | 1 + 9 files changed, 21 insertions(+), 13 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/log/Level.cpp b/IntelPresentMon/CommonUtilities/log/Level.cpp index 033c2be4..ac6f4f9a 100644 --- a/IntelPresentMon/CommonUtilities/log/Level.cpp +++ b/IntelPresentMon/CommonUtilities/log/Level.cpp @@ -8,7 +8,7 @@ namespace pmon::util::log { std::string GetLevelName(Level lv) noexcept { - return std::string{ reflect::enum_name(lv)}; + return std::string{ reflect::enum_name(lv) }; } std::map GetLevelMapNarrow() noexcept @@ -18,7 +18,7 @@ namespace pmon::util::log for (int n = 0; n < (int)Level::EndOfEnumKeys; n++) { const auto lvl = Level(n); auto key = ToLower(GetLevelName(lvl)); - if (key != "Unknown") { + if (!key.starts_with("level")) { map[std::move(key)] = lvl; } } diff --git a/IntelPresentMon/CommonUtilities/log/Verbose.cpp b/IntelPresentMon/CommonUtilities/log/Verbose.cpp index 6f2dab0b..6aa0413a 100644 --- a/IntelPresentMon/CommonUtilities/log/Verbose.cpp +++ b/IntelPresentMon/CommonUtilities/log/Verbose.cpp @@ -6,7 +6,7 @@ namespace pmon::util::log { std::string GetVerboseModuleName(V mod) noexcept { - return std::string{ reflect::enum_name(mod) }; + return std::string{ reflect::enum_name(mod) }; } std::map GetVerboseModuleMapNarrow() noexcept diff --git a/IntelPresentMon/Core/source/cli/CliOptions.h b/IntelPresentMon/Core/source/cli/CliOptions.h index fe49d82c..48cd3e24 100644 --- a/IntelPresentMon/Core/source/cli/CliOptions.h +++ b/IntelPresentMon/Core/source/cli/CliOptions.h @@ -60,7 +60,7 @@ namespace p2c::cli private: Group glists_{ this, "Standard", "Standard options for the list subcommand" }; public: Flag listMetrics{ this, "--metrics,-m", "Output a list of available metrics" }; Flag listMetricsStats{ this, "--stats,-s", "Output a list of available stats for each metric" }; - Flag listDevices{ this, "--devices,-d", "Output a list of available graphics adapters" }; + Flag listDevices{ this, "--devices,-d", "Output a list of available devices" }; Flag listFilterFrame{ this, "--filter-frame,-f", "Filter to only metrics available for use with frame event capture" }; Flag listFilterDynamic{ this, "--filter-dynamic,-y", "Filter to only metrics available for use with dynamic polling" }; Option listSearch{ this, "--search", {}, "Substring to filter metric results on (case-insensitive)" }; diff --git a/IntelPresentMon/Core/source/pmon/RawFrameDataMetricList.h b/IntelPresentMon/Core/source/pmon/RawFrameDataMetricList.h index 3a668ee3..5fe04d0a 100644 --- a/IntelPresentMon/Core/source/pmon/RawFrameDataMetricList.h +++ b/IntelPresentMon/Core/source/pmon/RawFrameDataMetricList.h @@ -91,10 +91,10 @@ namespace p2c::pmon Element{.metricId = PM_METRIC_GPU_MEM_VOLTAGE_LIMITED, .deviceId = activeDeviceId }, Element{.metricId = PM_METRIC_GPU_MEM_UTILIZATION_LIMITED, .deviceId = activeDeviceId }, - Element{.metricId = PM_METRIC_CPU_UTILIZATION }, - Element{.metricId = PM_METRIC_CPU_POWER }, - Element{.metricId = PM_METRIC_CPU_TEMPERATURE }, - Element{.metricId = PM_METRIC_CPU_FREQUENCY }, + Element{.metricId = PM_METRIC_CPU_UTILIZATION, .deviceId = 65536 }, + Element{.metricId = PM_METRIC_CPU_POWER, .deviceId = 65536 }, + Element{.metricId = PM_METRIC_CPU_TEMPERATURE, .deviceId = 65536 }, + Element{.metricId = PM_METRIC_CPU_FREQUENCY, .deviceId = 65536 }, }; if (enableTimestamp) { diff --git a/IntelPresentMon/Interprocess/source/IntrospectionPopulators.h b/IntelPresentMon/Interprocess/source/IntrospectionPopulators.h index 972801d4..40684db6 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionPopulators.h +++ b/IntelPresentMon/Interprocess/source/IntrospectionPopulators.h @@ -7,7 +7,7 @@ namespace pmon::ipc::intro { - constexpr uint32_t kSystemDeviceId = 0x1'0000; + constexpr uint32_t kSystemDeviceId = 65536; void PopulateEnums(ShmSegmentManager* pSegmentManager, struct IntrospectionRoot& root); void PopulateMetrics(ShmSegmentManager* pSegmentManager, struct IntrospectionRoot& root); void PopulateUnits(ShmSegmentManager* pSegmentManager, struct IntrospectionRoot& root); diff --git a/IntelPresentMon/KernelProcess/KernelProcess.args.json b/IntelPresentMon/KernelProcess/KernelProcess.args.json index 7ff821c5..e496679e 100644 --- a/IntelPresentMon/KernelProcess/KernelProcess.args.json +++ b/IntelPresentMon/KernelProcess/KernelProcess.args.json @@ -116,6 +116,10 @@ "Id": "5217575f-37d8-4f60-a2d2-a85992ce4341", "Command": "--ui-flag enable-chromium-debug" }, + { + "Id": "f4a7ccc2-978f-4bd0-af05-a70ce115b1eb", + "Command": "list --devices" + }, { "Id": "a82f2a21-c9de-460e-8f38-b10026e23809", "Command": "capture", diff --git a/IntelPresentMon/KernelProcess/winmain.cpp b/IntelPresentMon/KernelProcess/winmain.cpp index 48909c3a..0e963ab8 100644 --- a/IntelPresentMon/KernelProcess/winmain.cpp +++ b/IntelPresentMon/KernelProcess/winmain.cpp @@ -310,10 +310,11 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi } // list adapter devices if (opt.listDevices) { - std::cout << "List of graphics adapters:\n"; + std::cout << "List of queryable devices:\n"; for (auto&& d : pIntro->GetDevices()) { - if (!d.GetId()) continue; - std::cout << d.GetName() << " [" << d.GetId() << "] (" << d.IntrospectVendor().GetName() << ")\n"; + std::cout << d.GetName() << " [" << d.GetId() << "] " + << d.IntrospectVendor().GetName() + << " (" << d.IntrospectType().GetName() << ")\n"; } } return 0; @@ -409,6 +410,7 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi // don't exit this process until the CEF control panel exits cefChild.wait(); } + // TODO: organize headless CLI code into own source modules else if (opt.subcCapture.Active()) { DWORD pid; if (opt.capTargetPid) { diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index 877d00d5..b78da514 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -1309,7 +1309,8 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements) // current release: only 1 gpu device maybe be polled at a time for (auto& q : queryElements) { // validate that maximum 1 device (gpu) id is specified throughout the query - if (q.deviceId != 0) { + // universal (0) and system (65536) device metrics are exempt from this limit + if (q.deviceId != 0 && q.deviceId != 65536) { if (!referencedDevice_) { referencedDevice_ = q.deviceId; } diff --git a/IntelPresentMon/PresentMonService/acts/EnumerateAdapters.h b/IntelPresentMon/PresentMonService/acts/EnumerateAdapters.h index ef7d19a3..ec3719be 100644 --- a/IntelPresentMon/PresentMonService/acts/EnumerateAdapters.h +++ b/IntelPresentMon/PresentMonService/acts/EnumerateAdapters.h @@ -15,6 +15,7 @@ namespace pmon::svc::acts namespace rn = std::ranges; namespace vi = rn::views; + // TODO: remove this action, it is redundant in light of Introspection class ACT_NAME : public ACT_TYPE { public: From 376d0969b58bd27493ad7c9843de62963c593907 Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 22 Dec 2025 17:05:39 +0900 Subject: [PATCH 071/205] fixing permissions on gpu segment store --- IntelPresentMon/Interprocess/source/Interprocess.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/IntelPresentMon/Interprocess/source/Interprocess.cpp b/IntelPresentMon/Interprocess/source/Interprocess.cpp index 3846805d..8461b44b 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.cpp +++ b/IntelPresentMon/Interprocess/source/Interprocess.cpp @@ -73,7 +73,8 @@ namespace pmon::ipc auto& gpuShm = gpuShms_.emplace( std::piecewise_construct, std::forward_as_tuple(deviceId), - std::forward_as_tuple(namer_.MakeGpuName(deviceId)) + std::forward_as_tuple(namer_.MakeGpuName(deviceId), + static_cast(Permissions_{})) ).first->second; // populate rings based on caps for (auto&& [m, count] : caps) { From a0ac0d95b2f39569c697fb12d124dfddf543bb9c Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 23 Dec 2025 08:48:24 +0900 Subject: [PATCH 072/205] dedicated sys device and activation for gpu tele includes log proc/thread naming, enhancements on the gpu tele thread, waitevent improvement --- IntelPresentMon/AppCef/source/winmain.cpp | 2 +- IntelPresentMon/CommonUtilities/win/Event.h | 5 +- IntelPresentMon/KernelProcess/winmain.cpp | 8 +- .../PresentMonService/CliOptions.h | 1 + .../PresentMonService/PMMainThread.cpp | 215 ++++++++++-------- .../PresentMonService/PresentMon.h | 13 +- .../RealtimePresentMonSession.cpp | 2 +- .../PresentMonService/ServiceMain.cpp | 3 + 8 files changed, 151 insertions(+), 98 deletions(-) diff --git a/IntelPresentMon/AppCef/source/winmain.cpp b/IntelPresentMon/AppCef/source/winmain.cpp index 46b2bad8..24b2c79a 100644 --- a/IntelPresentMon/AppCef/source/winmain.cpp +++ b/IntelPresentMon/AppCef/source/winmain.cpp @@ -194,7 +194,7 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi } // name this process / thread - log::IdentificationTable::AddThisProcess(opt.cefType.AsOptional().value_or("main-client")); + log::IdentificationTable::AddThisProcess("cef-" + opt.cefType.AsOptional().value_or("mclient")); log::IdentificationTable::AddThisThread("main"); // initialize the logging system diff --git a/IntelPresentMon/CommonUtilities/win/Event.h b/IntelPresentMon/CommonUtilities/win/Event.h index d0a14996..827f3a2b 100644 --- a/IntelPresentMon/CommonUtilities/win/Event.h +++ b/IntelPresentMon/CommonUtilities/win/Event.h @@ -27,11 +27,12 @@ namespace pmon::util::win std::optional WaitOnMultipleEvents(std::span events, bool waitAll = false, uint32_t milli = 0xFFFF'FFFF); template concept Eventable = std::derived_from || std::same_as; + // TODO: this can just return a non-optional more simply template - std::optional WaitAnyEvent(const E&...events) + uint32_t WaitAnyEvent(const E&...events) { Event::HandleType handles[] = { events... }; - return WaitOnMultipleEvents(handles); + return *WaitOnMultipleEvents(handles); } template diff --git a/IntelPresentMon/KernelProcess/winmain.cpp b/IntelPresentMon/KernelProcess/winmain.cpp index 0e963ab8..368061f7 100644 --- a/IntelPresentMon/KernelProcess/winmain.cpp +++ b/IntelPresentMon/KernelProcess/winmain.cpp @@ -1,6 +1,7 @@ #include "../CommonUtilities/win/WinAPI.h" #include "../Core/source/kernel/Kernel.h" #include "../Core/source/infra/util/FolderResolver.h" +#include "../CommonUtilities/log/IdentificationTable.h" #include "../Interprocess/source/act/SymmetricActionServer.h" #include "kact/KernelExecutionContext.h" #include "../AppCef/source/util/cact/TargetLostAction.h" @@ -124,6 +125,8 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi #endif try { + util::log::IdentificationTable::AddThisProcess("kproc"); + util::log::IdentificationTable::AddThisThread("main"); // if we were run from a parent with a console (terminal?), try to attach there const bool fromTerminal = TryAttachToParentConsole_(); // parse the command line arguments and make them globally available @@ -482,10 +485,9 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi } } else { // wait indefinitely until %q command received or kernel signal - auto res = util::win::WaitAnyEvent( + if (util::win::WaitAnyEvent( dynamic_cast(pKernelHandler.get())->stopEvent_, - stopCommandEvent); - if (res && *res == 1) { + stopCommandEvent) == 1) { std::cerr << "Capture terminated by %q command." << std::endl; } else { diff --git a/IntelPresentMon/PresentMonService/CliOptions.h b/IntelPresentMon/PresentMonService/CliOptions.h index ed8ce4a1..f615b084 100644 --- a/IntelPresentMon/PresentMonService/CliOptions.h +++ b/IntelPresentMon/PresentMonService/CliOptions.h @@ -40,6 +40,7 @@ namespace clio private: Group gt_{ this, "Testing", "Automated testing features" }; public: Flag enableTestControl{ this, "--enable-test-control", "Enable test control over stdio" }; Flag disableLegacyBackpressure{ this, "--disable-legacy-backpressure", "Disable backpressure to enable testing of new IPC" }; + Flag newTelemetryActivation{ this, "--new-telemetry-activation", "Enable the new telemetry activation via metric usage tracking" }; static constexpr const char* description = "Intel PresentMon service for frame and system performance measurement"; static constexpr const char* name = "PresentMonService.exe"; diff --git a/IntelPresentMon/PresentMonService/PMMainThread.cpp b/IntelPresentMon/PresentMonService/PMMainThread.cpp index 93e2721c..0c74658e 100644 --- a/IntelPresentMon/PresentMonService/PMMainThread.cpp +++ b/IntelPresentMon/PresentMonService/PMMainThread.cpp @@ -18,6 +18,7 @@ #include "../CommonUtilities/IntervalWaiter.h" #include "../CommonUtilities/PrecisionWaiter.h" #include "../CommonUtilities/win/Event.h" +#include "../CommonUtilities/log/IdentificationTable.h" #include "../CommonUtilities/log/GlogShim.h" #include "testing/TestControl.h" @@ -47,7 +48,7 @@ void EventFlushThreadEntry_(Service* const srv, PresentMon* const pm) while (true) { pmlog_verb(v::etwq)("Begin idle ETW flush wait"); // if event index 0 is signalled that means we are stopping - if (*win::WaitAnyEvent(srv->GetServiceStopHandle(), pm->GetStreamingStartHandle()) == 0) { + if (win::WaitAnyEvent(srv->GetServiceStopHandle(), pm->GetStreamingStartHandle()) == 0) { pmlog_dbg("exiting ETW flush thread due to stop handle"); return; } @@ -278,109 +279,142 @@ static void PopulateGpuTelemetryRings_( void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, - PowerTelemetryContainer* const ptc, ipc::ServiceComms* const pComms) + PowerTelemetryContainer* const ptc, ipc::ServiceComms* const pComms, bool newActivation) { - if (srv == nullptr || pm == nullptr || ptc == nullptr) { - // TODO: log error here - return; - } - - // we first wait for a client control connection before populating telemetry container - // after populating, we sample each adapter to gather availability information - // this is deferred until client connection in order to increase the probability that - // telemetry metric availability is accurately assessed - { - const HANDLE events[]{ - srv->GetClientSessionHandle(), - srv->GetServiceStopHandle(), - }; - const auto waitResult = WaitForMultipleObjects((DWORD)std::size(events), events, FALSE, INFINITE); - // TODO: check for wait result error - // if events[1] was signalled, that means service is stopping so exit thread - if ((waitResult - WAIT_OBJECT_0) == 1) { + using util::win::WaitAnyEvent; + using util::win::WaitAnyEventFor; + try { + util::log::IdentificationTable::AddThisThread("tele-gpu"); + pmlog_dbg("Starting gpu telemetry thread"); + if (srv == nullptr || pm == nullptr || ptc == nullptr) { + pmlog_error("Required parameter was null"); return; } - pmon::util::QpcTimer timer; - ptc->Repopulate(); - for (auto&& [i, adapter] : ptc->GetPowerTelemetryAdapters() | vi::enumerate) { - // sample 2x here as workaround/kludge because Intel provider misreports 1st sample - adapter->Sample(); - adapter->Sample(); - pComms->RegisterGpuDevice(adapter->GetVendor(), adapter->GetName(), - ipc::intro::ConvertBitset(adapter->GetPowerTelemetryCapBits())); - // after registering, we know that at least the store is available even - // if the introspection itself is not complete - auto& gpuStore = pComms->GetGpuDataStore(uint32_t(i + 1)); - // TODO: replace this placeholder routine for populating statics - gpuStore.statics.name = adapter->GetName().c_str(); - gpuStore.statics.vendor = adapter->GetVendor(); - gpuStore.statics.memSize = adapter->GetDedicatedVideoMemory(); - gpuStore.statics.maxMemBandwidth = adapter->GetVideoMemoryMaxBandwidth(); - gpuStore.statics.sustainedPowerLimit = adapter->GetSustainedPowerLimit(); - // max fanspeed is polled in old system but static in new system, shim here - // TODO: make this fully static - // infer number of fans by the size of the telemetry ring array for fan speed - const auto nFans = gpuStore.telemetryData.ArraySize(PM_METRIC_GPU_FAN_SPEED); - auto& sample = adapter->GetNewest(); - for (size_t i = 0; i < nFans; i++) { - gpuStore.statics.maxFanSpeedRpm.push_back(sample.max_fan_speed_rpm[i]); - } - } - pComms->FinalizeGpuDevices(); - pmlog_info(std::format("Finished populating GPU telemetry introspection, {} seconds elapsed", timer.Mark())); - } - // only start periodic polling when streaming starts - // exit polling loop and this thread when service is stopping - { - IntervalWaiter waiter{ 0.016 }; - const HANDLE events[]{ - pm->GetStreamingStartHandle(), - srv->GetServiceStopHandle(), - }; - while (1) { - auto waitResult = WaitForMultipleObjects((DWORD)std::size(events), events, FALSE, INFINITE); - // TODO: check for wait result error - // if events[1] was signalled, that means service is stopping so exit thread - if ((waitResult - WAIT_OBJECT_0) == 1) { + // we first wait for a client control connection before populating telemetry container + // after populating, we sample each adapter to gather availability information + // this is deferred until client connection in order to increase the probability that + // telemetry metric availability is accurately assessed + { + if (WaitAnyEvent(srv->GetClientSessionHandle(), srv->GetServiceStopHandle()) == 1) { + // if events[1] was signalled, that means service is stopping so exit thread + pmlog_dbg("Exiting gpu telemetry thread before initialization"); return; } - // otherwise we assume streaming has started and we begin the polling loop - while (WaitForSingleObject(srv->GetServiceStopHandle(), 0) != WAIT_OBJECT_0) { - // if device was reset (driver installed etc.) we need to repopulate telemetry - if (WaitForSingleObject(srv->GetResetPowerTelemetryHandle(), 0) == WAIT_OBJECT_0) { - // TODO: log error here or inside of repopulate - ptc->Repopulate(); + pmon::util::QpcTimer timer; + ptc->Repopulate(); + for (auto&& [i, adapter] : ptc->GetPowerTelemetryAdapters() | vi::enumerate) { + // sample 2x here as workaround/kludge because Intel provider misreports 1st sample + adapter->Sample(); + adapter->Sample(); + pComms->RegisterGpuDevice(adapter->GetVendor(), adapter->GetName(), + ipc::intro::ConvertBitset(adapter->GetPowerTelemetryCapBits())); + // after registering, we know that at least the store is available even + // if the introspection itself is not complete + auto& gpuStore = pComms->GetGpuDataStore(uint32_t(i + 1)); + // TODO: replace this placeholder routine for populating statics + gpuStore.statics.name = adapter->GetName().c_str(); + gpuStore.statics.vendor = adapter->GetVendor(); + gpuStore.statics.memSize = adapter->GetDedicatedVideoMemory(); + gpuStore.statics.maxMemBandwidth = adapter->GetVideoMemoryMaxBandwidth(); + gpuStore.statics.sustainedPowerLimit = adapter->GetSustainedPowerLimit(); + // max fanspeed is polled in old system but static in new system, shim here + // TODO: make this fully static + // infer number of fans by the size of the telemetry ring array for fan speed + const auto nFans = gpuStore.telemetryData.ArraySize(PM_METRIC_GPU_FAN_SPEED); + auto& sample = adapter->GetNewest(); + for (size_t i = 0; i < nFans; i++) { + gpuStore.statics.maxFanSpeedRpm.push_back(sample.max_fan_speed_rpm[i]); } - auto& adapters = ptc->GetPowerTelemetryAdapters(); - for (size_t idx = 0; idx < adapters.size(); ++idx) { - auto& adapter = adapters[idx]; - adapter->Sample(); + } + pComms->FinalizeGpuDevices(); + pmlog_info(std::format("Finished populating GPU telemetry introspection, {} seconds elapsed", timer.Mark())); + } - // Get the newest sample from the provider - const auto& sample = adapter->GetNewest(); + // only start periodic polling when streaming starts + // exit polling loop and this thread when service is stopping + { + IntervalWaiter waiter{ 0.016 }; + while (true) { + if (newActivation) { + pmlog_dbg("(re)starting gpu idle wait (new)"); + if (WaitAnyEvent(pm->GetDeviceUsageEvent(), srv->GetServiceStopHandle()) == 1) { + pmlog_dbg("gpu telemetry received exit code, thread exiting"); + return; + } + else { + // if any of our gpu telemetry devices are active enter polling loop + bool hasActive = false; + for (auto&& [i, ad] : ptc->GetPowerTelemetryAdapters() | vi::enumerate) { + const auto deviceId = uint32_t(i) + 1; + if (pm->CheckDeviceMetricUsage(deviceId)) { + pmlog_dbg("detected gpu active").pmwatch(deviceId); + hasActive = true; + break; + } + } + if (!hasActive) { + pmlog_dbg("received device usage event, but no gpu tele device was active"); + continue; + } + } + } + else { + pmlog_dbg("(re)starting gpu idle wait (legacy)"); + const HANDLE events[]{ + pm->GetStreamingStartHandle(), + srv->GetServiceStopHandle(), + }; + auto waitResult = WaitForMultipleObjects((DWORD)std::size(events), events, FALSE, INFINITE); + // TODO: check for wait result error + // if events[1] was signalled, that means service is stopping so exit thread + if ((waitResult - WAIT_OBJECT_0) == 1) { + return; + } + } + // otherwise we assume streaming has started and we begin the polling loop + pmlog_dbg("entering gpu tele active poll loop"); + while (!WaitAnyEventFor(0ms, srv->GetServiceStopHandle())) { + // if device was reset (driver installed etc.) we need to repopulate telemetry + if (WaitAnyEventFor(0ms, srv->GetResetPowerTelemetryHandle())) { + // TODO: log error here or inside of repopulate + ptc->Repopulate(); + } + // poll all gpu adapter devices + // TODO: only poll devices that are actually active + auto& adapters = ptc->GetPowerTelemetryAdapters(); + for (size_t idx = 0; idx < adapters.size(); ++idx) { + auto& adapter = adapters[idx]; + adapter->Sample(); - // Retrieve the matching GPU store. - auto& store = pComms->GetGpuDataStore(uint32_t(idx + 1)); + // Get the newest sample from the provider + const auto& sample = adapter->GetNewest(); - PopulateGpuTelemetryRings_(store, sample); - } - // Convert from the ms to seconds as GetTelemetryPeriod returns back - // ms and SetInterval expects seconds. - waiter.SetInterval(pm->GetGpuTelemetryPeriod()/1000.); - waiter.Wait(); - // go dormant if there are no active streams left - // TODO: consider race condition here if client stops and starts streams rapidly - if (pm->GetActiveStreams() == 0) { - break; + // Retrieve the matching GPU store. + auto& store = pComms->GetGpuDataStore(uint32_t(idx + 1)); + + PopulateGpuTelemetryRings_(store, sample); + } + // Convert from the ms to seconds as GetTelemetryPeriod returns back + // ms and SetInterval expects seconds. + waiter.SetInterval(pm->GetGpuTelemetryPeriod() / 1000.); + waiter.Wait(); + // go dormant if there are no active streams left + // TODO: consider race condition here if client stops and starts streams rapidly + if (pm->GetActiveStreams() == 0) { + break; + } } } } } + catch (...) { + pmlog_error(util::ReportException("Exception leaked to top level of gpu telemetry thread")); + } } void CpuTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, ipc::ServiceComms* pComms, - pwr::cpu::CpuTelemetry* const cpu) noexcept + pwr::cpu::CpuTelemetry* const cpu, bool newActivation) noexcept { // we don't expect any exceptions in this system during normal operation // (maybe during initialization at most, not during polling) @@ -388,6 +422,7 @@ void CpuTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, ipc::Ser // don't let this thread crash the process, just exit with an error for later // diagnosis try { + util::log::IdentificationTable::AddThisThread("tele-sys"); IntervalWaiter waiter{ 0.016 }; if (srv == nullptr || pm == nullptr) { // TODO: log error on this condition @@ -528,7 +563,8 @@ void PresentMonMainThread(Service* const pSvc) auto pActionServer = std::make_unique(pSvc, &pm, opt.controlPipe.AsOptional()); try { - gpuTelemetryThread = std::jthread{ PowerTelemetryThreadEntry_, pSvc, &pm, &ptc, pComms.get() }; + gpuTelemetryThread = std::jthread{ PowerTelemetryThreadEntry_, pSvc, &pm, &ptc, + pComms.get(), opt.newTelemetryActivation }; } catch (...) { LOG(ERROR) << "failed creating gpu(power) telemetry thread" << std::endl; @@ -548,7 +584,8 @@ void PresentMonMainThread(Service* const pSvc) } if (cpu) { - cpuTelemetryThread = std::jthread{ CpuTelemetryThreadEntry_, pSvc, &pm, pComms.get(), cpu.get()}; + cpuTelemetryThread = std::jthread{ CpuTelemetryThreadEntry_, pSvc, &pm, pComms.get(), + cpu.get(), opt.newTelemetryActivation }; pm.SetCpu(cpu); // sample once to populate the cap bits cpu->Sample(); diff --git a/IntelPresentMon/PresentMonService/PresentMon.h b/IntelPresentMon/PresentMonService/PresentMon.h index 309011ba..0ee1903d 100644 --- a/IntelPresentMon/PresentMonService/PresentMon.h +++ b/IntelPresentMon/PresentMonService/PresentMon.h @@ -4,6 +4,7 @@ #include "PresentMonSession.h" #include "EtwLogger.h" #include "FrameBroadcaster.h" +#include "../CommonUtilities/win/Event.h" #include #include #include @@ -95,8 +96,15 @@ class PresentMon } void SetDeviceMetricUsage(std::unordered_set usage) { - std::lock_guard lk{ metricDeviceUsageMtx_ }; - metricDeviceUsage_ = std::move(usage); + { + std::lock_guard lk{ metricDeviceUsageMtx_ }; + metricDeviceUsage_ = std::move(usage); + } + deviceUsageEvt_.Set(); + } + HANDLE GetDeviceUsageEvent() const + { + return deviceUsageEvt_.Get(); } void StartPlayback(); void StopPlayback(); @@ -106,4 +114,5 @@ class PresentMon std::unique_ptr pSession_; mutable std::shared_mutex metricDeviceUsageMtx_; std::unordered_set metricDeviceUsage_; + util::win::Event deviceUsageEvt_{ false }; }; \ No newline at end of file diff --git a/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp b/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp index 7909a873..ece657e6 100644 --- a/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp +++ b/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp @@ -493,7 +493,7 @@ void RealtimePresentMonSession::Output() { // wait for either events to process or periodic polling timer while (auto idx = util::win::WaitAnyEvent(pm_consumer_->hEventsReadyEvent, hTimer)) { // events are ready so we should process them - if (*idx == 0) { + if (idx == 0) { pmlog_verb(v::etwq)("Event(s) ready"); break; } diff --git a/IntelPresentMon/PresentMonService/ServiceMain.cpp b/IntelPresentMon/PresentMonService/ServiceMain.cpp index ac2571df..12f0dad6 100644 --- a/IntelPresentMon/PresentMonService/ServiceMain.cpp +++ b/IntelPresentMon/PresentMonService/ServiceMain.cpp @@ -10,6 +10,7 @@ #include "Registry.h" #include "../Versioning/BuildId.h" #include "../CommonUtilities/log/GlobalPolicy.h" +#include "../CommonUtilities/log/IdentificationTable.h" TCHAR serviceName[MaxBufferLength] = TEXT("Intel PresentMon Service"); using namespace pmon; @@ -17,6 +18,8 @@ using namespace pmon; // common entry point whether invoked as service or as app int CommonEntry(DWORD argc, LPTSTR* argv, bool asApp) { + util::log::IdentificationTable::AddThisProcess("service"); + util::log::IdentificationTable::AddThisThread("main"); logsetup::LogChannelManager logMan_; // parse command line, return with error code from CLI11 if running as app if (auto e = clio::Options::Init(argc, argv); e && asApp) { From 1eaa1a64260a13cb8a432bb24bb58a107d072d93 Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 23 Dec 2025 08:59:18 +0900 Subject: [PATCH 073/205] allowing dynamic frame queries to have sys and gpu devices --- IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index 91f40e00..05239859 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -235,8 +235,8 @@ namespace pmon::mid uint64_t offset = 0u; for (auto& qe : queryElements) { - // A device of zero is NOT a graphics adapter. - if (qe.deviceId != 0) { + // A device of zero is NOT a graphics adapter; 65536 is sys and not gpu either + if (qe.deviceId != 0 && qe.deviceId != 65536) { // If we have already set a device id in this query, check to // see if it's the same device id as previously set. Currently // we don't support querying multiple gpu devices in the one From bbd465701849a2d411aff92ab8276193e981b816 Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 23 Dec 2025 12:55:06 +0900 Subject: [PATCH 074/205] fixing svc race condition, test for new telemetry activation --- .../Interprocess/source/HistoryRing.h | 5 ++ .../InterimBroadcasterTests.cpp | 49 ++++++++++++------- .../PresentMonService/PMMainThread.cpp | 35 +++++++++++-- .../PresentMonService/PresentMon.cpp | 1 + 4 files changed, 67 insertions(+), 23 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/HistoryRing.h b/IntelPresentMon/Interprocess/source/HistoryRing.h index 42b954a2..8a7440f0 100644 --- a/IntelPresentMon/Interprocess/source/HistoryRing.h +++ b/IntelPresentMon/Interprocess/source/HistoryRing.h @@ -45,6 +45,11 @@ namespace pmon::ipc { return samples_.Empty(); } + size_t Size() const + { + const auto range = GetSerialRange(); + return range.second - range.first; + } // First serial with timestamp >= given timestamp. // If all samples have timestamp < given timestamp, returns last (one past end). size_t LowerBoundSerial(uint64_t timestamp) const diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index be0fc439..0edfc487 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -293,7 +293,7 @@ namespace InterimBroadcasterTests public: TEST_METHOD_INITIALIZE(Setup) { - fixture_.Setup(); + fixture_.Setup({"--new-telemetry-activation"s}); } TEST_METHOD_CLEANUP(Cleanup) { @@ -326,16 +326,18 @@ namespace InterimBroadcasterTests // set telemetry period so we have a known baseline client.DispatchSync(svc::acts::SetTelemetryPeriod::Params{ .telemetrySamplePeriodMs = 100 }); - // as a stopgap we target a process in order to trigger service-side telemetry collection - // TODO: remove this when we enable service-side query awareness of connected clients - auto pres = fixture_.LaunchPresenter(); - client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pres.GetId() }); + // update server with metric/device usage information + // this will trigger gpu telemetry collection + client.DispatchSync(svc::acts::ReportMetricUse::Params{ { + { PM_METRIC_GPU_TEMPERATURE, 1, 0 }, + { PM_METRIC_GPU_POWER, 1, 0 }, + } }); // get the store containing adapter telemetry auto& gpu = pComms->GetGpuDataStore(1); // allow a short warmup - std::this_thread::sleep_for(500ms); + std::this_thread::sleep_for(150ms); std::vector::Sample> tempSamples; std::vector::Sample> powerSamples; @@ -402,16 +404,11 @@ namespace InterimBroadcasterTests // set telemetry period so we have a known baseline client.DispatchSync(svc::acts::SetTelemetryPeriod::Params{ .telemetrySamplePeriodMs = 40 }); - // as a stopgap we target a process in order to trigger service-side telemetry collection - // TODO: remove this when we enable service-side query awareness of connected clients - auto pres = fixture_.LaunchPresenter(); - client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pres.GetId() }); + // target gpu device 1 (hardcoded for test) + const uint32_t TargetDeviceID = 1; // get the store containing adapter telemetry - auto& gpu = pComms->GetGpuDataStore(1); - - // allow a short warmup - std::this_thread::sleep_for(500ms); + auto& gpu = pComms->GetGpuDataStore(TargetDeviceID); // build the set of expected rings from introspection Logger::WriteMessage("Introspection Metrics\n=====================\n"); @@ -430,7 +427,7 @@ namespace InterimBroadcasterTests // check availability for target gpu size_t arraySize = 0; for (auto&& di : m.GetDeviceMetricInfo()) { - if (di.GetDevice().GetId() != 1) { + if (di.GetDevice().GetId() != TargetDeviceID) { // skip over non-matching devices continue; } @@ -453,17 +450,33 @@ namespace InterimBroadcasterTests // validate that the expected number of rings sets are present in the store Assert::AreEqual(introspectionAvailability.size(), (size_t)rn::distance(gpu.telemetryData.Rings())); + + { + // build metric use set from above introspection results + std::unordered_set uses; + for (auto&& [met, siz] : introspectionAvailability) { + if (siz > 0) { + uses.insert({ met, TargetDeviceID, 0 }); + } + } + // update server with metric/device usage information + // this will trigger gpu telemetry collection + client.DispatchSync(svc::acts::ReportMetricUse::Params{ std::move(uses) }); + } - // validate that exepected rings are present and are populated with samples + // allow a short warmup + std::this_thread::sleep_for(150ms); + + // validate that exepected rings are populated with samples and have correct dimensions for (auto&& [met, size] : introspectionAvailability) { // array sizes should match Assert::AreEqual(size, gpu.telemetryData.ArraySize(met), pMetricMap->at(met).wideName.c_str()); std::visit([&](auto const& rings) { - // for each history ring in set, make sure it has at least one sample in it + // for each history ring in set, make sure it has at least more than one sample in it for (size_t i = 0; i < size; i++) { auto& name = pMetricMap->at(met).wideName; - Assert::IsFalse(rings[i].Empty(), + Assert::IsTrue(rings[i].Size() > 1, std::format(L"{}[{}]", name, i).c_str()); auto& sample = rings[i].Newest(); Logger::WriteMessage(std::format(L"{}[{}]: {}@{}\n", name, i, diff --git a/IntelPresentMon/PresentMonService/PMMainThread.cpp b/IntelPresentMon/PresentMonService/PMMainThread.cpp index 0c74658e..683f552f 100644 --- a/IntelPresentMon/PresentMonService/PMMainThread.cpp +++ b/IntelPresentMon/PresentMonService/PMMainThread.cpp @@ -360,6 +360,7 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, } } else { + // TODO: remove legacy branch here pmlog_dbg("(re)starting gpu idle wait (legacy)"); const HANDLE events[]{ pm->GetStreamingStartHandle(), @@ -373,6 +374,7 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, } } // otherwise we assume streaming has started and we begin the polling loop + // TODO: consider poll loop per device architecture instead of loop all pmlog_dbg("entering gpu tele active poll loop"); while (!WaitAnyEventFor(0ms, srv->GetServiceStopHandle())) { // if device was reset (driver installed etc.) we need to repopulate telemetry @@ -399,10 +401,26 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, // ms and SetInterval expects seconds. waiter.SetInterval(pm->GetGpuTelemetryPeriod() / 1000.); waiter.Wait(); - // go dormant if there are no active streams left - // TODO: consider race condition here if client stops and starts streams rapidly - if (pm->GetActiveStreams() == 0) { - break; + if (newActivation) { + // go dormant if no gpu devices are in use + bool anyUsed = false; + for (size_t idx = 0; idx < adapters.size(); ++idx) { + if (pm->CheckDeviceMetricUsage(uint32_t(idx + 1))) { + anyUsed = true; + break; + } + } + if (!anyUsed) { + break; + } + } + else { + // go dormant if there are no active streams left + // TODO: consider race condition here if client stops and starts streams rapidly + // TODO: remove this legacy branch + if (pm->GetActiveStreams() == 0) { + break; + } } } } @@ -636,7 +654,14 @@ void PresentMonMainThread(Service* const pSvc) } // Stop the PresentMon sessions - pm.StopTraceSessions(); + pm.StopTraceSessions(); + // wait for the telemetry threads to exit + if (gpuTelemetryThread.joinable()) { + gpuTelemetryThread.join(); + } + if (cpuTelemetryThread.joinable()) { + cpuTelemetryThread.join(); + } } catch (...) { LOG(ERROR) << "Exception in PMMainThread, bailing" << std::endl; diff --git a/IntelPresentMon/PresentMonService/PresentMon.cpp b/IntelPresentMon/PresentMonService/PresentMon.cpp index 46ff1826..b02986a1 100644 --- a/IntelPresentMon/PresentMonService/PresentMon.cpp +++ b/IntelPresentMon/PresentMonService/PresentMon.cpp @@ -29,6 +29,7 @@ PresentMon::PresentMon(svc::FrameBroadcaster& broadcaster, PresentMon::~PresentMon() { pSession_->CheckTraceSessions(true); + pmlog_dbg("PresentMon object destructor finishing"); } PM_STATUS PresentMon::StartStreaming(uint32_t client_process_id, uint32_t target_process_id, From b9416697d1450a0c6a175afb61cd213100ed2e17 Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 23 Dec 2025 16:57:34 +0900 Subject: [PATCH 075/205] fixing usage event system to notify all --- .../InterimBroadcasterTests.cpp | 41 +++++++---- .../PresentMonService/PMMainThread.cpp | 72 ++++++++++++++----- .../PresentMonService/PresentMon.h | 32 +++++++-- 3 files changed, 106 insertions(+), 39 deletions(-) diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 0edfc487..1e2ebfac 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -132,7 +132,7 @@ namespace InterimBroadcasterTests public: TEST_METHOD_INITIALIZE(Setup) { - fixture_.Setup(); + fixture_.Setup({ "--new-telemetry-activation"s }); } TEST_METHOD_CLEANUP(Cleanup) { @@ -154,26 +154,38 @@ namespace InterimBroadcasterTests { mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + // acquire introspection with enhanced wrapper interface auto pIntro = pComms->GetIntrospectionRoot(); pmapi::intro::Root intro{ pIntro, [](auto* p) {delete p; } }; pmapi::EnumMap::Refresh(intro); auto pMetricMap = pmapi::EnumMap::GetKeyMap(PM_ENUM_METRIC); + // set telemetry period so we have a known baseline client.DispatchSync(svc::acts::SetTelemetryPeriod::Params{ .telemetrySamplePeriodMs = 100 }); - // as a stopgap we target a process in order to trigger service-side telemetry collection - // TODO: remove this when we enable service-side query awareness of connected clients - auto pres = fixture_.LaunchPresenter(); - client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pres.GetId() }); + // get the store containing system-wide telemetry (cpu etc.) auto& sys = pComms->GetSystemDataStore(); for (auto&& [met, r] : sys.telemetryData.Rings()) { Logger::WriteMessage(std::format(" TeleRing@{}\n", pMetricMap->at(met).narrowName).c_str()); // TODO: understand the disconnect between CPU Core Utility showing up here - // and now showing up in the UI + // and not showing up in the UI (update: it is blacklisted manually in UI introspection) } Assert::AreEqual(2ull, (size_t)rn::distance(sys.telemetryData.Rings())); - std::this_thread::sleep_for(500ms); + + // system device id constant + const uint32_t SystemDeviceID = 65536; + + // update server with metric/device usage information + // this will trigger system telemetry collection + client.DispatchSync(svc::acts::ReportMetricUse::Params{ { + { PM_METRIC_CPU_UTILIZATION, SystemDeviceID, 0 }, + { PM_METRIC_CPU_FREQUENCY, SystemDeviceID, 0 }, + } }); + + // allow warm-up period + std::this_thread::sleep_for(150ms); + // check that we have data for frequency and utilization std::vector::Sample> utilizSamples; std::vector::Sample> freqSamples; @@ -206,6 +218,7 @@ namespace InterimBroadcasterTests Assert::IsTrue(sample.value > 1500.); } } + // make sure samples actually change over time Assert::AreNotEqual(utilizSamples.front().timestamp, utilizSamples.back().timestamp); Assert::AreNotEqual(utilizSamples.front().value, utilizSamples.back().value); @@ -227,11 +240,6 @@ namespace InterimBroadcasterTests // set telemetry period so we have a known baseline client.DispatchSync(svc::acts::SetTelemetryPeriod::Params{ .telemetrySamplePeriodMs = 40 }); - // as a stopgap we target a process in order to trigger service-side telemetry collection - // TODO: remove this when we enable service-side query awareness of connected clients - auto pres = fixture_.LaunchPresenter(); - client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pres.GetId() }); - // get the store containing adapter telemetry auto& sys = pComms->GetSystemDataStore(); @@ -326,15 +334,18 @@ namespace InterimBroadcasterTests // set telemetry period so we have a known baseline client.DispatchSync(svc::acts::SetTelemetryPeriod::Params{ .telemetrySamplePeriodMs = 100 }); + // target gpu device 1 (hardcoded for test) + const uint32_t TargetDeviceID = 1; + // update server with metric/device usage information // this will trigger gpu telemetry collection client.DispatchSync(svc::acts::ReportMetricUse::Params{ { - { PM_METRIC_GPU_TEMPERATURE, 1, 0 }, - { PM_METRIC_GPU_POWER, 1, 0 }, + { PM_METRIC_GPU_TEMPERATURE, TargetDeviceID, 0 }, + { PM_METRIC_GPU_POWER, TargetDeviceID, 0 }, } }); // get the store containing adapter telemetry - auto& gpu = pComms->GetGpuDataStore(1); + auto& gpu = pComms->GetGpuDataStore(TargetDeviceID); // allow a short warmup std::this_thread::sleep_for(150ms); diff --git a/IntelPresentMon/PresentMonService/PMMainThread.cpp b/IntelPresentMon/PresentMonService/PMMainThread.cpp index 683f552f..01f66e2c 100644 --- a/IntelPresentMon/PresentMonService/PMMainThread.cpp +++ b/IntelPresentMon/PresentMonService/PMMainThread.cpp @@ -286,7 +286,7 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, try { util::log::IdentificationTable::AddThisThread("tele-gpu"); pmlog_dbg("Starting gpu telemetry thread"); - if (srv == nullptr || pm == nullptr || ptc == nullptr) { + if (srv == nullptr || pm == nullptr || ptc == nullptr || pComms == nullptr) { pmlog_error("Required parameter was null"); return; } @@ -401,6 +401,7 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, // ms and SetInterval expects seconds. waiter.SetInterval(pm->GetGpuTelemetryPeriod() / 1000.); waiter.Wait(); + // conditions for ending active poll and returning to idle state if (newActivation) { // go dormant if no gpu devices are in use bool anyUsed = false; @@ -434,6 +435,8 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, void CpuTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, ipc::ServiceComms* pComms, pwr::cpu::CpuTelemetry* const cpu, bool newActivation) noexcept { + using util::win::WaitAnyEvent; + using util::win::WaitAnyEventFor; // we don't expect any exceptions in this system during normal operation // (maybe during initialization at most, not during polling) // but if they do happen, it is a halting condition for the system telemetry @@ -441,24 +444,46 @@ void CpuTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, ipc::Ser // diagnosis try { util::log::IdentificationTable::AddThisThread("tele-sys"); - IntervalWaiter waiter{ 0.016 }; - if (srv == nullptr || pm == nullptr) { - // TODO: log error on this condition + pmlog_dbg("Starting system telemetry thread"); + if (srv == nullptr || pm == nullptr || pComms == nullptr || cpu == nullptr) { + pmlog_error("Required parameter was null"); return; } - const HANDLE events[]{ - pm->GetStreamingStartHandle(), - srv->GetServiceStopHandle(), - }; - - while (1) { - auto waitResult = WaitForMultipleObjects((DWORD)std::size(events), events, FALSE, INFINITE); - auto i = waitResult - WAIT_OBJECT_0; - if (i == 1) { - return; + IntervalWaiter waiter{ 0.016 }; + while (true) { + if (newActivation) { + pmlog_dbg("(re)starting system idle wait (new)"); + if (WaitAnyEvent(pm->GetDeviceUsageEvent(), srv->GetServiceStopHandle()) == 1) { + pmlog_dbg("system telemetry received exit code, thread exiting"); + return; + } + else { + // if system telemetry metrics active enter active polling loop + if (pm->CheckDeviceMetricUsage(65536)) { + pmlog_dbg("detected system active"); + } + else { + pmlog_dbg("received device usage event, but system tele device was not active"); + continue; + } + } + } + else { + // TODO: remove legacy branch here + pmlog_dbg("(re)starting system idle wait (legacy)"); + const HANDLE events[]{ + pm->GetStreamingStartHandle(), + srv->GetServiceStopHandle(), + }; + auto waitResult = WaitForMultipleObjects((DWORD)std::size(events), events, FALSE, INFINITE); + // TODO: check for wait result error + // if events[1] was signalled, that means service is stopping so exit thread + if ((waitResult - WAIT_OBJECT_0) == 1) { + return; + } } - while (WaitForSingleObject(srv->GetServiceStopHandle(), 0) != WAIT_OBJECT_0) { + while (!WaitAnyEventFor(0ms, srv->GetServiceStopHandle())) { // TODO:streamer replace this flow with a call that populates rings of a store cpu->Sample(); // placeholder routine shim to translate cpu tranfer struct into rings @@ -493,10 +518,19 @@ void CpuTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, ipc::Ser // ms and SetInterval expects seconds. waiter.SetInterval(pm->GetGpuTelemetryPeriod() / 1000.); waiter.Wait(); - // Get the number of currently active streams - auto num_active_streams = pm->GetActiveStreams(); - if (num_active_streams == 0) { - break; + // conditions for ending active poll and returning to idle state + if (newActivation) { + if (!pm->CheckDeviceMetricUsage(65536)) { + break; + } + } + else { + // go dormant if there are no active streams left + // TODO: consider race condition here if client stops and starts streams rapidly + // TODO: remove this legacy branch + if (pm->GetActiveStreams() == 0) { + break; + } } } } diff --git a/IntelPresentMon/PresentMonService/PresentMon.h b/IntelPresentMon/PresentMonService/PresentMon.h index 0ee1903d..6f2bcc56 100644 --- a/IntelPresentMon/PresentMonService/PresentMon.h +++ b/IntelPresentMon/PresentMonService/PresentMon.h @@ -5,10 +5,15 @@ #include "EtwLogger.h" #include "FrameBroadcaster.h" #include "../CommonUtilities/win/Event.h" +#include "../CommonUtilities/Hash.h" #include #include #include +#include #include +#include +#include +#include using namespace pmon; @@ -96,15 +101,31 @@ class PresentMon } void SetDeviceMetricUsage(std::unordered_set usage) { + // we need exclusive lock to prevent concurrent access to usage data while being modified { std::lock_guard lk{ metricDeviceUsageMtx_ }; metricDeviceUsage_ = std::move(usage); } - deviceUsageEvt_.Set(); + // keep shared lock now to prevent modification to event set while we are iterating it + // if this were non-shared, it would cause the listeners to block immediately on wake + std::shared_lock lk2{ metricDeviceUsageMtx_ }; + for (auto& kv : deviceUsageEvts_) { + kv.second.Set(); + } } - HANDLE GetDeviceUsageEvent() const + HANDLE GetDeviceUsageEvent(std::source_location loc = std::source_location::current()) const { - return deviceUsageEvt_.Get(); + const DeviceUsageEvtKey key{ loc.file_name(), (uint32_t)loc.line() }; + { + std::shared_lock lk{ metricDeviceUsageMtx_ }; + if (auto it = deviceUsageEvts_.find(key); it != deviceUsageEvts_.end()) { + return it->second.Get(); + } + } + // get non-shared lock for modification purposes (add new event) + std::lock_guard lk2{ metricDeviceUsageMtx_ }; + auto it = deviceUsageEvts_.emplace(key, util::win::Event{ false, false }).first; + return it->second.Get(); } void StartPlayback(); void StopPlayback(); @@ -114,5 +135,6 @@ class PresentMon std::unique_ptr pSession_; mutable std::shared_mutex metricDeviceUsageMtx_; std::unordered_set metricDeviceUsage_; - util::win::Event deviceUsageEvt_{ false }; -}; \ No newline at end of file + using DeviceUsageEvtKey = std::pair; + mutable std::unordered_map deviceUsageEvts_; +}; From bcfe1121f1971febb3bff738f067312d40d94d22 Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 23 Dec 2025 17:23:36 +0900 Subject: [PATCH 076/205] updating system ring util test and fixture test --- .../InterimBroadcasterTests.cpp | 85 ++++++++++++------- 1 file changed, 55 insertions(+), 30 deletions(-) diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 1e2ebfac..a4adc725 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -120,7 +120,7 @@ namespace InterimBroadcasterTests mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); auto pIntro = pComms->GetIntrospectionRoot(); - Assert::AreEqual(2ull, pIntro->pDevices->size); + Assert::AreEqual(3ull, pIntro->pDevices->size); auto pDevice = static_cast(pIntro->pDevices->pData[1]); Assert::AreEqual("NVIDIA GeForce RTX 2080 Ti", pDevice->pName->pData); } @@ -246,45 +246,70 @@ namespace InterimBroadcasterTests // allow a short warmup std::this_thread::sleep_for(500ms); - // build the set of expected rings from introspection - Logger::WriteMessage("Introspection Metrics\n=====================\n"); - std::map introspectionAvailability; - const auto TryAddMetric = [&](PM_METRIC metric) { - auto&& m = intro.FindMetric(metric); - if (m.GetDeviceMetricInfo().empty()) { - return; - } - auto&& dmi = m.GetDeviceMetricInfo().front(); - if (dmi.IsAvailable()) { - const auto arraySize = dmi.GetArraySize(); - introspectionAvailability[metric] = arraySize; - // dump for review in output pane - Logger::WriteMessage(std::format("[{}] {}\n", arraySize, - pMetricMap->at(m.GetId()).narrowName).c_str()); + // system device id constant + const uint32_t SystemDeviceID = 65536; + + // build the set of expected rings from the store, and cross-check against introspection + Logger::WriteMessage("Store Metrics\n=============\n"); + std::map storeRings; + for (auto&& [met, r] : sys.telemetryData.Rings()) { + const auto storeArraySize = sys.telemetryData.ArraySize(met); + storeRings[met] = storeArraySize; + + // dump for review in output pane + Logger::WriteMessage(std::format("[{}] {}\n", storeArraySize, + pMetricMap->at(met).narrowName).c_str()); + + // validate introspection says the metric is available for the system device + auto&& m = intro.FindMetric(met); + bool matchedDevice = false; + size_t introArraySize = 0; + for (auto&& di : m.GetDeviceMetricInfo()) { + if (di.GetDevice().GetId() != SystemDeviceID) { + // skip over non-matching devices + continue; + } + matchedDevice = true; + if (di.GetAvailability() == PM_METRIC_AVAILABILITY_AVAILABLE) { + introArraySize = di.GetArraySize(); + } + // either way, if we get here, device matched so no need to continue + break; } - }; - // TODO: replace this with code that iterates over all metrics and automatically - // evaluates all cpu telemetry metrics - TryAddMetric(PM_METRIC_CPU_POWER); - TryAddMetric(PM_METRIC_CPU_TEMPERATURE); - TryAddMetric(PM_METRIC_CPU_UTILIZATION); - TryAddMetric(PM_METRIC_CPU_FREQUENCY); - TryAddMetric(PM_METRIC_CPU_CORE_UTILITY); - Logger::WriteMessage(std::format("Total: {}\n", introspectionAvailability.size()).c_str()); + Assert::IsTrue(matchedDevice, pMetricMap->at(met).wideName.c_str()); + Assert::AreEqual(storeArraySize, introArraySize, pMetricMap->at(met).wideName.c_str()); + } + Logger::WriteMessage(std::format("Total: {}\n", storeRings.size()).c_str()); // validate that the expected number of rings sets are present in the store - Assert::AreEqual(introspectionAvailability.size(), (size_t)rn::distance(sys.telemetryData.Rings())); + Assert::AreEqual(storeRings.size(), (size_t)rn::distance(sys.telemetryData.Rings())); - // validate that exepected rings are present and are populated with samples - for (auto&& [met, size] : introspectionAvailability) { + { + // build metric use set from above store results + std::unordered_set uses; + for (auto&& [met, siz] : storeRings) { + if (siz > 0) { + uses.insert({ met, SystemDeviceID, 0 }); + } + } + // update server with metric/device usage information + // this will trigger system telemetry collection + client.DispatchSync(svc::acts::ReportMetricUse::Params{ std::move(uses) }); + } + + // allow a short warmup + std::this_thread::sleep_for(150ms); + + // validate that exepected rings are populated with samples and have correct dimensions + for (auto&& [met, size] : storeRings) { // array sizes should match Assert::AreEqual(size, sys.telemetryData.ArraySize(met), pMetricMap->at(met).wideName.c_str()); std::visit([&](auto const& rings) { - // for each history ring in set, make sure it has at least one sample in it + // for each history ring in set, make sure it has at least more than one sample in it for (size_t i = 0; i < size; i++) { auto& name = pMetricMap->at(met).wideName; - Assert::IsFalse(rings[i].Empty(), + Assert::IsTrue(rings[i].Size() > 1, std::format(L"{}[{}]", name, i).c_str()); auto& sample = rings[i].Newest(); Logger::WriteMessage(std::format(L"{}[{}]: {}@{}\n", name, i, From 0061cf571bd1e5440be721961a0f5ba620b50210 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Tue, 23 Dec 2025 06:42:16 -0800 Subject: [PATCH 077/205] Unified SwapChain in place. WIP ETL's passing --- .../CommonUtilities/mc/MetricsCalculator.cpp | 31 +- .../CommonUtilities/mc/MetricsCalculator.h | 3 +- .../CommonUtilities/mc/UnifiedSwapChain.cpp | 17 +- .../CommonUtilities/mc/UnifiedSwapChain.h | 13 + IntelPresentMon/UnitTests/MetricsCore.cpp | 31 +- PresentMon/Console.cpp | 22 +- PresentMon/OutputThread.cpp | 648 +----------------- PresentMon/PresentMon.args.json | 20 +- PresentMon/PresentMon.hpp | 8 +- Tests/GoldEtlCsvTests.cpp | 6 +- 10 files changed, 143 insertions(+), 656 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 38e304c5..73268582 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -247,7 +247,7 @@ namespace pmon::util::metrics return; } - uint64_t currentSimStart = CalculateSimStartTime(chain, present, chain.animationErrorSource); + uint64_t currentSimStart = CalculateAnimationErrorSimStartTime(chain, present, chain.animationErrorSource); if (currentSimStart == 0 || chain.lastDisplayedSimStartTime == 0 || @@ -282,13 +282,18 @@ namespace pmon::util::metrics if (isFirstProviderSimTime) { // Seed only: no animation time yet. UpdateAfterPresent will flip us // into AppProvider/PCL and latch firstAppSimStartTime. + // TODO: Once ETL tests are passing, change to std::nullopt. + // out.msAnimationTime = std::nullopt; out.msAnimationTime = 0.0; return; } - uint64_t currentSimStart = CalculateSimStartTime(chain, present, chain.animationErrorSource); + uint64_t currentSimStart = CalculateAnimationErrorSimStartTime(chain, present, chain.animationErrorSource); if (currentSimStart == 0) { + // TODO: Once ETL tests are passing, change to std::nullopt. out.msAnimationTime = std::nullopt; + //out.msAnimationTime = 0.0; + return; } @@ -539,7 +544,15 @@ namespace pmon::util::metrics return std::nullopt; } - auto currentSimStartTime = CalculateSimStartTime(chain, present, chain.animationErrorSource); + // The current sim start time is only dependent on the current frame's simulation start times. + // Preference is PCL, then App. + uint64_t currentSimStartTime = 0; + if (present.pclSimStartTime != 0) { + currentSimStartTime = present.pclSimStartTime; + } + else if (present.appSimStartTime != 0) { + currentSimStartTime = present.appSimStartTime; + } if (chain.lastSimStartTime != 0 && currentSimStartTime != 0) { return qpc.DeltaUnsignedMilliSeconds( chain.lastSimStartTime, @@ -680,6 +693,7 @@ namespace pmon::util::metrics const uint64_t nextScreenTime = 0; const bool isDisplayed = false; const bool isAppFrame = true; + const FrameType frameType = FrameType::NotSet; auto metrics = ComputeFrameMetrics( qpc, @@ -688,6 +702,7 @@ namespace pmon::util::metrics nextScreenTime, isDisplayed, isAppFrame, + frameType, chainState); ApplyStateDeltas(chainState, metrics.stateDeltas); @@ -724,14 +739,17 @@ namespace pmon::util::metrics AdjustScreenTimeForCollapsedPresentNV(present, nextDisplayed, screenTime, nextScreenTime); const bool isAppFrame = (displayIndex == indexing.appIndex); + const bool isDisplayedInstance = isDisplayed && screenTime != 0 && nextScreenTime != 0; + const FrameType frameType = isDisplayedInstance ? present.getDisplayedFrameType(displayIndex) : FrameType::NotSet; auto metrics = ComputeFrameMetrics( qpc, present, screenTime, nextScreenTime, - isDisplayed, + isDisplayedInstance, isAppFrame, + frameType, chainState); ApplyStateDeltas(chainState, metrics.stateDeltas); @@ -1074,12 +1092,15 @@ namespace pmon::util::metrics uint64_t nextScreenTime, bool isDisplayed, bool isAppFrame, + FrameType frameType, const SwapChainCoreState& chain) { ComputedMetrics result{}; FrameMetrics& metrics = result.metrics; + metrics.frameType = frameType; + CalculateBasePresentMetrics( qpc, present, @@ -1167,7 +1188,7 @@ namespace pmon::util::metrics } // Helper: Calculate simulation start time (for animation error) - uint64_t CalculateSimStartTime( + uint64_t CalculateAnimationErrorSimStartTime( const SwapChainCoreState& chainState, const FrameData& present, AnimationErrorSource source) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h index 95db2f3a..4efd0e68 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h @@ -53,6 +53,7 @@ namespace pmon::util::metrics uint64_t nextScreenTime, bool isDisplayed, bool isAppFrame, + FrameType frameType, const SwapChainCoreState& chain); // Helper: Calculate CPU start time @@ -61,7 +62,7 @@ namespace pmon::util::metrics const FrameData& present); // Helper: Calculate simulation start time (for animation error) - uint64_t CalculateSimStartTime( + uint64_t CalculateAnimationErrorSimStartTime( const SwapChainCoreState& chainState, const FrameData& present, AnimationErrorSource source); diff --git a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp index ac6be17c..4cd85929 100644 --- a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp +++ b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp @@ -4,13 +4,26 @@ #include "UnifiedSwapChain.h" #include "../PresentData/PresentMonTraceConsumer.hpp" +#include + namespace pmon::util::metrics { + uint64_t UnifiedSwapChain::GetLastPresentQpc() const + { + return swapChain.lastPresent.has_value() ? swapChain.lastPresent->presentStartTime : 0; + } + + bool UnifiedSwapChain::IsPrunableBefore(uint64_t minTimestampQpc) const + { + auto const last = GetLastPresentQpc(); + return last != 0 && last < minTimestampQpc; + } + void UnifiedSwapChain::SanitizeDisplayedRepeatedPresents(FrameData& present) { - // Port of OutputThread.cpp::ReportMetrics() Remove Repeated flips pre-pass, - // but applied to FrameData (so we dont mutate PresentEvent). + // Port of OutputThread.cpp::ReportMetrics() �Remove Repeated flips� pre-pass, + // but applied to FrameData (so we don�t mutate PresentEvent). auto& d = present.displayed; for (size_t i = 0; i + 1 < d.size(); ) { const auto a = d[i].first; diff --git a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h index b2009d18..6663a9e7 100644 --- a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h +++ b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h @@ -27,6 +27,19 @@ namespace pmon::util::metrics std::vector Enqueue(FrameData present); + uint64_t GetLastPresentQpc() const; + bool IsPrunableBefore(uint64_t minTimestampQpc) const; + + // Frame statistics + float avgCPUDuration = 0.f; + float avgGPUDuration = 0.f; + float avgDisplayLatency = 0.f; + float avgDisplayedTime = 0.f; + float avgMsUntilDisplayed = 0.f; + float avgMsBetweenDisplayChange = 0.f; + double emaInput2FrameStartTime = 0.f; + double accumulatedInput2FrameStartTime = 0.f; + private: static void SanitizeDisplayedRepeatedPresents(FrameData& present); std::optional waitingDisplayed_; diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index 7d7c082a..f198e15d 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -713,7 +713,7 @@ namespace MetricsCoreTests } }; - TEST_CLASS(CalculateSimStartTimeTests) + TEST_CLASS(CalculateAnimationErrorSimStartTimeTests) { public: TEST_METHOD(UsesCpuStartSource) @@ -729,7 +729,7 @@ namespace MetricsCoreTests FrameData current{}; current.appSimStartTime = 5000; // Has appSim, but source is CpuStart - auto result = CalculateSimStartTime(swapChain, current, AnimationErrorSource::CpuStart); + auto result = CalculateAnimationErrorSimStartTime(swapChain, current, AnimationErrorSource::CpuStart); // Should use CPU start calculation: 1000 + 50 = 1050 Assert::AreEqual(1050ull, result); @@ -748,7 +748,7 @@ namespace MetricsCoreTests FrameData current{}; current.appSimStartTime = 5000; - auto result = CalculateSimStartTime(swapChain, current, AnimationErrorSource::AppProvider); + auto result = CalculateAnimationErrorSimStartTime(swapChain, current, AnimationErrorSource::AppProvider); // Should use appSimStartTime Assert::AreEqual(5000ull, result); @@ -767,7 +767,7 @@ namespace MetricsCoreTests FrameData current{}; current.pclSimStartTime = 6000; - auto result = CalculateSimStartTime(swapChain, current, AnimationErrorSource::PCLatency); + auto result = CalculateAnimationErrorSimStartTime(swapChain, current, AnimationErrorSource::PCLatency); // Should use pclSimStartTime Assert::AreEqual(6000ull, result); @@ -1220,7 +1220,30 @@ namespace MetricsCoreTests Assert::AreEqual(uint64_t(0), chain.lastDisplayedScreenTime); Assert::AreEqual(uint64_t(0), chain.lastDisplayedFlipDelay); } + + TEST_METHOD(ComputeMetricsForPresent_IntelXefg_Discarded_NoNext_ChainNotUpdated) + { + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + // 3x Intel_XEFG then 1 Application; no nextDisplayed + FrameData present = MakeFrame( + PresentResult::Discarded, + 30'000, 700, 40'000, + { + { FrameType::Intel_XEFG, 0 }, + }); + auto metrics = ComputeMetricsForPresent(qpc, present, nullptr, chain); + + // Should process 1 + Assert::AreEqual(size_t(1), metrics.size()); + // Chain update postponed until nextDisplayed + Assert::IsFalse(chain.lastPresent.has_value()); + Assert::IsFalse(chain.lastAppPresent.has_value()); + Assert::AreEqual(uint64_t(0), chain.lastDisplayedScreenTime); + Assert::AreEqual(uint64_t(0), chain.lastDisplayedFlipDelay); + } TEST_METHOD(ComputeMetricsForPresent_AmdAfmf_WithNext_AppProcessedAndUpdatesChain) { QpcConverter qpc(10'000'000, 0); diff --git a/PresentMon/Console.cpp b/PresentMon/Console.cpp index 7ff8f2af..13bc0a09 100644 --- a/PresentMon/Console.cpp +++ b/PresentMon/Console.cpp @@ -273,28 +273,28 @@ void UpdateConsole(uint32_t processId, ProcessInfo const& processInfo) ConsolePrint(L" %016llX", address); - if (chain.mLastPresent != nullptr) { + if (chain.mUnifiedSwapChain.swapChain.lastPresent.has_value()) { ConsolePrint(L" (%hs): SyncInterval=%d Flags=%d CPU=%.3fms (%.1f fps)", - RuntimeToString(chain.mLastPresent->Runtime), - chain.mLastPresent->SyncInterval, - chain.mLastPresent->PresentFlags, - chain.mAvgCPUDuration, - CalculateFPSForPrintf(chain.mAvgCPUDuration)); + RuntimeToString(chain.mUnifiedSwapChain.swapChain.lastPresent.value().runtime), + chain.mUnifiedSwapChain.swapChain.lastPresent.value().syncInterval, + chain.mUnifiedSwapChain.swapChain.lastPresent.value().presentFlags, + chain.mUnifiedSwapChain.avgCPUDuration, + CalculateFPSForPrintf(chain.mUnifiedSwapChain.avgCPUDuration)); if (args.mTrackDisplay) { ConsolePrint(L" Display=%.3fms (%.1f fps)", - chain.mAvgDisplayedTime, - CalculateFPSForPrintf(chain.mAvgDisplayedTime)); + chain.mUnifiedSwapChain.avgDisplayedTime, + CalculateFPSForPrintf(chain.mUnifiedSwapChain.avgDisplayedTime)); } if (args.mTrackGPU) { - ConsolePrint(L" GPU=%.3fms", chain.mAvgGPUDuration); + ConsolePrint(L" GPU=%.3fms", chain.mUnifiedSwapChain.avgGPUDuration); } if (args.mTrackDisplay) { ConsolePrint(L" Latency=%.3fms %hs", - chain.mAvgDisplayLatency, - PresentModeToString(chain.mLastPresent->PresentMode)); + chain.mUnifiedSwapChain.avgDisplayLatency, + PresentModeToString(chain.mUnifiedSwapChain.swapChain.lastPresent.value().presentMode)); } } diff --git a/PresentMon/OutputThread.cpp b/PresentMon/OutputThread.cpp index 387c388b..8ba95006 100644 --- a/PresentMon/OutputThread.cpp +++ b/PresentMon/OutputThread.cpp @@ -244,592 +244,6 @@ static void UpdateAverage(float* avg, double value) } } -static void UpdateChain( - SwapChainData* chain, - std::shared_ptr const& p) -{ - if (p->FinalState == PresentResult::Presented) { - if (p->Displayed.empty() == false) { - if (p->Displayed.back().first == FrameType::NotSet || - p->Displayed.back().first == FrameType::Application) { - - // If the chain animation error source has been set to either - // app provider or PCL latency then set the last displayed simulation start time and the - // first app simulation start time based on the animation error source type. - if (chain->mAnimationErrorSource == AnimationErrorSource::AppProvider) { - chain->mLastDisplayedSimStartTime = p->AppSimStartTime; - if (chain->mFirstAppSimStartTime == 0) { - // Received the first app sim start time. - chain->mFirstAppSimStartTime = p->AppSimStartTime; - } - chain->mLastDisplayedAppScreenTime = p->Displayed.back().second; - } else if (chain->mAnimationErrorSource == AnimationErrorSource::PCLatency) { - // In the case of PCLatency only set values if pcl sim start time is not zero. - if (p->PclSimStartTime != 0) { - chain->mLastDisplayedSimStartTime = p->PclSimStartTime; - if (chain->mFirstAppSimStartTime == 0) { - // Received the first app sim start time. - chain->mFirstAppSimStartTime = p->PclSimStartTime; - } - chain->mLastDisplayedAppScreenTime = p->Displayed.back().second; - } - } else { - // Currently sourcing animation error from CPU start time, however check - // to see if we have a valid app provider or PCL sim start time and set the - // new animation source and set the first app sim start time - if (p->AppSimStartTime != 0) { - chain->mAnimationErrorSource = AnimationErrorSource::AppProvider; - chain->mLastDisplayedSimStartTime = p->AppSimStartTime; - if (chain->mFirstAppSimStartTime == 0) { - // Received the first app sim start time. - chain->mFirstAppSimStartTime = p->AppSimStartTime; - } - chain->mLastDisplayedAppScreenTime = p->Displayed.back().second; - } else if (p->PclSimStartTime != 0) { - chain->mAnimationErrorSource = AnimationErrorSource::PCLatency; - chain->mLastDisplayedSimStartTime = p->PclSimStartTime; - if (chain->mFirstAppSimStartTime == 0) { - // Received the first app sim start time. - chain->mFirstAppSimStartTime = p->PclSimStartTime; - } - chain->mLastDisplayedAppScreenTime = p->Displayed.back().second; - } else { - if (chain->mLastAppPresent != nullptr) { - chain->mLastDisplayedSimStartTime = chain->mLastAppPresent->PresentStartTime + - chain->mLastAppPresent->TimeInPresent; - } - chain->mLastDisplayedAppScreenTime = p->Displayed.back().second; - } - } - } - } - // Want this to always be updated with the last displayed screen time regardless if the - // frame was generated or not - chain->mLastDisplayedScreenTime = p->Displayed.empty() ? 0 : p->Displayed.back().second; - // Update last flipDelay. For NV GPU, size of p->Displayed can only be empty or 1 - chain->mLastDisplayedFlipDelay = p->Displayed.empty() ? 0 : p->FlipDelay; - } - - if (p->Displayed.empty() == false) { - if (p->Displayed.back().first == FrameType::NotSet || - p->Displayed.back().first == FrameType::Application) { - chain->mLastAppPresent = p; - } - } else { - // If the last present was not displayed, we need to set the last app present to the last - // present. - chain->mLastAppPresent = p; - } - - // Set chain->mLastSimStartTime to either p->PclSimStartTime or p->AppSimStartTime depending on - // if either are not zero. If both are zero, do not set. - if (p->PclSimStartTime != 0) { - chain->mLastSimStartTime = p->PclSimStartTime; - } else if (p->AppSimStartTime != 0) { - chain->mLastSimStartTime = p->AppSimStartTime; - } - - // Want this to always be updated with the last present regardless if the - // frame was generated or not - chain->mLastPresent = p; -} - -static void AdjustScreenTimeForCollapsedPresentNV1( - SwapChainData* chain, - std::shared_ptr const& p, - uint64_t& screenTime) -{ - if (chain->mLastDisplayedFlipDelay > 0 && (chain->mLastDisplayedScreenTime > screenTime)) { - // If chain->mLastDisplayedScreenTime that is adjusted by flipDelay is larger than screenTime, - // it implies the last displayed present is a collapsed present, or a runt frame. - // So we adjust the screenTime and flipDelay of screenTime, - // effectively making screenTime equals to chain->mLastDisplayedScreenTime. - - // Cast away constness of p to adjust the screenTime and flipDelay. - PresentEvent* pp = const_cast(p.get()); - if (!pp->Displayed.empty()) { - pp->FlipDelay += chain->mLastDisplayedScreenTime - screenTime; - pp->Displayed[0].second = chain->mLastDisplayedScreenTime; - screenTime = pp->Displayed[0].second; - } - } -} - -PM_UNUSED_FN -static void ReportMetrics1( - PMTraceSession const& pmSession, - ProcessInfo* processInfo, - SwapChainData* chain, - std::shared_ptr const& p, - bool isRecording, - bool computeAvg) -{ - bool displayed = p->FinalState == PresentResult::Presented; - - uint64_t screenTime = p->Displayed.empty() ? 0 : p->Displayed[0].second; - - // Special handling for NV flipDelay - AdjustScreenTimeForCollapsedPresentNV1(chain, p, screenTime); - - FrameMetrics1 metrics; - metrics.msBetweenPresents = chain->mLastPresent == nullptr ? 0 : pmSession.TimestampDeltaToUnsignedMilliSeconds(chain->mLastPresent->PresentStartTime, p->PresentStartTime); - metrics.msInPresentApi = pmSession.TimestampDeltaToMilliSeconds(p->TimeInPresent); - metrics.msUntilRenderComplete = pmSession.TimestampDeltaToMilliSeconds(p->PresentStartTime, p->ReadyTime); - metrics.msUntilDisplayed = !displayed ? 0 : pmSession.TimestampDeltaToUnsignedMilliSeconds(p->PresentStartTime, screenTime); - metrics.msBetweenDisplayChange = !displayed || chain->mLastDisplayedScreenTime == 0 ? 0 : pmSession.TimestampDeltaToUnsignedMilliSeconds(chain->mLastDisplayedScreenTime, screenTime); - metrics.msUntilRenderStart = pmSession.TimestampDeltaToMilliSeconds(p->PresentStartTime, p->GPUStartTime); - metrics.msGPUDuration = pmSession.TimestampDeltaToMilliSeconds(p->GPUDuration); - metrics.msVideoDuration = pmSession.TimestampDeltaToMilliSeconds(p->GPUVideoDuration); - metrics.msSinceInput = p->InputTime == 0 ? 0 : pmSession.TimestampDeltaToMilliSeconds(p->PresentStartTime - p->InputTime); - metrics.qpcScreenTime = screenTime; - metrics.msFlipDelay = p->FlipDelay ? pmSession.TimestampDeltaToMilliSeconds(p->FlipDelay) : 0; - - if (isRecording) { - UpdateCsv(pmSession, processInfo, *p, metrics); - } - - if (computeAvg) { - UpdateAverage(&chain->mAvgCPUDuration, metrics.msBetweenPresents); - UpdateAverage(&chain->mAvgGPUDuration, metrics.msGPUDuration); - if (metrics.msUntilDisplayed > 0) { - UpdateAverage(&chain->mAvgDisplayLatency, metrics.msUntilDisplayed); - if (metrics.msBetweenDisplayChange > 0) { - UpdateAverage(&chain->mAvgDisplayedTime, metrics.msBetweenDisplayChange); - } - } - } - - UpdateChain(chain, p); -} - -static void CalculateAnimationTime( - PMTraceSession const& pmSession, - const uint64_t& firstAppSimStartTime, - const uint64_t& currentSimTime, - double& animationTime) { - - auto firstSimStartTime = firstAppSimStartTime != 0 ? firstAppSimStartTime : pmSession.mStartTimestamp.QuadPart; - if (currentSimTime > firstSimStartTime) { - animationTime = pmSession.TimestampDeltaToMilliSeconds(firstSimStartTime, currentSimTime); - } else { - animationTime = 0.; - } -} - - -static void AdjustScreenTimeForCollapsedPresentNV( - std::shared_ptr const& p, - PresentEvent const* nextDisplayedPresent, - uint64_t& screenTime, - uint64_t& nextScreenTime) -{ - // nextDisplayedPresent should always be non-null for NV GPU. - if (p->FlipDelay && screenTime > nextScreenTime && nextDisplayedPresent) { - // If screenTime that is adjusted by flipDelay is larger than nextScreenTime, - // it implies this present is a collapsed present, or a runt frame. - // So we adjust the screenTime and flipDelay of nextDisplayedPresent, - // effectively making nextScreenTime equals to screenTime. - - // Cast away constness of nextDisplayedPresent to adjust the screenTime and flipDelay. - PresentEvent* nextDispPresent = const_cast(nextDisplayedPresent); - nextDispPresent->FlipDelay += (screenTime - nextScreenTime); - nextScreenTime = screenTime; - nextDispPresent->Displayed[0].second = nextScreenTime; - } -} - -static void ReportMetricsHelper( - PMTraceSession const& pmSession, - ProcessInfo* processInfo, - SwapChainData* chain, - std::shared_ptr const& p, - PresentEvent const* nextDisplayedPresent, - bool isRecording, - bool computeAvg) -{ - // Figure out what display index to start processing. - // - // The following cases are expected: - // p.Displayed empty and nextDisplayedPresent == nullptr: process p as not displayed - // p.Displayed with size N and nextDisplayedPresent == nullptr: process p.Displayed[0..N-2] as displayed, postponing N-1 - // p.Displayed with size N and nextDisplayedPresent != nullptr: process p.Displayed[N-1] as displayed - auto displayCount = p->Displayed.size(); - bool displayed = p->FinalState == PresentResult::Presented && displayCount > 0; - size_t displayIndex = displayed && nextDisplayedPresent != nullptr ? displayCount - 1 : 0; - - // Figure out what display index to attribute cpu work, gpu work, animation error, and input - // latency to. Start looking from the current display index. - size_t appIndex = std::numeric_limits::max(); - if (displayCount > 0) { - for (size_t i = displayIndex; i < displayCount; ++i) { - if (p->Displayed[i].first == FrameType::NotSet || - p->Displayed[i].first == FrameType::Application) { - appIndex = i; - break; - } - } - } else { - // If there are no displayed frames - appIndex = 0; - } - - do { - // PB = PresentStartTime - // PE = PresentEndTime - // D = ScreenTime - // - // chain->mLastPresent: PB--PE----D - // p: | PB--PE----D - // ... | | | | PB--PE - // nextDisplayedPresent: | | | | PB--PE----D - // | | | | | - // mCPUStart/mCPUBusy: |------->| | | | - // mCPUWait: |-->| | | - // mDisplayLatency: |----------------->| | - // mDisplayedTime: |---------------------->| - - // Lookup the ScreenTime and next ScreenTime - uint64_t screenTime = 0; - uint64_t nextScreenTime = 0; - if (displayed) { - screenTime = p->Displayed[displayIndex].second; - - if (displayIndex + 1 < displayCount) { - nextScreenTime = p->Displayed[displayIndex + 1].second; - } else if (nextDisplayedPresent != nullptr) { - nextScreenTime = nextDisplayedPresent->Displayed[0].second; - } else { - return; - } - } - - double msGPUDuration = 0.0; - - FrameMetrics metrics{}; - metrics.mCPUStart = 0; - - // Calculate these metrics for every present - metrics.mTimeInSeconds = p->PresentStartTime; - metrics.mMsBetweenPresents = chain->mLastPresent == nullptr ? 0 : - pmSession.TimestampDeltaToUnsignedMilliSeconds(chain->mLastPresent->PresentStartTime, p->PresentStartTime); - metrics.mMsInPresentApi = pmSession.TimestampDeltaToMilliSeconds(p->TimeInPresent); - metrics.mMsUntilRenderComplete = pmSession.TimestampDeltaToMilliSeconds(p->PresentStartTime, p->ReadyTime); - - if (chain->mLastAppPresent) { - if (chain->mLastAppPresent->AppPropagatedPresentStartTime != 0) { - metrics.mCPUStart = chain->mLastAppPresent->AppPropagatedPresentStartTime + chain->mLastAppPresent->AppPropagatedTimeInPresent; - } - else { - metrics.mCPUStart = chain->mLastAppPresent->PresentStartTime + chain->mLastAppPresent->TimeInPresent; - } - } else { - metrics.mCPUStart = chain->mLastPresent == nullptr ? 0 : - chain->mLastPresent->PresentStartTime + chain->mLastPresent->TimeInPresent; - } - - if (displayIndex == appIndex) { - uint64_t gpuStartTime = 0; - if (p->AppPropagatedPresentStartTime != 0) { - msGPUDuration = pmSession.TimestampDeltaToUnsignedMilliSeconds(p->AppPropagatedGPUStartTime, p->AppPropagatedReadyTime); - gpuStartTime = p->AppPropagatedGPUStartTime; - metrics.mMsCPUBusy = pmSession.TimestampDeltaToUnsignedMilliSeconds(metrics.mCPUStart, p->AppPropagatedPresentStartTime); - metrics.mMsCPUWait = pmSession.TimestampDeltaToMilliSeconds(p->AppPropagatedTimeInPresent); - metrics.mMsGPULatency = pmSession.TimestampDeltaToUnsignedMilliSeconds(metrics.mCPUStart, gpuStartTime); - metrics.mMsGPUBusy = pmSession.TimestampDeltaToMilliSeconds(p->AppPropagatedGPUDuration); - metrics.mMsVideoBusy = pmSession.TimestampDeltaToMilliSeconds(p->AppPropagatedGPUVideoDuration); - metrics.mMsGPUWait = std::max(0.0, msGPUDuration - metrics.mMsGPUBusy); - } else { - msGPUDuration = pmSession.TimestampDeltaToUnsignedMilliSeconds(p->GPUStartTime, p->ReadyTime); - gpuStartTime = p->GPUStartTime; - metrics.mMsCPUBusy = pmSession.TimestampDeltaToUnsignedMilliSeconds(metrics.mCPUStart, p->PresentStartTime); - metrics.mMsCPUWait = pmSession.TimestampDeltaToMilliSeconds(p->TimeInPresent); - metrics.mMsGPULatency = pmSession.TimestampDeltaToUnsignedMilliSeconds(metrics.mCPUStart, gpuStartTime); - metrics.mMsGPUBusy = pmSession.TimestampDeltaToMilliSeconds(p->GPUDuration); - metrics.mMsVideoBusy = pmSession.TimestampDeltaToMilliSeconds(p->GPUVideoDuration); - metrics.mMsGPUWait = std::max(0.0, msGPUDuration - metrics.mMsGPUBusy); - } - - // Need both AppSleepStart and AppSleepEnd to calculate XellSleep - metrics.mMsInstrumentedSleep = (p->AppSleepEndTime == 0 || p->AppSleepStartTime == 0) ? 0 : - pmSession.TimestampDeltaToUnsignedMilliSeconds(p->AppSleepStartTime, p->AppSleepEndTime); - // If there isn't a valid sleep end time use the sim start time - auto InstrumentedStartTime = p->AppSleepEndTime != 0 ? p->AppSleepEndTime : p->AppSimStartTime; - // If neither the sleep end time or sim start time is valid, there is no - // way to calculate the Xell Gpu latency - metrics.mMsInstrumentedGpuLatency = InstrumentedStartTime == 0 ? 0 : - pmSession.TimestampDeltaToUnsignedMilliSeconds(InstrumentedStartTime, gpuStartTime); - - // If we have both a valid pcl sim start time and a valid app sim start time, we use the pcl sim start time. - if (p->PclSimStartTime != 0) { - metrics.mMsBetweenSimStarts = pmSession.TimestampDeltaToUnsignedMilliSeconds(chain->mLastSimStartTime, p->PclSimStartTime); - } else if (p->AppSimStartTime != 0) { - metrics.mMsBetweenSimStarts = pmSession.TimestampDeltaToUnsignedMilliSeconds(chain->mLastSimStartTime, p->AppSimStartTime); - } - } else { - metrics.mMsCPUBusy = 0; - metrics.mMsCPUWait = 0; - metrics.mMsGPULatency = 0; - metrics.mMsGPUBusy = 0; - metrics.mMsVideoBusy = 0; - metrics.mMsGPUWait = 0; - metrics.mMsInstrumentedSleep = 0; - metrics.mMsInstrumentedGpuLatency = 0; - metrics.mMsBetweenSimStarts = 0; - } - - // If the frame was displayed regardless of how it was produced, calculate the following - // metrics - if (displayed) { - // Special handling for NV flipDelay - AdjustScreenTimeForCollapsedPresentNV(p, nextDisplayedPresent, screenTime, nextScreenTime); - - // Calculate the various display metrics - metrics.mMsFlipDelay = p->FlipDelay ? pmSession.TimestampDeltaToMilliSeconds(p->FlipDelay) : 0; - metrics.mMsDisplayLatency = pmSession.TimestampDeltaToUnsignedMilliSeconds(metrics.mCPUStart, screenTime); - metrics.mMsDisplayedTime = pmSession.TimestampDeltaToUnsignedMilliSeconds(screenTime, nextScreenTime); - metrics.mMsUntilDisplayed = pmSession.TimestampDeltaToUnsignedMilliSeconds(p->PresentStartTime, screenTime); - metrics.mMsBetweenDisplayChange = chain->mLastDisplayedScreenTime == 0 ? 0 : - pmSession.TimestampDeltaToUnsignedMilliSeconds(chain->mLastDisplayedScreenTime, screenTime); - metrics.mScreenTime = screenTime; - - // If we have AppRenderSubmitStart calculate the render latency - metrics.mMsInstrumentedRenderLatency = p->AppRenderSubmitStartTime == 0 ? 0 : - pmSession.TimestampDeltaToUnsignedMilliSeconds(p->AppRenderSubmitStartTime, screenTime); - metrics.mMsReadyTimeToDisplayLatency = pmSession.TimestampDeltaToUnsignedMilliSeconds(p->ReadyTime, screenTime); - // If there isn't a valid sleep end time use the sim start time - auto InstrumentedStartTime = p->AppSleepEndTime != 0 ? p->AppSleepEndTime : p->AppSimStartTime; - // If neither the sleep end time or sim start time is valid, there is no - // way to calculate the Xell Gpu latency - metrics.mMsInstrumentedLatency = InstrumentedStartTime == 0 ? 0 : - pmSession.TimestampDeltaToUnsignedMilliSeconds(InstrumentedStartTime, screenTime); - - metrics.mMsPcLatency = 0.f; - // Check to see if we have a valid pc latency sim start time. - if (p->PclSimStartTime != 0) { - if (p->PclInputPingTime == 0) { - if (chain->mAccumulatedInput2FrameStartTime != 0) { - // This frame was displayed but we don't have a pc latency input time. However, there is accumulated time - // so there is a pending input that will now hit the screen. Add in the time from the last not - // displayed pc simulation start to this frame's pc simulation start. - chain->mAccumulatedInput2FrameStartTime += - pmSession.TimestampDeltaToUnsignedMilliSeconds(chain->mLastReceivedNotDisplayedPclSimStart, p->PclSimStartTime); - // Add all of the accumlated time to the average input to frame start time. - chain->mEmaInput2FrameStartTime = pmon::util::CalculateEma( - chain->mEmaInput2FrameStartTime, - chain->mAccumulatedInput2FrameStartTime, - 0.1); - // Reset the tracking variables for when we have a dropped frame with a pc latency input - chain->mAccumulatedInput2FrameStartTime = 0.f; - chain->mLastReceivedNotDisplayedPclSimStart = 0; - } - } else { - chain->mEmaInput2FrameStartTime = pmon::util::CalculateEma( - chain->mEmaInput2FrameStartTime, - pmSession.TimestampDeltaToUnsignedMilliSeconds(p->PclInputPingTime, p->PclSimStartTime), - 0.1); - } - } - // If we have a non-zero average input to frame start time and a PC Latency simulation - // start time calculate the PC Latency - auto simStartTime = p->PclSimStartTime != 0 ? p->PclSimStartTime : chain->mLastSimStartTime; - if (chain->mEmaInput2FrameStartTime != 0.f && simStartTime != 0) { - metrics.mMsPcLatency = chain->mEmaInput2FrameStartTime + - pmSession.TimestampDeltaToMilliSeconds(simStartTime, screenTime); - } - } else { - metrics.mMsDisplayLatency = 0; - metrics.mMsDisplayedTime = 0; - metrics.mMsUntilDisplayed = 0; - metrics.mMsBetweenDisplayChange = 0; - metrics.mScreenTime = 0; - metrics.mMsInstrumentedLatency = 0; - metrics.mMsInstrumentedRenderLatency = 0; - metrics.mMsReadyTimeToDisplayLatency = 0; - metrics.mMsPcLatency = 0; - if (p->PclSimStartTime != 0) { - if (p->PclInputPingTime != 0) { - // This frame was dropped but we have valid pc latency input and simulation start - // times. Calculate the initial input to sim start time. - chain->mAccumulatedInput2FrameStartTime = - pmSession.TimestampDeltaToUnsignedMilliSeconds(p->PclInputPingTime, p->PclSimStartTime); - } else if (chain->mAccumulatedInput2FrameStartTime != 0.f) { - // This frame was also dropped and there is no pc latency input time. However, since we have - // accumulated time this means we have a pending input that has had multiple dropped frames - // and has not yet hit the screen. Calculate the time between the last not displayed sim start and - // this sim start and add it to our accumulated total - chain->mAccumulatedInput2FrameStartTime += - pmSession.TimestampDeltaToUnsignedMilliSeconds(chain->mLastReceivedNotDisplayedPclSimStart, p->PclSimStartTime); - } - chain->mLastReceivedNotDisplayedPclSimStart = p->PclSimStartTime; - } - } - - // The following metrics use both the frame's displayed and origin information. - metrics.mMsClickToPhotonLatency = 0; - metrics.mMsAllInputPhotonLatency = 0; - metrics.mMsInstrumentedInputTime = 0; - metrics.mMsAnimationError = std::nullopt; - metrics.mAnimationTime = std::nullopt; - - if (displayIndex == appIndex) { - if (displayed) { - // For all input device metrics check to see if there were any previous device input times - // that were attached to a dropped frame and if so use the last received times for the - // metric calculations - auto updatedInputTime = chain->mLastReceivedNotDisplayedAllInputTime == 0 ? 0 : - pmSession.TimestampDeltaToUnsignedMilliSeconds(chain->mLastReceivedNotDisplayedAllInputTime, screenTime); - metrics.mMsAllInputPhotonLatency = p->InputTime == 0 ? updatedInputTime : - pmSession.TimestampDeltaToUnsignedMilliSeconds(p->InputTime, screenTime); - - updatedInputTime = chain->mLastReceivedNotDisplayedMouseClickTime == 0 ? 0 : - pmSession.TimestampDeltaToUnsignedMilliSeconds(chain->mLastReceivedNotDisplayedMouseClickTime, screenTime); - metrics.mMsClickToPhotonLatency = p->MouseClickTime == 0 ? updatedInputTime : - pmSession.TimestampDeltaToUnsignedMilliSeconds(p->MouseClickTime, screenTime); - - updatedInputTime = chain->mLastReceivedNotDisplayedAppProviderInputTime == 0 ? 0 : - pmSession.TimestampDeltaToUnsignedMilliSeconds(chain->mLastReceivedNotDisplayedAppProviderInputTime, screenTime); - metrics.mMsInstrumentedInputTime = p->AppInputSample.first == 0 ? updatedInputTime : - pmSession.TimestampDeltaToUnsignedMilliSeconds(p->AppInputSample.first, screenTime); - - // Reset all last received device times - chain->mLastReceivedNotDisplayedAllInputTime = 0; - chain->mLastReceivedNotDisplayedMouseClickTime = 0; - chain->mLastReceivedNotDisplayedAppProviderInputTime = 0; - - // Next calculate the animation error and animation time. First calculate the simulation - // start time. Simulation start can be either an app provided sim start time via the provider or - // PCL stats or, if not present,the cpu start. - uint64_t simStartTime = 0; - if (chain->mAnimationErrorSource == AnimationErrorSource::PCLatency) { - // If the pcl latency is the source of the animation error then use the pcl sim start time. - simStartTime = p->PclSimStartTime; - } else if (chain->mAnimationErrorSource == AnimationErrorSource::AppProvider) { - // If the app provider is the source of the animation error then use the app sim start time. - simStartTime = p->AppSimStartTime; - } else if (chain->mAnimationErrorSource == AnimationErrorSource::CpuStart) { - // If the cpu start time is the source of the animation error then use the cpu start time. - simStartTime = metrics.mCPUStart; - } - - if (chain->mLastDisplayedSimStartTime != 0) { - // If the simulation start time is less than the last displayed simulation start time it means - // we are transitioning to app provider events. - if (simStartTime > chain->mLastDisplayedSimStartTime) { - metrics.mMsAnimationError = pmSession.TimestampDeltaToMilliSeconds(screenTime - chain->mLastDisplayedAppScreenTime, - simStartTime - chain->mLastDisplayedSimStartTime); - } - } - // If we have a value in app sim start or pcl sim start and we haven't set the first - // sim start time then we are transitioning from using cpu start to - // an application provided timestamp. Set the animation time to zero - // for the first frame. - if ((p->AppSimStartTime != 0 || p->PclSimStartTime != 0) && chain->mFirstAppSimStartTime == 0) { - metrics.mAnimationTime = 0.; - } - else { - double animationTime = 0.; - CalculateAnimationTime(pmSession, chain->mFirstAppSimStartTime, simStartTime, animationTime); - metrics.mAnimationTime = animationTime; - } - } else { - if (p->InputTime != 0) { - chain->mLastReceivedNotDisplayedAllInputTime = p->InputTime; - } - if (p->MouseClickTime != 0) { - chain->mLastReceivedNotDisplayedMouseClickTime = p->MouseClickTime; - } - if (p->AppInputSample.first != 0) { - chain->mLastReceivedNotDisplayedAppProviderInputTime = p->AppInputSample.first; - } - } - } - - - if (p->Displayed.empty()) { - metrics.mFrameType = FrameType::NotSet; - } else { - metrics.mFrameType = p->Displayed[displayIndex].first; - } - - if (isRecording) { - UpdateCsv(pmSession, processInfo, *p, metrics); - } - - if (computeAvg) { - if (displayIndex == appIndex) { - UpdateAverage(&chain->mAvgCPUDuration, metrics.mMsCPUBusy + metrics.mMsCPUWait); - UpdateAverage(&chain->mAvgGPUDuration, msGPUDuration); - } - if (displayed) { - UpdateAverage(&chain->mAvgDisplayLatency, metrics.mMsDisplayLatency); - UpdateAverage(&chain->mAvgDisplayedTime, metrics.mMsDisplayedTime); - UpdateAverage(&chain->mAvgMsUntilDisplayed, metrics.mMsUntilDisplayed); - UpdateAverage(&chain->mAvgMsBetweenDisplayChange, metrics.mMsBetweenDisplayChange); - } - } - - displayIndex += 1; - } while (displayIndex < displayCount); - - UpdateChain(chain, p); -} - -PM_UNUSED_FN -static void ReportMetrics( - PMTraceSession const& pmSession, - ProcessInfo* processInfo, - SwapChainData* chain, - std::shared_ptr const& p, - bool isRecording, - bool computeAvg) -{ - // Remove Repeated flips if they are in Application->Repeated or Repeated->Application sequences. - for (size_t i = 0, n = p->Displayed.size(); i + 1 < n; ) { - if (p->Displayed[i].first == FrameType::Application && p->Displayed[i + 1].first == FrameType::Repeated) { - p->Displayed.erase(p->Displayed.begin() + i + 1); - n -= 1; - } else if (p->Displayed[i].first == FrameType::Repeated && p->Displayed[i + 1].first == FrameType::Application) { - p->Displayed.erase(p->Displayed.begin() + i); - n -= 1; - } else { - i += 1; - } - } - - // For the chain's first present, we just initialize mLastPresent to give a baseline for the - // first frame. - if (chain->mLastPresent == nullptr) { - UpdateChain(chain, p); - return; - } - - // If chain->mPendingPresents is non-empty, then it contains a displayed present followed by - // some number of discarded presents. If the displayed present has multiple Displayed entries, - // all but the last have already been handled. - // - // If p is displayed, then we can complete all pending presents, and complete any flips in p - // except for the last one, but then we have to add p to the pending list to wait for the next - // displayed frame. - // - // If p is not displayed, we can process it now unless it is blocked behind an earlier present - // waiting for the next displayed one, in which case we need to add it to the pending list as - // well. - if (p->FinalState == PresentResult::Presented) { - for (auto const& p2 : chain->mPendingPresents) { - ReportMetricsHelper(pmSession, processInfo, chain, p2, p.get(), isRecording, computeAvg); - } - ReportMetricsHelper(pmSession, processInfo, chain, p, nullptr, isRecording, computeAvg); - chain->mPendingPresents.clear(); - chain->mPendingPresents.push_back(p); - } else { - if (chain->mPendingPresents.empty()) { - ReportMetricsHelper(pmSession, processInfo, chain, p, nullptr, isRecording, computeAvg); - } else { - chain->mPendingPresents.push_back(p); - } - } -} - static void PruneOldSwapChainData( PMTraceSession const& pmSession, uint64_t latestTimestamp) @@ -847,8 +261,8 @@ static void PruneOldSwapChainData( auto processInfo = &pair.second; // Check if this is DWM process - const bool isDwmProcess = - util::str::ToLower(processInfo->mModuleName).contains(L"dwm.exe"); + const auto processNameLower = util::str::ToLower(processInfo->mModuleName); + const bool isDwmProcess = (processNameLower.find(L"dwm.exe") != std::wstring::npos); for (auto ii = processInfo->mSwapChain.begin(), ie = processInfo->mSwapChain.end(); ii != ie; ) { auto swapChainAddress = ii->first; @@ -857,7 +271,14 @@ static void PruneOldSwapChainData( // Don't prune DWM swap chains with address 0x0 bool shouldSkipPruning = isDwmProcess && swapChainAddress == 0x0; - if (!shouldSkipPruning && chain->mLastPresent && chain->mLastPresent->PresentStartTime < minTimestamp) { + // Unified pruning: never prune while the unified swapchain still has + // buffered work (e.g., waiting for nextDisplayed). + bool shouldPrune = false; + if (!shouldSkipPruning) { + shouldPrune = chain->mUnifiedSwapChain.IsPrunableBefore(minTimestamp); + } + + if (shouldPrune) { ii = processInfo->mSwapChain.erase(ii); } else { @@ -929,8 +350,7 @@ static bool GetPresentProcessInfo( } auto chain = &processInfo->mSwapChain[presentEvent->SwapChainAddress]; - if (chain->mLastPresent == nullptr) { - UpdateChain(chain, presentEvent); + if (!chain->mUnifiedSwapChain.swapChain.lastPresent.has_value()) { using namespace pmon::util::metrics; chain->mUnifiedSwapChain.SeedFromFirstPresent(FrameData::CopyFrameData(presentEvent)); return true; @@ -938,7 +358,7 @@ static bool GetPresentProcessInfo( *outProcessInfo = processInfo; *outChain = chain; - *outPresentTime = chain->mLastPresent->PresentStartTime; + *outPresentTime = chain->mUnifiedSwapChain.GetLastPresentQpc(); return false; } @@ -964,19 +384,6 @@ static void ProcessRecordingToggle( } } -static bool ShouldUpdateLegacyChain( - pmon::util::metrics::FrameData const& p, - std::optional const& nextDisplayed) -{ - // Mirrors legacy UpdateChain timing and the unified calculators update rules: - // - Not presented: update immediately - // - Presented + has nextDisplayed: update (Case 3 finalizes) - // - Presented + no nextDisplayed: update only if DisplayedCount > 1 (Case 2 processed an instance) - if (p.finalState != PresentResult::Presented) return true; - if (nextDisplayed.has_value()) return true; - return p.displayed.size() > 1; -} - static void AdjustScreenTimeForCollapsedPresentNV1_Unified( pmon::util::metrics::SwapChainCoreState const& s, pmon::util::metrics::FrameData const& p, @@ -1051,13 +458,13 @@ static void ReportMetrics1Unified( } if (computeAvg) { - UpdateAverage(&chain->mAvgCPUDuration, metrics.msBetweenPresents); - UpdateAverage(&chain->mAvgGPUDuration, metrics.msGPUDuration); + UpdateAverage(&chain->mUnifiedSwapChain.avgCPUDuration, metrics.msBetweenPresents); + UpdateAverage(&chain->mUnifiedSwapChain.avgGPUDuration, metrics.msGPUDuration); if (metrics.msUntilDisplayed > 0) { - UpdateAverage(&chain->mAvgDisplayLatency, metrics.msUntilDisplayed); + UpdateAverage(&chain->mUnifiedSwapChain.avgDisplayLatency, metrics.msUntilDisplayed); if (metrics.msBetweenDisplayChange > 0) { - UpdateAverage(&chain->mAvgDisplayedTime, metrics.msBetweenDisplayChange); + UpdateAverage(&chain->mUnifiedSwapChain.avgDisplayedTime, metrics.msBetweenDisplayChange); } } } @@ -1149,7 +556,7 @@ static void ProcessEvents( // Do we need to emit metrics for this present? const bool emit = (isRecording || computeAvg); - for (auto const& it : ready) { + for (auto& it : ready) { // Build FrameData copies for the unified calculator state-advance (and V2 metrics). using namespace pmon::util::metrics; @@ -1184,28 +591,17 @@ static void ProcessEvents( } if (computeAvg) { - UpdateAverage(&chain->mAvgCPUDuration, m.msCPUBusy + m.msCPUWait); + UpdateAverage(&chain->mUnifiedSwapChain.avgCPUDuration, m.msCPUBusy + m.msCPUWait); if (m.msUntilDisplayed > 0) { - UpdateAverage(&chain->mAvgDisplayLatency, m.msDisplayLatency); - UpdateAverage(&chain->mAvgDisplayedTime, m.msDisplayedTime); - UpdateAverage(&chain->mAvgMsUntilDisplayed, m.msUntilDisplayed); - UpdateAverage(&chain->mAvgMsBetweenDisplayChange, m.msBetweenDisplayChange); + UpdateAverage(&chain->mUnifiedSwapChain.avgDisplayLatency, m.msDisplayLatency); + UpdateAverage(&chain->mUnifiedSwapChain.avgDisplayedTime, m.msDisplayedTime); + UpdateAverage(&chain->mUnifiedSwapChain.avgMsUntilDisplayed, m.msUntilDisplayed); + UpdateAverage(&chain->mUnifiedSwapChain.avgMsBetweenDisplayChange, m.msBetweenDisplayChange); } } } } } - - // Temporary compatibility shim: keep legacy swapchain fields in sync enough for pruning/etc. - if (ShouldUpdateLegacyChain(it.present, it.nextDisplayed)) { - UpdateChain(chain, presentEvent); - } - } - - // If not emitting and nothing became ready (e.g., queued behind displayed), legacy behavior still - // updated the chain with the raw present. Keep that until Step 3 removes legacy dependencies. - if (!emit && ready.empty()) { - UpdateChain(chain, presentEvent); } } diff --git a/PresentMon/PresentMon.args.json b/PresentMon/PresentMon.args.json index 0ca03c6e..3b3942b9 100644 --- a/PresentMon/PresentMon.args.json +++ b/PresentMon/PresentMon.args.json @@ -68,11 +68,15 @@ }, { "Id": "8f014852-6512-466b-99cb-0a8724c36862", - "Command": "--output_file gold_5.csv" + "Command": "--output_file \"E:\\EtlTesting\\ETLDebugging\\test_case_8_unified-fid.csv\"" }, { "Id": "1606ee3e-ac1c-4fea-9a87-736864dd1f00", - "Command": "--etl_file \"C:\\Users\\Chili\\Desktop\\cpp\\ipm-relay\\Tests\\Gold\\test_case_5.etl\"" + "Command": "--etl_file \"E:\\EtlTesting\\GoldETLTests-External\\test_case_8.etl\"" + }, + { + "Id": "abf6cf88-1afe-4b6d-ac24-71ba39c9615a", + "Command": "--etl_file \"D:\\source\\repos\\drivers.gpu.tools.presentmon\\Tests\\Gold\\test_case_0.etl\"" }, { "Id": "8dc2827c-9d81-4c64-8596-ea11dbd81c8f", @@ -105,6 +109,18 @@ { "Id": "892a1932-a81e-4f5c-b30f-899e6884457d", "Command": "--track_frame_type" + }, + { + "Id": "3f809034-342e-40c3-a24a-c836e5c0ae29", + "Command": "--track_app_timing" + }, + { + "Id": "5e1344fb-cd04-46d7-a2c7-7b69faf0101a", + "Command": "--track_pc_latency" + }, + { + "Id": "279d1ed9-07a1-4217-a370-3d058d565a49", + "Command": "--v1_metrics" } ] } \ No newline at end of file diff --git a/PresentMon/PresentMon.hpp b/PresentMon/PresentMon.hpp index 908f75e6..4dfbc23a 100644 --- a/PresentMon/PresentMon.hpp +++ b/PresentMon/PresentMon.hpp @@ -38,10 +38,8 @@ which is controlled from MainThread based on user input or timer. #include #include -#if defined(_MSC_VER) -#define PM_UNUSED_FN __pragma(warning(suppress : 4505)) // unreferenced local function removed -#else -#define PM_UNUSED_FN +#ifndef PM_KEEP_LEGACY_SWAPCHAIN_DATA +#define PM_KEEP_LEGACY_SWAPCHAIN_DATA 0 #endif // Verbosity of console output for normal operation: @@ -193,6 +191,7 @@ struct FrameMetrics2 { // - pending presents whose metrics cannot be computed until future presents are received, // - exponential averages of key metrics displayed in console output. struct SwapChainData { +#if PM_KEEP_LEGACY_SWAPCHAIN_DATA // Pending presents waiting for the next displayed present. std::vector> mPendingPresents; @@ -237,6 +236,7 @@ struct SwapChainData { // Internal NVIDIA Metrics uint64_t mLastDisplayedFlipDelay = 0; +#endif // Unified swap chain for unfied metrics calculations pmon::util::metrics::UnifiedSwapChain mUnifiedSwapChain; diff --git a/Tests/GoldEtlCsvTests.cpp b/Tests/GoldEtlCsvTests.cpp index 494ad4d9..adcc2300 100644 --- a/Tests/GoldEtlCsvTests.cpp +++ b/Tests/GoldEtlCsvTests.cpp @@ -92,7 +92,11 @@ class Tests : public ::testing::Test, TestArgs { const char* goldDecimalAddr = strchr(b, '.'); size_t testDecimalNumbersCount = testDecimalAddr == nullptr ? 0 : ((a + strlen(a)) - testDecimalAddr - 1); size_t goldDecimalNumbersCount = goldDecimalAddr == nullptr ? 0 : ((b + strlen(b)) - goldDecimalAddr - 1); - double threshold = pow(0.1, std::min(testDecimalNumbersCount, goldDecimalNumbersCount)); + size_t decimals = std::min(testDecimalNumbersCount, goldDecimalNumbersCount); + // Cap precision used for tolerance to avoid ultra-long double-to-string artifacts + // (e.g., 100.06490000000001 vs 100.06489999999999) + decimals = std::min(decimals, size_t(9)); + double threshold = pow(0.1, decimals); double difference = testNumber - goldNumber; if (difference > -threshold && difference < threshold) { From 7535b4a16818efe468d489e0d341c6a8d4af42ec Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 24 Dec 2025 08:17:17 +0900 Subject: [PATCH 078/205] enable logging for interim tests --- .../PresentMonAPI2Tests/Logging.cpp | 138 +++++++++++++++++- IntelPresentMon/PresentMonAPI2Tests/Logging.h | 7 + .../PresentMonAPI2Tests/TestProcess.h | 4 +- 3 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 IntelPresentMon/PresentMonAPI2Tests/Logging.h diff --git a/IntelPresentMon/PresentMonAPI2Tests/Logging.cpp b/IntelPresentMon/PresentMonAPI2Tests/Logging.cpp index 4b29c5ec..86011942 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/Logging.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/Logging.cpp @@ -1,11 +1,141 @@ +#include "Logging.h" + +#include "../CommonUtilities/log/Log.h" +#include "../CommonUtilities/log/Channel.h" +#include "../CommonUtilities/log/MsvcDebugDriver.h" +#include "../CommonUtilities/log/BasicFileDriver.h" +#include "../CommonUtilities/log/TextFormatter.h" +#include "../CommonUtilities/log/SimpleFileStrategy.h" +#include "../CommonUtilities/log/LinePolicy.h" +#include "../CommonUtilities/log/ErrorCodeResolvePolicy.h" +#include "../CommonUtilities/log/ErrorCodeResolver.h" +#include "../CommonUtilities/log/IdentificationTable.h" +#include "../CommonUtilities/str/String.h" +#include "../CommonUtilities/win/HrErrorCodeProvider.h" +#include "../CommonUtilities/win/WinAPI.h" +#include "../PresentMonAPIWrapperCommon/PmErrorCodeProvider.h" +#include "../PresentMonAPI2/Internal.h" + +#include +#include #include +#include namespace pmon::util::log { + namespace + { + std::shared_ptr MakeChannel_() + { + // channel (use custom deleter to ensure deletion in this module's heap) + auto pChannel = std::shared_ptr{ new Channel{}, [](Channel* p) { delete p; } }; + // error resolver + auto pErrorResolver = std::make_shared(); + pErrorResolver->AddProvider(std::make_unique()); + pErrorResolver->AddProvider(std::make_unique()); + // error resolving policy + auto pErrPolicy = std::make_shared(); + pErrPolicy->SetResolver(std::move(pErrorResolver)); + pChannel->AttachComponent(std::move(pErrPolicy)); + // make and add the line-tracking policy + pChannel->AttachComponent(std::make_shared()); + // attach debugger output by default + const auto pFormatter = std::make_shared(); + pChannel->AttachComponent(std::make_shared(pFormatter), "drv:dbg"); + return pChannel; + } + } + // this is injected into to util::log namespace and hooks into that system - // not using logging yet in the tests so just return empty pointer - std::shared_ptr GetDefaultChannel() noexcept + std::shared_ptr GetDefaultChannel() noexcept { - return {}; + return GetDefaultChannelWithFactory(MakeChannel_); + } +} + +namespace pmon::test +{ + namespace + { + struct LogLinkState + { + std::mutex mtx; + bool linked = false; + LoggingSingletons getters{}; + }; + + LogLinkState& GetLogLinkState_() + { + static LogLinkState state; + return state; + } + + util::log::Level ParseLogLevel_(const std::string& logLevel) + { + if (logLevel.empty()) { + return util::log::Level::Debug; + } + const auto levelMap = util::log::GetLevelMapNarrow(); + const auto key = util::str::ToLower(logLevel); + if (auto it = levelMap.find(key); it != levelMap.end()) { + return it->second; + } + return util::log::Level::Debug; + } + + std::string BuildLogFileName_(const std::filesystem::path& folder) + { + return std::format("test-harness-{}.txt", GetCurrentProcessId()); + } + } + + void SetupTestLogging(const std::string& logFolder, const std::string& logLevel) noexcept + { + try { + auto pChannel = util::log::GetDefaultChannel(); + if (!pChannel) { + return; + } + + const auto level = ParseLogLevel_(logLevel); + auto& policy = util::log::GlobalPolicy::Get(); + policy.SetLogLevel(level); + policy.SetTraceLevel(util::log::Level::Error); + policy.SetExceptionTrace(false); + + if (!logFolder.empty()) { + std::filesystem::path folderPath{ logFolder }; + std::error_code ec; + std::filesystem::create_directories(folderPath, ec); + const auto filePath = folderPath / BuildLogFileName_(folderPath); + auto pFormatter = std::make_shared(); + pChannel->AttachComponent(std::make_shared( + pFormatter, + std::make_shared(std::move(filePath))), + "drv:file"); + } + + auto& linkState = GetLogLinkState_(); + LoggingSingletons gettersCopy{}; + { + std::lock_guard lock{ linkState.mtx }; + if (!linkState.linked) { + linkState.getters = pmLinkLogging_(pChannel, []() -> util::log::IdentificationTable& { + return util::log::IdentificationTable::Get_(); + }); + linkState.linked = true; + } + gettersCopy = linkState.getters; + } + + if (gettersCopy) { + auto& dllPolicy = gettersCopy.getGlobalPolicy(); + dllPolicy.SetLogLevel(level); + dllPolicy.SetTraceLevel(util::log::Level::Error); + dllPolicy.SetExceptionTrace(false); + } + } + catch (...) { + } } -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonAPI2Tests/Logging.h b/IntelPresentMon/PresentMonAPI2Tests/Logging.h new file mode 100644 index 00000000..7ce1a38b --- /dev/null +++ b/IntelPresentMon/PresentMonAPI2Tests/Logging.h @@ -0,0 +1,7 @@ +#pragma once +#include + +namespace pmon::test +{ + void SetupTestLogging(const std::string& logFolder, const std::string& logLevel) noexcept; +} diff --git a/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h b/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h index 3112f193..3391ed56 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h +++ b/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h @@ -4,6 +4,7 @@ #include "../CommonUtilities/win/WinAPI.h" #include "CppUnitTest.h" #include "JobManager.h" +#include "Logging.h" #include "TestCommands.h" #include "../CommonUtilities/file/FileUtils.h" #include "../CommonUtilities/pipe/Pipe.h" @@ -257,6 +258,7 @@ class CommonTestFixture void Setup(std::vector args = {}) { + pmon::test::SetupTestLogging(GetCommonArgs().logFolder, GetCommonArgs().logLevel); StartService_(args, GetCommonArgs()); svcArgs_ = std::move(args); } @@ -323,4 +325,4 @@ class CommonTestFixture JobManager jobMan_; std::thread ioctxRunThread_; as::io_context ioctx_; -}; \ No newline at end of file +}; From 72f9b0b0953b59865146307c13a120b7763ee3d5 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 24 Dec 2025 09:56:02 +0900 Subject: [PATCH 079/205] add startQpc, some reorg of the frame data store statics/bookkeeping --- .../Interprocess/source/DataStores.h | 8 ++++- .../InterimBroadcasterTests.cpp | 6 ++-- .../PresentMonService/FrameBroadcaster.h | 29 ++++++++++++++----- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/DataStores.h b/IntelPresentMon/Interprocess/source/DataStores.h index 0de6c861..06477106 100644 --- a/IntelPresentMon/Interprocess/source/DataStores.h +++ b/IntelPresentMon/Interprocess/source/DataStores.h @@ -22,14 +22,20 @@ namespace pmon::ipc frameData{ cap, segMan.get_allocator(), backpressured }, statics{ .applicationName{ segMan.get_allocator() } } {} + // values that never change over the life of a target, available for use with metric queries + // often lazy initialized upon receipt of the first present/frame struct Statics { - uint32_t processId; ShmString applicationName; } statics; + // values used for internal bookkeeping, often static (but not necessarily), typically not derived from frame data + // and typically initialized once on first aquisition of target; may also feed into metric queries struct Bookkeeping { + uint32_t processId; + int64_t startQpc; bool staticInitComplete = false; + bool bookkeepingInitComplete = false; bool isPlayback = false; } bookkeeping{}; ShmRing frameData; diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index a4adc725..aca62ace 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -550,7 +550,7 @@ namespace InterimBroadcasterTests // verify static data auto& store = pComms->GetFrameDataStore(pres.GetId()); - Assert::AreEqual(pres.GetId(), store.statics.processId); + Assert::AreEqual(pres.GetId(), store.bookkeeping.processId); const std::string staticAppName = store.statics.applicationName.c_str(); Assert::AreEqual("PresentBench.exe"s, staticAppName); } @@ -653,7 +653,7 @@ namespace InterimBroadcasterTests // verify static data auto& store = pComms->GetFrameDataStore(pid); - Assert::AreEqual(pid, store.statics.processId); + Assert::AreEqual(pid, store.bookkeeping.processId); const std::string staticAppName = store.statics.applicationName.c_str(); Assert::AreEqual("Heaven.exe"s, staticAppName); } @@ -726,7 +726,7 @@ namespace InterimBroadcasterTests // verify static data auto& store = pComms->GetFrameDataStore(pid); - Assert::AreEqual(pid, store.statics.processId); + Assert::AreEqual(pid, store.bookkeeping.processId); const std::string staticAppName = store.statics.applicationName.c_str(); Assert::AreEqual("Heaven.exe"s, staticAppName); } diff --git a/IntelPresentMon/PresentMonService/FrameBroadcaster.h b/IntelPresentMon/PresentMonService/FrameBroadcaster.h index 34df2163..d4a212c0 100644 --- a/IntelPresentMon/PresentMonService/FrameBroadcaster.h +++ b/IntelPresentMon/PresentMonService/FrameBroadcaster.h @@ -82,23 +82,36 @@ namespace pmon::svc std::shared_ptr RegisterTarget(uint32_t pid, bool isPlayback, bool isBackpressured) { std::lock_guard lk{ mtx_ }; + const auto startQpc = util::GetCurrentTimestamp(); auto pSegment = comms_.CreateOrGetFrameDataSegment(pid, isBackpressured); auto& store = pSegment->GetStore(); - // just overwrite these every time Register is called, it will always be the same - store.statics.processId = pid; - store.bookkeeping.isPlayback = isPlayback; + auto& book = store.bookkeeping; + // init bookkeeping only once and only here + if (!book.bookkeepingInitComplete) { + book.processId = pid; // we can also init this static here + book.isPlayback = isPlayback; + book.startQpc = startQpc; + book.bookkeepingInitComplete = true; + } + else { // sanity checks for subsequent opens + if (book.processId != pid || book.isPlayback != isPlayback || + book.startQpc >= startQpc) { + pmlog_error("Mismatch in bookkeeping data") + .pmwatch(book.processId).pmwatch(pid) + .pmwatch(book.isPlayback).pmwatch(isPlayback) + .pmwatch(book.startQpc).pmwatch(startQpc); + } + } // initialize name/pid statics on new store segment creation - // for playback, this needs to be done when processing 1st process event - if (!store.bookkeeping.staticInitComplete && !isPlayback) { - store.bookkeeping.staticInitComplete = true; + // for playback, this needs to be deferred to when processing 1st process event + if (!book.staticInitComplete && !isPlayback) { + book.staticInitComplete = true; try { store.statics.applicationName = util::win::GetExecutableModulePath( util::win::OpenProcess(pid) ).filename().string().c_str(); } catch (...) { - // if we reach here a race condition has occurred where the target has exited - // so we will mark this in the bookkeeping pmlog_warn("Process exited right as it was being initialized").pmwatch(pid); } } From b0f1189566334b6d5cd81bc0d2dcb1b347bb1005 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 24 Dec 2025 13:42:39 +0900 Subject: [PATCH 080/205] noreport gpu test and improved test log stability with flush --- .../InterimBroadcasterTests.cpp | 64 +++++++++++++++++++ .../PresentMonAPI2Tests/Logging.cpp | 13 ++++ IntelPresentMon/PresentMonAPI2Tests/Logging.h | 6 ++ .../PresentMonAPI2Tests.vcxproj | 3 + .../PresentMonAPI2Tests/TestProcess.h | 5 ++ 5 files changed, 91 insertions(+) diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index aca62ace..7a818e3a 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -138,6 +138,39 @@ namespace InterimBroadcasterTests { fixture_.Cleanup(); } + // trying to use a store without reporting use + TEST_METHOD(NoReport) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + + // acquire introspection with enhanced wrapper interface + auto pIntro = pComms->GetIntrospectionRoot(); + pmapi::intro::Root intro{ pIntro, [](auto* p) {delete p; } }; + pmapi::EnumMap::Refresh(intro); + auto pMetricMap = pmapi::EnumMap::GetKeyMap(PM_ENUM_METRIC); + + // set telemetry period so we have a known baseline + client.DispatchSync(svc::acts::SetTelemetryPeriod::Params{ .telemetrySamplePeriodMs = 100 }); + + // get the store containing system-wide telemetry (cpu etc.) + auto& sys = pComms->GetSystemDataStore(); + for (auto&& [met, r] : sys.telemetryData.Rings()) { + Logger::WriteMessage(std::format(" TeleRing@{}\n", pMetricMap->at(met).narrowName).c_str()); + // TODO: understand the disconnect between CPU Core Utility showing up here + // and not showing up in the UI (update: it is blacklisted manually in UI introspection) + } + Assert::AreEqual(2ull, (size_t)rn::distance(sys.telemetryData.Rings())); + + // system device id constant + const uint32_t SystemDeviceID = 65536; + + // allow warm-up period + std::this_thread::sleep_for(650ms); + + // we expect 0 data point in the rings for the system since it does not populate on init + Assert::AreEqual(0ull, sys.telemetryData.FindRing(PM_METRIC_CPU_UTILIZATION).at(0).Size()); + } // static store TEST_METHOD(StaticData) { @@ -332,6 +365,37 @@ namespace InterimBroadcasterTests { fixture_.Cleanup(); } + // trying to use a store without reporting use + TEST_METHOD(NoReport) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + + // acquire introspection with enhanced wrapper interface + auto pIntro = pComms->GetIntrospectionRoot(); + pmapi::intro::Root intro{ pIntro, [](auto* p) { delete p; } }; + pmapi::EnumMap::Refresh(intro); + auto pMetricMap = pmapi::EnumMap::GetKeyMap(PM_ENUM_METRIC); + + // set telemetry period so we have a known baseline + client.DispatchSync(svc::acts::SetTelemetryPeriod::Params{ .telemetrySamplePeriodMs = 100 }); + + // target gpu device 1 (hardcoded for test) + const uint32_t TargetDeviceID = 1; + + // get the store containing adapter telemetry + auto& gpu = pComms->GetGpuDataStore(TargetDeviceID); + for (auto&& [met, r] : gpu.telemetryData.Rings()) { + Logger::WriteMessage(std::format(" TeleRing@{}\n", pMetricMap->at(met).narrowName).c_str()); + } + Assert::IsTrue((size_t)rn::distance(gpu.telemetryData.Rings()) > 0); + + // allow warm-up period + std::this_thread::sleep_for(650ms); + + // we expect 0 data points in the rings for the gpu since it does not populate on init + Assert::AreEqual(0ull, gpu.telemetryData.FindRing(PM_METRIC_GPU_TEMPERATURE).at(0).Size()); + } // static store TEST_METHOD(StaticData) { diff --git a/IntelPresentMon/PresentMonAPI2Tests/Logging.cpp b/IntelPresentMon/PresentMonAPI2Tests/Logging.cpp index 86011942..fedfbd4c 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/Logging.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/Logging.cpp @@ -13,6 +13,7 @@ #include "../CommonUtilities/str/String.h" #include "../CommonUtilities/win/HrErrorCodeProvider.h" #include "../CommonUtilities/win/WinAPI.h" +#include "../CommonUtilities/Exception.h" #include "../PresentMonAPIWrapperCommon/PmErrorCodeProvider.h" #include "../PresentMonAPI2/Internal.h" @@ -138,4 +139,16 @@ namespace pmon::test catch (...) { } } + + LogChannelManager::LogChannelManager() noexcept + { + util::InstallSehTranslator(); + util::log::BootDefaultChannelEager(); + } + + LogChannelManager::~LogChannelManager() + { + pmFlushEntryPoint_(); + util::log::FlushEntryPoint(); + } } diff --git a/IntelPresentMon/PresentMonAPI2Tests/Logging.h b/IntelPresentMon/PresentMonAPI2Tests/Logging.h index 7ce1a38b..bf17f64b 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/Logging.h +++ b/IntelPresentMon/PresentMonAPI2Tests/Logging.h @@ -4,4 +4,10 @@ namespace pmon::test { void SetupTestLogging(const std::string& logFolder, const std::string& logLevel) noexcept; + + struct LogChannelManager + { + LogChannelManager() noexcept; + ~LogChannelManager(); + }; } diff --git a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj index a324d17e..9bae7fff 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj +++ b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj @@ -117,6 +117,9 @@ {8f86d067-2437-46fc-8f82-4d7155ceced7} + + {5ddba061-53a0-4835-8aaf-943f403f924f} + {cee032ed-b0d3-47f8-bdae-d46757b0061b} diff --git a/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h b/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h index 3391ed56..1fbcfd35 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h +++ b/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h @@ -258,6 +258,9 @@ class CommonTestFixture void Setup(std::vector args = {}) { + if (!logManager_) { + logManager_.emplace(); + } pmon::test::SetupTestLogging(GetCommonArgs().logFolder, GetCommonArgs().logLevel); StartService_(args, GetCommonArgs()); svcArgs_ = std::move(args); @@ -266,6 +269,7 @@ class CommonTestFixture { StopService_(GetCommonArgs()); ioctxRunThread_.join(); + logManager_.reset(); } void RebootService(std::optional> newArgs = {}) { @@ -325,4 +329,5 @@ class CommonTestFixture JobManager jobMan_; std::thread ioctxRunThread_; as::io_context ioctx_; + std::optional logManager_; }; From faef109d97e9a048aceeb3fe0b37eb823b9ea347 Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 29 Dec 2025 09:11:01 +0900 Subject: [PATCH 081/205] size calculation and control for IPC data store segments --- .gitignore | 5 +- .../Interprocess/Interprocess.vcxproj | 3 +- .../Interprocess/Interprocess.vcxproj.filters | 5 +- .../Interprocess/source/DataStores.cpp | 120 ++++++++++++++++++ .../Interprocess/source/DataStores.h | 42 +++++- .../Interprocess/source/Interprocess.cpp | 68 ++++++---- .../Interprocess/source/Interprocess.h | 8 +- .../source/IntrospectionTransfer.h | 37 +++++- .../Interprocess/source/OwnedDataSegment.h | 76 +++++------ IntelPresentMon/InterprocessMock/Main.cpp | 8 +- .../InterimBroadcasterTests.cpp | 2 +- .../PresentMonAPI2Tests/Logging.cpp | 2 +- IntelPresentMon/PresentMonAPI2Tests/Logging.h | 2 +- .../PresentMonAPI2Tests.vcxproj | 2 +- .../PresentMonAPI2Tests/TestProcess.h | 2 +- .../PresentMonService/CliOptions.h | 8 +- .../PresentMonService/PMMainThread.cpp | 20 ++- IntelPresentMon/PresentMonService/Registry.h | 6 +- .../SampleClient/IpcComponentServer.cpp | 19 ++- .../SampleClient/SampleClient.vcxproj | 7 +- 20 files changed, 337 insertions(+), 105 deletions(-) create mode 100644 IntelPresentMon/Interprocess/source/DataStores.cpp diff --git a/.gitignore b/.gitignore index b4a43b56..b2888304 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -## Ignore Visual Studio temporary files, build results, and +## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # Build results @@ -15,6 +15,7 @@ # Visual Studio 2015 cache/options directory .vs/ +AGENTS.md # Visual C++ cache files ipch/ @@ -61,4 +62,4 @@ PresentMon/ddETWExternalEventsTEMP.BIN # Local runsettings files (user-specific test configurations) *.local.runsettings -Directory.Build.props \ No newline at end of file +Directory.Build.props diff --git a/IntelPresentMon/Interprocess/Interprocess.vcxproj b/IntelPresentMon/Interprocess/Interprocess.vcxproj index bcc0731b..b3804b30 100644 --- a/IntelPresentMon/Interprocess/Interprocess.vcxproj +++ b/IntelPresentMon/Interprocess/Interprocess.vcxproj @@ -11,6 +11,7 @@ + @@ -173,4 +174,4 @@ - \ No newline at end of file + diff --git a/IntelPresentMon/Interprocess/Interprocess.vcxproj.filters b/IntelPresentMon/Interprocess/Interprocess.vcxproj.filters index 94250281..b40c0548 100644 --- a/IntelPresentMon/Interprocess/Interprocess.vcxproj.filters +++ b/IntelPresentMon/Interprocess/Interprocess.vcxproj.filters @@ -15,6 +15,9 @@ + + Source Files + Source Files @@ -183,4 +186,4 @@ Header Files - \ No newline at end of file + diff --git a/IntelPresentMon/Interprocess/source/DataStores.cpp b/IntelPresentMon/Interprocess/source/DataStores.cpp new file mode 100644 index 00000000..46098da1 --- /dev/null +++ b/IntelPresentMon/Interprocess/source/DataStores.cpp @@ -0,0 +1,120 @@ +#include "DataStores.h" +#include "MetricCapabilities.h" +#include "IntrospectionTransfer.h" +#include "IntrospectionDataTypeMapping.h" +#include "../../CommonUtilities/Memory.h" +#include +#include +#include + +namespace pmon::ipc +{ + namespace + { + constexpr size_t kSegmentAlignmentBytes = 64 * 1024; + constexpr size_t kFrameLeewayPerRingBytes = 2 * 1024 * 1024; + constexpr size_t kFrameLeewayBaseBytes = 8 * 1024 * 1024; + constexpr size_t kTelemetryLeewayPerRingBytes = 64 * 1024; + constexpr size_t kTelemetryLeewayBaseBytes = 2 * 1024 * 1024; + + size_t PadToAlignment(size_t bytes, size_t alignment) + { + return bytes + util::GetPadding(bytes, alignment); + } + + template + struct DataTypeSizeBridger + { + static size_t Invoke() + { + return intro::DataTypeToStaticType_sz; + } + static size_t Default() + { + return 0ull; + } + }; + + size_t EstimateSampleBytes(PM_DATA_TYPE type) + { + const size_t valueBytes = intro::BridgeDataType(type); + const size_t safeValueBytes = valueBytes > 0 ? valueBytes : sizeof(uint32_t); + const size_t pad = util::GetPadding(safeValueBytes, alignof(uint64_t)); + return safeValueBytes + pad + sizeof(uint64_t); + } + + bool ShouldAllocateTelemetryRing(PM_METRIC metricId, + const intro::IntrospectionMetric& metric) + { + if (metric.GetMetricType() == PM_METRIC_TYPE_STATIC) { + return false; + } + if (metricId == PM_METRIC_GPU_FAN_SPEED_PERCENT || + metricId == PM_METRIC_GPU_MEM_UTILIZATION) { + return false; + } + return true; + } + + size_t TelemetrySegmentBytes(const DataStoreSizingInfo& sizing, PM_DEVICE_TYPE deviceType) + { + if (sizing.overrideBytes) { + return *sizing.overrideBytes; + } + if (!sizing.pRoot || !sizing.pCaps) { + throw std::logic_error("DataStoreSizingInfo requires introspection root and caps"); + } + + std::unordered_map deviceTypeById; + for (const auto& pDevice : sizing.pRoot->GetDevices()) { + deviceTypeById.emplace(pDevice->GetId(), pDevice->GetType()); + } + + size_t ringCount = 0; + size_t payloadBytes = 0; + for (auto&& [metricId, count] : *sizing.pCaps) { + const auto& metric = sizing.pRoot->FindMetric(metricId); + bool matchesDeviceType = false; + for (const auto& pInfo : metric.GetDeviceMetricInfo()) { + const auto it = deviceTypeById.find(pInfo->GetDeviceId()); + if (it != deviceTypeById.end() && it->second == deviceType) { + matchesDeviceType = true; + break; + } + } + if (!matchesDeviceType) { + throw std::logic_error( + "DataStoreSizingInfo caps contain a metric outside the expected device type"); + } + if (!ShouldAllocateTelemetryRing(metricId, metric)) { + continue; + } + payloadBytes += count * sizing.ringSamples * + EstimateSampleBytes(metric.GetDataTypeInfo().GetFrameType()); + ringCount += count; + } + + const size_t leewayBytes = + ringCount * kTelemetryLeewayPerRingBytes + kTelemetryLeewayBaseBytes; + return PadToAlignment(payloadBytes + leewayBytes, kSegmentAlignmentBytes); + } + } + + size_t FrameDataStore::CalculateSegmentBytes(const DataStoreSizingInfo& sizing) + { + const size_t payloadBytes = sizing.ringSamples * sizeof(FrameData); + const size_t leewayBytes = kFrameLeewayPerRingBytes + kFrameLeewayBaseBytes; + return PadToAlignment(payloadBytes + leewayBytes, kSegmentAlignmentBytes); + } + + size_t GpuDataStore::CalculateSegmentBytes(const DataStoreSizingInfo& sizing) + { + return TelemetrySegmentBytes(sizing, PM_DEVICE_TYPE_GRAPHICS_ADAPTER); + } + + size_t SystemDataStore::CalculateSegmentBytes(const DataStoreSizingInfo& sizing) + { + return TelemetrySegmentBytes(sizing, PM_DEVICE_TYPE_SYSTEM); + } +} + diff --git a/IntelPresentMon/Interprocess/source/DataStores.h b/IntelPresentMon/Interprocess/source/DataStores.h index 06477106..4e027eeb 100644 --- a/IntelPresentMon/Interprocess/source/DataStores.h +++ b/IntelPresentMon/Interprocess/source/DataStores.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "SharedMemoryTypes.h" #include "ShmRing.h" #include "TelemetryMap.h" @@ -6,6 +6,7 @@ #include "../../CommonUtilities/log/Log.h" #include "../../PresentMonAPI2/PresentMonAPI.h" #include "FrameDataPlaceholder.h" +#include #include // these data stores intended to be hosted within StreamedDataSegment instances via template @@ -14,14 +15,34 @@ namespace pmon::ipc { + class MetricCapabilities; + namespace intro + { + struct IntrospectionRoot; + } + + struct DataStoreSizingInfo + { + // Telemetry-only: introspection root + capability map. + const intro::IntrospectionRoot* pRoot = nullptr; + const MetricCapabilities* pCaps = nullptr; + // Frame + telemetry: ring sample capacity and optional override size. + size_t ringSamples = 0; + std::optional overrideBytes; + // Frame-only: backpressure behavior for frame rings. + bool backpressured = false; + }; + struct FrameDataStore { - static constexpr size_t virtualSegmentSize = 50'000'000; FrameDataStore(ShmSegmentManager& segMan, size_t cap, bool backpressured) : frameData{ cap, segMan.get_allocator(), backpressured }, statics{ .applicationName{ segMan.get_allocator() } } {} + FrameDataStore(ShmSegmentManager& segMan, const DataStoreSizingInfo& sizing) + : FrameDataStore(segMan, sizing.ringSamples, sizing.backpressured) + {} // values that never change over the life of a target, available for use with metric queries // often lazy initialized upon receipt of the first present/frame struct Statics @@ -39,11 +60,12 @@ namespace pmon::ipc bool isPlayback = false; } bookkeeping{}; ShmRing frameData; + + static size_t CalculateSegmentBytes(const DataStoreSizingInfo& sizing); }; struct GpuDataStore { - static constexpr size_t virtualSegmentSize = 2'000'000; GpuDataStore(ShmSegmentManager& segMan) : telemetryData{ segMan.get_allocator() }, @@ -51,6 +73,9 @@ namespace pmon::ipc .name{ segMan.get_allocator() }, .maxFanSpeedRpm{ segMan.get_allocator() } } {} + GpuDataStore(ShmSegmentManager& segMan, const DataStoreSizingInfo&) + : GpuDataStore(segMan) + {} struct Statics { PM_DEVICE_VENDOR vendor; @@ -61,16 +86,20 @@ namespace pmon::ipc ShmVector maxFanSpeedRpm; } statics; TelemetryMap telemetryData; + + static size_t CalculateSegmentBytes(const DataStoreSizingInfo& sizing); }; struct SystemDataStore { - static constexpr size_t virtualSegmentSize = 1'000'000; SystemDataStore(ShmSegmentManager& segMan) : telemetryData{ segMan.get_allocator() }, statics{ .cpuName{ segMan.get_allocator() } } {} + SystemDataStore(ShmSegmentManager& segMan, const DataStoreSizingInfo&) + : SystemDataStore(segMan) + {} struct Statics { PM_DEVICE_VENDOR cpuVendor; @@ -78,5 +107,8 @@ namespace pmon::ipc double cpuPowerLimit; } statics; TelemetryMap telemetryData; + + static size_t CalculateSegmentBytes(const DataStoreSizingInfo& sizing); }; -} \ No newline at end of file +} + diff --git a/IntelPresentMon/Interprocess/source/Interprocess.cpp b/IntelPresentMon/Interprocess/source/Interprocess.cpp index 8461b44b..e0d14be4 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.cpp +++ b/IntelPresentMon/Interprocess/source/Interprocess.cpp @@ -1,4 +1,4 @@ -#include "../../CommonUtilities/win/WinAPI.h" +#include "../../CommonUtilities/win/WinAPI.h" #include "Interprocess.h" #include "IntrospectionTransfer.h" #include "IntrospectionPopulators.h" @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "../../PresentMonService/GlobalIdentifiers.h" #include @@ -28,8 +29,6 @@ namespace pmon::ipc class CommsBase_ { protected: - static constexpr size_t frameRingSize_ = 1'000; - static constexpr size_t telemetryRingSize_ = 1'000; static constexpr size_t introShmSize_ = 0x10'0000; static constexpr const char* introspectionRootName_ = "in-root"; static constexpr const char* introspectionMutexName_ = "in-mtx"; @@ -39,9 +38,13 @@ namespace pmon::ipc class ServiceComms_ : public ServiceComms, CommsBase_ { public: - ServiceComms_(std::string prefix) + ServiceComms_(std::string prefix, + size_t frameRingSamples, + size_t telemetryRingSamples) : namer_{ std::move(prefix) }, + frameRingSamples_{ std::max(frameRingSamples, kMinRingSamples_) }, + telemetryRingSamples_{ std::max(telemetryRingSamples, kMinRingSamples_) }, shm_{ bip::create_only, namer_.MakeIntrospectionName().c_str(), introShmSize_, nullptr, Permissions_{} }, pIntroMutex_{ ShmMakeNamedUnique( @@ -49,9 +52,7 @@ namespace pmon::ipc pIntroSemaphore_{ ShmMakeNamedUnique( introspectionSemaphoreName_, shm_.get_segment_manager(), 0) }, pRoot_{ ShmMakeNamedUnique(introspectionRootName_, - shm_.get_segment_manager(), shm_.get_segment_manager()) }, - systemShm_{ namer_.MakeSystemName(), - static_cast(Permissions_{}) } + shm_.get_segment_manager(), shm_.get_segment_manager()) } { PreInitializeIntrospection_(); } @@ -74,6 +75,7 @@ namespace pmon::ipc std::piecewise_construct, std::forward_as_tuple(deviceId), std::forward_as_tuple(namer_.MakeGpuName(deviceId), + DataStoreSizingInfo{ pRoot_.get().get(), &caps, telemetryRingSamples_ }, static_cast(Permissions_{})) ).first->second; // populate rings based on caps @@ -91,7 +93,7 @@ namespace pmon::ipc } const auto dataType = metric.GetDataTypeInfo().GetFrameType(); gpuShm.GetStore().telemetryData.AddRing( - m, telemetryRingSize_, count, dataType + m, telemetryRingSamples_, count, dataType ); } } @@ -112,6 +114,11 @@ namespace pmon::ipc intro::PopulateCpu( shm_.get_segment_manager(), *pRoot_, vendor, std::move(deviceName), caps ); + if (!systemShm_) { + systemShm_.emplace(namer_.MakeSystemName(), + DataStoreSizingInfo{ pRoot_.get().get(), &caps, telemetryRingSamples_ }, + static_cast(Permissions_{})); + } // populate rings based on caps for (auto&& [m, count] : caps) { const auto& metric = pRoot_->FindMetric(m); @@ -121,8 +128,8 @@ namespace pmon::ipc continue; } const auto dataType = metric.GetDataTypeInfo().GetFrameType(); - systemShm_.GetStore().telemetryData.AddRing( - m, telemetryRingSize_, count, dataType + systemShm_->GetStore().telemetryData.AddRing( + m, telemetryRingSamples_, count, dataType ); } introCpuComplete_ = true; @@ -147,10 +154,11 @@ namespace pmon::ipc // make a frame data store as shared ptr pFrameData = std::make_shared>( namer_.MakeFrameName(pid), - static_cast(Permissions_{}), - frameRingSize_, - backpressured - ); + DataStoreSizingInfo{ + .ringSamples = frameRingSamples_, + .backpressured = backpressured, + }, + static_cast(Permissions_{})); // store a weak reference pWeak = pFrameData; } @@ -186,7 +194,10 @@ namespace pmon::ipc } SystemDataStore& GetSystemDataStore() override { - return systemShm_.GetStore(); + if (!systemShm_) { + throw std::runtime_error("System data segment not initialized"); + } + return systemShm_->GetStore(); } private: @@ -243,7 +254,10 @@ namespace pmon::ipc } // data + static constexpr size_t kMinRingSamples_ = 8; ShmNamer namer_; + size_t frameRingSamples_; + size_t telemetryRingSamples_; ShmSegment shm_; ShmUniquePtr pIntroMutex_; ShmUniquePtr pIntroSemaphore_; @@ -252,7 +266,7 @@ namespace pmon::ipc bool introGpuComplete_ = false; bool introCpuComplete_ = false; - OwnedDataSegment systemShm_; + std::optional> systemShm_; std::unordered_map>> frameShmWeaks_; std::unordered_map> gpuShms_; }; @@ -263,9 +277,10 @@ namespace pmon::ipc MiddlewareComms_(std::string prefix, std::string salt) : namer_{ std::move(prefix), std::move(salt) }, - shm_{ bip::open_only, namer_.MakeIntrospectionName().c_str() }, - systemShm_{ namer_.MakeSystemName() } // eager-load system segment + shm_{ bip::open_only, namer_.MakeIntrospectionName().c_str() } { + WaitOnIntrospectionHoldoff_(1500); + systemShm_.emplace(namer_.MakeSystemName()); // Eager-load all GPU segments based on introspection auto ids = GetGpuDeviceIds_(); for (auto id : ids) { @@ -337,7 +352,10 @@ namespace pmon::ipc } const SystemDataStore& GetSystemDataStore() const override { - return systemShm_.GetStore(); + if (!systemShm_) { + throw std::runtime_error{ "System data segment not open" }; + } + return systemShm_->GetStore(); } private: @@ -397,16 +415,21 @@ namespace pmon::ipc ShmNamer namer_; ShmSegment shm_; // introspection shm - ViewedDataSegment systemShm_; + std::optional> systemShm_; std::unordered_map> gpuShms_; std::unordered_map> frameShms_; }; } std::unique_ptr - MakeServiceComms(std::string prefix) + MakeServiceComms(std::string prefix, + size_t frameRingSamples, + size_t telemetryRingSamples) { - return std::make_unique(std::move(prefix)); + return std::make_unique( + std::move(prefix), + frameRingSamples, + telemetryRingSamples); } std::unique_ptr @@ -415,3 +438,4 @@ namespace pmon::ipc return std::make_unique(std::move(prefix), std::move(salt)); } } + diff --git a/IntelPresentMon/Interprocess/source/Interprocess.h b/IntelPresentMon/Interprocess/source/Interprocess.h index ced0508c..046a0641 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.h +++ b/IntelPresentMon/Interprocess/source/Interprocess.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include #include #include @@ -49,6 +49,8 @@ namespace pmon::ipc virtual void CloseFrameDataStore(uint32_t pid) = 0; }; - std::unique_ptr MakeServiceComms(std::string prefix); + std::unique_ptr MakeServiceComms(std::string prefix, + size_t frameRingSamples, + size_t telemetryRingSamples); std::unique_ptr MakeMiddlewareComms(std::string prefix, std::string salt); -} \ No newline at end of file +} diff --git a/IntelPresentMon/Interprocess/source/IntrospectionTransfer.h b/IntelPresentMon/Interprocess/source/IntrospectionTransfer.h index f534fbb0..a803f514 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionTransfer.h +++ b/IntelPresentMon/Interprocess/source/IntrospectionTransfer.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include #include #include @@ -106,6 +106,10 @@ namespace pmon::ipc::intro { return { buffer_ }; } + std::span> GetElements() const + { + return { buffer_ }; + } void Sort() { if constexpr (LessThanComparable_) { @@ -250,6 +254,10 @@ namespace pmon::ipc::intro { return id_; } + PM_DEVICE_TYPE GetType() const + { + return type_; + } private: uint32_t id_; PM_DEVICE_TYPE type_; @@ -285,6 +293,10 @@ namespace pmon::ipc::intro } return pSelf; } + uint32_t GetDeviceId() const + { + return deviceId_; + } private: uint32_t deviceId_; PM_METRIC_AVAILABILITY availability_; @@ -460,6 +472,10 @@ namespace pmon::ipc::intro { return *pTypeInfo_; } + std::span> GetDeviceMetricInfo() const + { + return deviceMetricInfo_.GetElements(); + } PM_METRIC_TYPE GetMetricType() const { return type_; @@ -508,10 +524,18 @@ namespace pmon::ipc::intro { return metrics_.GetElements(); } + std::span> GetMetrics() const + { + return metrics_.GetElements(); + } std::span> GetDevices() { return devices_.GetElements(); } + std::span> GetDevices() const + { + return devices_.GetElements(); + } IntrospectionMetric& FindMetric(PM_METRIC metric) { for (auto& p : GetMetrics()) { @@ -521,6 +545,15 @@ namespace pmon::ipc::intro } throw util::Except<>("Metric ID not found in introspection"); } + const IntrospectionMetric& FindMetric(PM_METRIC metric) const + { + for (const auto& p : GetMetrics()) { + if (p->GetId() == metric) { + return *p; + } + } + throw util::Except<>("Metric ID not found in introspection"); + } using ApiType = PM_INTROSPECTION_ROOT; template @@ -559,4 +592,4 @@ namespace pmon::ipc::intro IntrospectionObjArray devices_; IntrospectionObjArray units_; }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/Interprocess/source/OwnedDataSegment.h b/IntelPresentMon/Interprocess/source/OwnedDataSegment.h index 560e7c6d..a9b3f0dc 100644 --- a/IntelPresentMon/Interprocess/source/OwnedDataSegment.h +++ b/IntelPresentMon/Interprocess/source/OwnedDataSegment.h @@ -1,8 +1,7 @@ -#pragma once +#pragma once #include "SharedMemoryTypes.h" -#include "MetricCapabilities.h" #include "DataStores.h" -#include +#include "../../CommonUtilities/log/Log.h" namespace pmon::ipc { @@ -11,26 +10,15 @@ namespace pmon::ipc class OwnedDataSegment { public: - // No ACL version - // For FrameDataStore: pass (ringCapacity) - // For GpuDataStore/SystemDataStore: pass no extra args - template + // Uses DataStoreSizingInfo for sizing across all stores; permissions are optional. OwnedDataSegment(const std::string& segmentName, - StoreArgs&&... storeArgs) + const DataStoreSizingInfo& sizing, + const bip::permissions& perms = {}) : - shm_{ bip::create_only, segmentName.c_str(), T::virtualSegmentSize }, - pData_{ MakeStore_(std::forward(storeArgs)...) } - {} - - // ACL version - template - OwnedDataSegment(const std::string& segmentName, - const bip::permissions& perms, - StoreArgs&&... storeArgs) - : - shm_{ bip::create_only, segmentName.c_str(), T::virtualSegmentSize, - nullptr, perms }, - pData_{ MakeStore_(std::forward(storeArgs)...) } + shm_{ bip::create_only, segmentName.c_str(), + ResolveSegmentBytes_(segmentName, sizing), + nullptr, perms }, + pData_{ MakeStore_(sizing) } {} T& GetStore() { return *pData_; } @@ -39,34 +27,32 @@ namespace pmon::ipc private: static constexpr const char* name_ = "seg-dat"; - // Factory that constructs the store in shared memory with the right allocator - template - ShmUniquePtr MakeStore_(StoreArgs&&... storeArgs) + static size_t ResolveSegmentBytes_(const std::string& segmentName, + const DataStoreSizingInfo& sizing) { - // FrameDataStore: expects (ShmAllocator&, size_t cap) - if constexpr (std::is_same_v) { - return ShmMakeNamedUnique( - name_, - shm_.get_segment_manager(), - *shm_.get_segment_manager(), - std::forward(storeArgs)... - ); - } - // Telemetry stores: expect TelemetryMap::AllocatorType& only - else if constexpr (std::is_same_v || - std::is_same_v) { - static_assert(sizeof...(StoreArgs) == 0, - "OwnedStreamedSegment " - "does not take extra ctor args"); - return ShmMakeNamedUnique( - name_, - shm_.get_segment_manager(), - *shm_.get_segment_manager() - ); - } + const auto calculatedSize = sizing.overrideBytes ? *sizing.overrideBytes : + T::CalculateSegmentBytes(sizing); + pmlog_dbg("Creating shm segment") + .pmwatch(segmentName) + .pmwatch(calculatedSize) + .pmwatch(sizing.ringSamples) + .pmwatch(sizing.overrideBytes.has_value()) + .pmwatch(sizing.backpressured); + return calculatedSize; + } + + ShmUniquePtr MakeStore_(const DataStoreSizingInfo& sizing) + { + return ShmMakeNamedUnique( + name_, + shm_.get_segment_manager(), + *shm_.get_segment_manager(), + sizing + ); } ShmSegment shm_; ShmUniquePtr pData_; }; } + diff --git a/IntelPresentMon/InterprocessMock/Main.cpp b/IntelPresentMon/InterprocessMock/Main.cpp index d10948db..aace5bda 100644 --- a/IntelPresentMon/InterprocessMock/Main.cpp +++ b/IntelPresentMon/InterprocessMock/Main.cpp @@ -1,4 +1,4 @@ -#include +#include #include "../Interprocess/source/ExperimentalInterprocess.h" #include "../Interprocess/source/Interprocess.h" #include "Options.h" @@ -269,7 +269,9 @@ int main(int argc, char** argv) else if (opts.basicIntro) { std::string buffer; - auto pServiceComms = pmon::ipc::MakeServiceComms(*opts.introNsm); + auto pServiceComms = pmon::ipc::MakeServiceComms(*opts.introNsm, + 1000, + 1500); pmon::ipc::intro::RegisterMockIntrospectionDevices(*pServiceComms); // signal to client that shm has been created @@ -307,4 +309,4 @@ int main(int argc, char** argv) } return 0; -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 7a818e3a..2fbeebb0 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2023 Intel Corporation +// Copyright (C) 2022-2023 Intel Corporation // SPDX-License-Identifier: MIT #include "../CommonUtilities/win/WinAPI.h" #include "CppUnitTest.h" diff --git a/IntelPresentMon/PresentMonAPI2Tests/Logging.cpp b/IntelPresentMon/PresentMonAPI2Tests/Logging.cpp index fedfbd4c..8096d37a 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/Logging.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/Logging.cpp @@ -1,4 +1,4 @@ -#include "Logging.h" +#include "Logging.h" #include "../CommonUtilities/log/Log.h" #include "../CommonUtilities/log/Channel.h" diff --git a/IntelPresentMon/PresentMonAPI2Tests/Logging.h b/IntelPresentMon/PresentMonAPI2Tests/Logging.h index bf17f64b..53a64194 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/Logging.h +++ b/IntelPresentMon/PresentMonAPI2Tests/Logging.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include namespace pmon::test diff --git a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj index 9bae7fff..12d27a8a 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj +++ b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj @@ -1,4 +1,4 @@ - + diff --git a/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h b/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h index 1fbcfd35..7b22bd6f 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h +++ b/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2023 Intel Corporation +// Copyright (C) 2022-2023 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #include "../CommonUtilities/win/WinAPI.h" diff --git a/IntelPresentMon/PresentMonService/CliOptions.h b/IntelPresentMon/PresentMonService/CliOptions.h index f615b084..f99d5aa1 100644 --- a/IntelPresentMon/PresentMonService/CliOptions.h +++ b/IntelPresentMon/PresentMonService/CliOptions.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "../CommonUtilities/cli/CliFramework.h" #include "../CommonUtilities/log/Level.h" #include "../CommonUtilities/log/Verbose.h" @@ -19,6 +19,10 @@ namespace clio Option controlPipe{ this, "--control-pipe", "", "Name of the named pipe to use for the client-service control channel" }; Option shmNamePrefix{ this, "--shm-name-prefix", R"(Global\pm_svc_shm)", "Prefix to use when naming shared memory segments" }; + private: Group gs_{ this, "Shared Memory", "Shared memory ring sizing" }; public: + Option frameRingSamples{ this, "--frame-ring-samples", 1000, "Number of frame samples to retain per target" }; + Option telemetryRingSamples{ this, "--telemetry-ring-samples", 1500, "Number of telemetry samples to retain per ring" }; + private: Group gd_{ this, "Debugging", "Aids in debugging this tool" }; public: Flag debug{ this, "--debug,-d", "Stall service by running in a loop after startup waiting for debugger to connect" }; Option timedStop{ this, "--timed-stop", -1, "Signal stop event after specified number of milliseconds" }; @@ -45,4 +49,4 @@ namespace clio static constexpr const char* description = "Intel PresentMon service for frame and system performance measurement"; static constexpr const char* name = "PresentMonService.exe"; }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonService/PMMainThread.cpp b/IntelPresentMon/PresentMonService/PMMainThread.cpp index 01f66e2c..84037104 100644 --- a/IntelPresentMon/PresentMonService/PMMainThread.cpp +++ b/IntelPresentMon/PresentMonService/PMMainThread.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #include "Logging.h" #include "Service.h" @@ -13,6 +13,7 @@ #include "../Interprocess/source/ShmNamer.h" #include "../Interprocess/source/MetricCapabilitiesShim.h" #include "CliOptions.h" +#include "Registry.h" #include "GlobalIdentifiers.h" #include #include "../CommonUtilities/IntervalWaiter.h" @@ -557,6 +558,15 @@ void PresentMonMainThread(Service* const pSvc) try { // alias for options auto& opt = clio::Options::Get(); + const auto& reg = Reg::Get(); + const auto frameRingSamples = opt.frameRingSamples.AsOptional().value_or( + reg.frameRingSamples.AsOptional() + .transform([](auto val) { return static_cast(val); }) + .value_or(*opt.frameRingSamples)); + const auto telemetryRingSamples = opt.telemetryRingSamples.AsOptional().value_or( + reg.telemetryRingSamples.AsOptional() + .transform([](auto val) { return static_cast(val); }) + .value_or(*opt.telemetryRingSamples)); // spin here waiting for debugger to attach, after which debugger should set // debug_service to false in order to proceed @@ -591,7 +601,9 @@ void PresentMonMainThread(Service* const pSvc) std::unique_ptr pComms; try { pmlog_dbg("Creating comms with shm prefix: ").pmwatch(*opt.shmNamePrefix); - pComms = ipc::MakeServiceComms(*opt.shmNamePrefix); + pComms = ipc::MakeServiceComms(*opt.shmNamePrefix, + frameRingSamples, + telemetryRingSamples); pmlog_info("Created comms with introspection shm name: ") .pmwatch(pComms->GetNamer().MakeIntrospectionName()); } @@ -636,8 +648,6 @@ void PresentMonMainThread(Service* const pSvc) } if (cpu) { - cpuTelemetryThread = std::jthread{ CpuTelemetryThreadEntry_, pSvc, &pm, pComms.get(), - cpu.get(), opt.newTelemetryActivation }; pm.SetCpu(cpu); // sample once to populate the cap bits cpu->Sample(); @@ -667,6 +677,8 @@ void PresentMonMainThread(Service* const pSvc) systemStore.statics.cpuName = cpu->GetCpuName().c_str(); systemStore.statics.cpuPowerLimit = cpu->GetCpuPowerLimit(); systemStore.statics.cpuVendor = vendor; + cpuTelemetryThread = std::jthread{ CpuTelemetryThreadEntry_, pSvc, &pm, pComms.get(), + cpu.get(), opt.newTelemetryActivation }; } else { // We were unable to determine the cpu. pComms->RegisterCpuDevice(PM_DEVICE_VENDOR_UNKNOWN, "UNKNOWN_CPU", diff --git a/IntelPresentMon/PresentMonService/Registry.h b/IntelPresentMon/PresentMonService/Registry.h index e2976992..96e0f151 100644 --- a/IntelPresentMon/PresentMonService/Registry.h +++ b/IntelPresentMon/PresentMonService/Registry.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "../CommonUtilities/reg/Registry.h" #include "../CommonUtilities/log/Level.h" #include "GlobalIdentifiers.h" @@ -10,6 +10,8 @@ struct Reg : public reg::RegistryBase Value logDir{ this, "logDir" }; Value middlewarePath{ this, pmon::gid::middlewarePathKey }; Value logVerboseModules{ this, "logVerboseModules" }; + Value frameRingSamples{ this, "frameRingSamples" }; + Value telemetryRingSamples{ this, "telemetryRingSamples" }; static constexpr const wchar_t* keyPath_ = pmon::gid::registryPath; -}; \ No newline at end of file +}; diff --git a/IntelPresentMon/SampleClient/IpcComponentServer.cpp b/IntelPresentMon/SampleClient/IpcComponentServer.cpp index a21020b4..b8664800 100644 --- a/IntelPresentMon/SampleClient/IpcComponentServer.cpp +++ b/IntelPresentMon/SampleClient/IpcComponentServer.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2025 Intel Corporation +// Copyright (C) 2022-2025 Intel Corporation // SPDX-License-Identifier: MIT #include "../Interprocess/source/OwnedDataSegment.h" @@ -28,16 +28,16 @@ static constexpr const char* kSystemSegName = "pm_ipc_system_store_test_seg"; // The test goal is ring push/read plumbing, not capability validation. static constexpr PM_METRIC kScalarMetric = PM_METRIC_CPU_FREQUENCY; static constexpr PM_METRIC kArrayMetric = PM_METRIC_CPU_UTILIZATION; +static constexpr size_t kSystemRingCapacity = 32; +static constexpr size_t kSystemSegmentBytes = 512 * 1024; static void BuildRings_(ipc::SystemDataStore& store) { - constexpr size_t ringCapacity = 32; - // Scalar metric - store.telemetryData.AddRing(kScalarMetric, ringCapacity, 1, PM_DATA_TYPE_DOUBLE); + store.telemetryData.AddRing(kScalarMetric, kSystemRingCapacity, 1, PM_DATA_TYPE_DOUBLE); // Array metric with 2 elements - store.telemetryData.AddRing(kArrayMetric, ringCapacity, 2, PM_DATA_TYPE_DOUBLE); + store.telemetryData.AddRing(kArrayMetric, kSystemRingCapacity, 2, PM_DATA_TYPE_DOUBLE); } static void PushDeterministicSamples_(ipc::SystemDataStore& store) @@ -77,8 +77,14 @@ static void PushDeterministicSamples_(ipc::SystemDataStore& store) // Submode entry point. int IpcComponentServer() { + ipc::DataStoreSizingInfo sizing{}; + sizing.overrideBytes = kSystemSegmentBytes; + // Create the shared memory segment hosting SystemDataStore. - ipc::OwnedDataSegment seg{ kSystemSegName }; + ipc::OwnedDataSegment seg{ + kSystemSegName, + sizing + }; auto& store = seg.GetStore(); // Only build the two test rings. @@ -114,3 +120,4 @@ int IpcComponentServer() return -1; } + diff --git a/IntelPresentMon/SampleClient/SampleClient.vcxproj b/IntelPresentMon/SampleClient/SampleClient.vcxproj index d533a812..46ae1010 100644 --- a/IntelPresentMon/SampleClient/SampleClient.vcxproj +++ b/IntelPresentMon/SampleClient/SampleClient.vcxproj @@ -1,4 +1,4 @@ - + @@ -138,6 +138,9 @@ {08a704d8-ca1c-45e9-8ede-542a1a43b53e} + + {ca23d648-daef-4f06-81d5-fe619bd31f0b} + {8f86d067-2437-46fc-8f82-4d7155ceced7} @@ -148,4 +151,4 @@ - \ No newline at end of file + From 7a0395e9c8bc7e6d7ae4552c4ff53b1ac402725b Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 29 Dec 2025 10:40:38 +0900 Subject: [PATCH 082/205] improve cohesion of ring population --- .../Interprocess/source/DataStores.cpp | 41 +++++++++++++++---- .../Interprocess/source/DataStores.h | 4 ++ .../Interprocess/source/Interprocess.cpp | 41 +++++-------------- 3 files changed, 46 insertions(+), 40 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/DataStores.cpp b/IntelPresentMon/Interprocess/source/DataStores.cpp index 46098da1..8239910a 100644 --- a/IntelPresentMon/Interprocess/source/DataStores.cpp +++ b/IntelPresentMon/Interprocess/source/DataStores.cpp @@ -56,11 +56,11 @@ namespace pmon::ipc return true; } - size_t TelemetrySegmentBytes(const DataStoreSizingInfo& sizing, PM_DEVICE_TYPE deviceType) + template + void ForEachTelemetryRing(const DataStoreSizingInfo& sizing, + PM_DEVICE_TYPE deviceType, + Func&& func) { - if (sizing.overrideBytes) { - return *sizing.overrideBytes; - } if (!sizing.pRoot || !sizing.pCaps) { throw std::logic_error("DataStoreSizingInfo requires introspection root and caps"); } @@ -70,8 +70,6 @@ namespace pmon::ipc deviceTypeById.emplace(pDevice->GetId(), pDevice->GetType()); } - size_t ringCount = 0; - size_t payloadBytes = 0; for (auto&& [metricId, count] : *sizing.pCaps) { const auto& metric = sizing.pRoot->FindMetric(metricId); bool matchesDeviceType = false; @@ -89,10 +87,25 @@ namespace pmon::ipc if (!ShouldAllocateTelemetryRing(metricId, metric)) { continue; } - payloadBytes += count * sizing.ringSamples * - EstimateSampleBytes(metric.GetDataTypeInfo().GetFrameType()); - ringCount += count; + const auto dataType = metric.GetDataTypeInfo().GetFrameType(); + func(metricId, count, dataType); } + } + + size_t TelemetrySegmentBytes(const DataStoreSizingInfo& sizing, PM_DEVICE_TYPE deviceType) + { + if (sizing.overrideBytes) { + return *sizing.overrideBytes; + } + + size_t ringCount = 0; + size_t payloadBytes = 0; + ForEachTelemetryRing(sizing, deviceType, + [&](PM_METRIC, size_t count, PM_DATA_TYPE dataType) { + payloadBytes += count * sizing.ringSamples * + EstimateSampleBytes(dataType); + ringCount += count; + }); const size_t leewayBytes = ringCount * kTelemetryLeewayPerRingBytes + kTelemetryLeewayBaseBytes; @@ -107,6 +120,16 @@ namespace pmon::ipc return PadToAlignment(payloadBytes + leewayBytes, kSegmentAlignmentBytes); } + void PopulateTelemetryRings(TelemetryMap& telemetryData, + const DataStoreSizingInfo& sizing, + PM_DEVICE_TYPE deviceType) + { + ForEachTelemetryRing(sizing, deviceType, + [&](PM_METRIC metricId, size_t count, PM_DATA_TYPE dataType) { + telemetryData.AddRing(metricId, sizing.ringSamples, count, dataType); + }); + } + size_t GpuDataStore::CalculateSegmentBytes(const DataStoreSizingInfo& sizing) { return TelemetrySegmentBytes(sizing, PM_DEVICE_TYPE_GRAPHICS_ADAPTER); diff --git a/IntelPresentMon/Interprocess/source/DataStores.h b/IntelPresentMon/Interprocess/source/DataStores.h index 4e027eeb..21b3bdce 100644 --- a/IntelPresentMon/Interprocess/source/DataStores.h +++ b/IntelPresentMon/Interprocess/source/DataStores.h @@ -110,5 +110,9 @@ namespace pmon::ipc static size_t CalculateSegmentBytes(const DataStoreSizingInfo& sizing); }; + + void PopulateTelemetryRings(TelemetryMap& telemetryData, + const DataStoreSizingInfo& sizing, + PM_DEVICE_TYPE deviceType); } diff --git a/IntelPresentMon/Interprocess/source/Interprocess.cpp b/IntelPresentMon/Interprocess/source/Interprocess.cpp index e0d14be4..de750f6d 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.cpp +++ b/IntelPresentMon/Interprocess/source/Interprocess.cpp @@ -70,32 +70,19 @@ namespace pmon::ipc shm_.get_segment_manager(), *pRoot_, deviceId, vendor, deviceName, caps ); + const DataStoreSizingInfo sizing{ pRoot_.get().get(), &caps, telemetryRingSamples_ }; // allocate map node and create shm segment auto& gpuShm = gpuShms_.emplace( std::piecewise_construct, std::forward_as_tuple(deviceId), std::forward_as_tuple(namer_.MakeGpuName(deviceId), - DataStoreSizingInfo{ pRoot_.get().get(), &caps, telemetryRingSamples_ }, + sizing, static_cast(Permissions_{})) ).first->second; // populate rings based on caps - for (auto&& [m, count] : caps) { - const auto& metric = pRoot_->FindMetric(m); - const auto metricType = metric.GetMetricType(); - // static metrics don't get rings - if (metricType == PM_METRIC_TYPE_STATIC) { - continue; - } - // TODO: systemize the labelling of metrics that are middleware-derived - if (m == PM_METRIC_GPU_FAN_SPEED_PERCENT || - m == PM_METRIC_GPU_MEM_UTILIZATION) { - continue; - } - const auto dataType = metric.GetDataTypeInfo().GetFrameType(); - gpuShm.GetStore().telemetryData.AddRing( - m, telemetryRingSamples_, count, dataType - ); - } + PopulateTelemetryRings(gpuShm.GetStore().telemetryData, + sizing, + PM_DEVICE_TYPE_GRAPHICS_ADAPTER); } void FinalizeGpuDevices() override { @@ -114,24 +101,16 @@ namespace pmon::ipc intro::PopulateCpu( shm_.get_segment_manager(), *pRoot_, vendor, std::move(deviceName), caps ); + const DataStoreSizingInfo sizing{ pRoot_.get().get(), &caps, telemetryRingSamples_ }; if (!systemShm_) { systemShm_.emplace(namer_.MakeSystemName(), - DataStoreSizingInfo{ pRoot_.get().get(), &caps, telemetryRingSamples_ }, + sizing, static_cast(Permissions_{})); } // populate rings based on caps - for (auto&& [m, count] : caps) { - const auto& metric = pRoot_->FindMetric(m); - const auto metricType = metric.GetMetricType(); - // static metrics don't get rings - if (metricType == PM_METRIC_TYPE_STATIC) { - continue; - } - const auto dataType = metric.GetDataTypeInfo().GetFrameType(); - systemShm_->GetStore().telemetryData.AddRing( - m, telemetryRingSamples_, count, dataType - ); - } + PopulateTelemetryRings(systemShm_->GetStore().telemetryData, + sizing, + PM_DEVICE_TYPE_SYSTEM); introCpuComplete_ = true; if (introGpuComplete_ && introCpuComplete_) { lck.unlock(); From dc17c0ed4fd910bd1700ebaaa5d9945fccaf6d72 Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 29 Dec 2025 14:02:16 +0900 Subject: [PATCH 083/205] better logging of sizing --- IntelPresentMon/CommonUtilities/Memory.h | 9 +- IntelPresentMon/CommonUtilities/log/Verbose.h | 5 +- .../CommonUtilities/reg/Registry.h | 16 ++-- .../Interprocess/source/DataStores.cpp | 83 +++++++++++++------ .../Interprocess/source/Interprocess.cpp | 17 +++- .../Interprocess/source/OwnedDataSegment.h | 14 +++- .../InterimBroadcasterTests.cpp | 5 +- .../PresentMonAPI2Tests/Logging.cpp | 53 +++++++++++- IntelPresentMon/PresentMonAPI2Tests/Logging.h | 5 +- .../PresentMonAPI2Tests/TestProcess.h | 43 +++++++++- IntelPresentMon/SampleClient/CliOptions.h | 6 +- IntelPresentMon/SampleClient/LogSetup.cpp | 10 ++- 12 files changed, 214 insertions(+), 52 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/Memory.h b/IntelPresentMon/CommonUtilities/Memory.h index d3daf46b..a2577d4f 100644 --- a/IntelPresentMon/CommonUtilities/Memory.h +++ b/IntelPresentMon/CommonUtilities/Memory.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include namespace pmon::util @@ -16,6 +16,11 @@ namespace pmon::util return GetPadding(byteIndex, alignof(T)); } + inline constexpr size_t PadToAlignment(size_t bytes, size_t alignment) + { + return bytes + GetPadding(bytes, alignment); + } + template class CloningUptr : public std::unique_ptr { @@ -119,4 +124,4 @@ namespace pmon::util // unique_ptr that uses LocalFree. template using UniqueLocalPtr = std::unique_ptr>; -} \ No newline at end of file +} diff --git a/IntelPresentMon/CommonUtilities/log/Verbose.h b/IntelPresentMon/CommonUtilities/log/Verbose.h index 63f3a0cc..7d8273cb 100644 --- a/IntelPresentMon/CommonUtilities/log/Verbose.h +++ b/IntelPresentMon/CommonUtilities/log/Verbose.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include #include @@ -14,9 +14,10 @@ namespace pmon::util::log core_window, etwq, kact, + ipc_sto, Count }; std::string GetVerboseModuleName(V mod) noexcept; std::map GetVerboseModuleMapNarrow() noexcept; -} \ No newline at end of file +} diff --git a/IntelPresentMon/CommonUtilities/reg/Registry.h b/IntelPresentMon/CommonUtilities/reg/Registry.h index 98118106..963fa043 100644 --- a/IntelPresentMon/CommonUtilities/reg/Registry.h +++ b/IntelPresentMon/CommonUtilities/reg/Registry.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include @@ -29,19 +29,17 @@ namespace pmon::util::reg {} std::optional AsOptional() const noexcept { + if (!Exists()) { + return {}; + } try { return Get(); } catch (const RegistryNotOpen&) { pmlog_warn("Optional access of registry value when key not open"); } - catch (const winreg::RegException& e) { - if (e.code().value() == 2) { - pmlog_dbg("Optional access of absent value"); - } - else { - pmlog_error(ReportException("Error during optional access of registry value")); - } + catch (const winreg::RegException&) { + pmlog_error(ReportException("Error during optional access of registry value")); } catch (...) { pmlog_error(ReportException("Unknown error during optional access of registry value")); @@ -209,4 +207,4 @@ namespace pmon::util::reg private: inline static bool readonly_ = false; }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/Interprocess/source/DataStores.cpp b/IntelPresentMon/Interprocess/source/DataStores.cpp index 8239910a..e660775d 100644 --- a/IntelPresentMon/Interprocess/source/DataStores.cpp +++ b/IntelPresentMon/Interprocess/source/DataStores.cpp @@ -3,7 +3,9 @@ #include "IntrospectionTransfer.h" #include "IntrospectionDataTypeMapping.h" #include "../../CommonUtilities/Memory.h" +#include "../../CommonUtilities/log/Verbose.h" #include +#include #include #include @@ -11,19 +13,21 @@ namespace pmon::ipc { namespace { - constexpr size_t kSegmentAlignmentBytes = 64 * 1024; - constexpr size_t kFrameLeewayPerRingBytes = 2 * 1024 * 1024; - constexpr size_t kFrameLeewayBaseBytes = 8 * 1024 * 1024; - constexpr size_t kTelemetryLeewayPerRingBytes = 64 * 1024; - constexpr size_t kTelemetryLeewayBaseBytes = 2 * 1024 * 1024; - - size_t PadToAlignment(size_t bytes, size_t alignment) + constexpr size_t kSegmentAlignmentBytes_ = 64 * 1024; + constexpr size_t kFrameScaleMul_ = 3; + constexpr size_t kFrameScaleDiv_ = 2; + constexpr size_t kTelemetryScaleGpuMul_ = 3; + constexpr size_t kTelemetryScaleSystemMul_ = 2; + constexpr size_t kTelemetryScaleDiv_ = 1; + constexpr size_t kFixedLeewayBytes_ = 4 * 1024; + + size_t ScaleBytes_(size_t bytes, size_t numerator, size_t denominator) { - return bytes + util::GetPadding(bytes, alignment); + return (bytes * numerator + denominator - 1) / denominator; } template - struct DataTypeSizeBridger + struct DataTypeSizeBridger_ { static size_t Invoke() { @@ -35,15 +39,15 @@ namespace pmon::ipc } }; - size_t EstimateSampleBytes(PM_DATA_TYPE type) + size_t EstimateSampleBytes_(PM_DATA_TYPE type) { - const size_t valueBytes = intro::BridgeDataType(type); + const size_t valueBytes = intro::BridgeDataType(type); const size_t safeValueBytes = valueBytes > 0 ? valueBytes : sizeof(uint32_t); const size_t pad = util::GetPadding(safeValueBytes, alignof(uint64_t)); return safeValueBytes + pad + sizeof(uint64_t); } - bool ShouldAllocateTelemetryRing(PM_METRIC metricId, + bool ShouldAllocateTelemetryRing_(PM_METRIC metricId, const intro::IntrospectionMetric& metric) { if (metric.GetMetricType() == PM_METRIC_TYPE_STATIC) { @@ -57,7 +61,7 @@ namespace pmon::ipc } template - void ForEachTelemetryRing(const DataStoreSizingInfo& sizing, + void ForEachTelemetryRing_(const DataStoreSizingInfo& sizing, PM_DEVICE_TYPE deviceType, Func&& func) { @@ -84,7 +88,7 @@ namespace pmon::ipc throw std::logic_error( "DataStoreSizingInfo caps contain a metric outside the expected device type"); } - if (!ShouldAllocateTelemetryRing(metricId, metric)) { + if (!ShouldAllocateTelemetryRing_(metricId, metric)) { continue; } const auto dataType = metric.GetDataTypeInfo().GetFrameType(); @@ -92,7 +96,7 @@ namespace pmon::ipc } } - size_t TelemetrySegmentBytes(const DataStoreSizingInfo& sizing, PM_DEVICE_TYPE deviceType) + size_t TelemetrySegmentBytes_(const DataStoreSizingInfo& sizing, PM_DEVICE_TYPE deviceType) { if (sizing.overrideBytes) { return *sizing.overrideBytes; @@ -100,31 +104,56 @@ namespace pmon::ipc size_t ringCount = 0; size_t payloadBytes = 0; - ForEachTelemetryRing(sizing, deviceType, - [&](PM_METRIC, size_t count, PM_DATA_TYPE dataType) { - payloadBytes += count * sizing.ringSamples * - EstimateSampleBytes(dataType); + ForEachTelemetryRing_(sizing, deviceType, + [&](PM_METRIC metricId, size_t count, PM_DATA_TYPE dataType) { + const size_t sampleBytes = EstimateSampleBytes_(dataType); + const size_t metricBytes = count * sizing.ringSamples * sampleBytes; + payloadBytes += metricBytes; ringCount += count; + pmlog_verb(util::log::V::ipc_sto)(std::format( + "ipc telem metric sizing | metric:{} count:{} ring_samples:{} sample_bytes:{} payload_bytes:{}", + static_cast(metricId), count, sizing.ringSamples, sampleBytes, metricBytes)); }); - const size_t leewayBytes = - ringCount * kTelemetryLeewayPerRingBytes + kTelemetryLeewayBaseBytes; - return PadToAlignment(payloadBytes + leewayBytes, kSegmentAlignmentBytes); + const size_t scaleMul = (deviceType == PM_DEVICE_TYPE_SYSTEM) ? + kTelemetryScaleSystemMul_ : kTelemetryScaleGpuMul_; + size_t scaledBytes = + ScaleBytes_(payloadBytes, scaleMul, kTelemetryScaleDiv_); + if (scaledBytes < payloadBytes + kFixedLeewayBytes_) { + scaledBytes = payloadBytes + kFixedLeewayBytes_; + } + const size_t leewayBytes = scaledBytes - payloadBytes; + const size_t totalBytes = util::PadToAlignment(scaledBytes, kSegmentAlignmentBytes_); + pmlog_verb(util::log::V::ipc_sto)(std::format( + "ipc telem sizing | ring_samples:{} ring_count:{} payload_bytes:{} scaled_bytes:{} fixed_leeway_bytes:{} leeway_bytes:{} alignment:{} total_bytes:{}", + sizing.ringSamples, ringCount, payloadBytes, scaledBytes, kFixedLeewayBytes_, + leewayBytes, kSegmentAlignmentBytes_, totalBytes)); + return totalBytes; } } size_t FrameDataStore::CalculateSegmentBytes(const DataStoreSizingInfo& sizing) { const size_t payloadBytes = sizing.ringSamples * sizeof(FrameData); - const size_t leewayBytes = kFrameLeewayPerRingBytes + kFrameLeewayBaseBytes; - return PadToAlignment(payloadBytes + leewayBytes, kSegmentAlignmentBytes); + size_t scaledBytes = + ScaleBytes_(payloadBytes, kFrameScaleMul_, kFrameScaleDiv_); + if (scaledBytes < payloadBytes + kFixedLeewayBytes_) { + scaledBytes = payloadBytes + kFixedLeewayBytes_; + } + const size_t leewayBytes = scaledBytes - payloadBytes; + const size_t totalBytes = util::PadToAlignment(scaledBytes, kSegmentAlignmentBytes_); + pmlog_verb(util::log::V::ipc_sto)(std::format( + "ipc frame sizing | ring_samples:{} payload_bytes:{} scaled_bytes:{} fixed_leeway_bytes:{} leeway_bytes:{} alignment:{} total_bytes:{}", + sizing.ringSamples, payloadBytes, scaledBytes, kFixedLeewayBytes_, + leewayBytes, kSegmentAlignmentBytes_, totalBytes)); + return totalBytes; } void PopulateTelemetryRings(TelemetryMap& telemetryData, const DataStoreSizingInfo& sizing, PM_DEVICE_TYPE deviceType) { - ForEachTelemetryRing(sizing, deviceType, + ForEachTelemetryRing_(sizing, deviceType, [&](PM_METRIC metricId, size_t count, PM_DATA_TYPE dataType) { telemetryData.AddRing(metricId, sizing.ringSamples, count, dataType); }); @@ -132,12 +161,12 @@ namespace pmon::ipc size_t GpuDataStore::CalculateSegmentBytes(const DataStoreSizingInfo& sizing) { - return TelemetrySegmentBytes(sizing, PM_DEVICE_TYPE_GRAPHICS_ADAPTER); + return TelemetrySegmentBytes_(sizing, PM_DEVICE_TYPE_GRAPHICS_ADAPTER); } size_t SystemDataStore::CalculateSegmentBytes(const DataStoreSizingInfo& sizing) { - return TelemetrySegmentBytes(sizing, PM_DEVICE_TYPE_SYSTEM); + return TelemetrySegmentBytes_(sizing, PM_DEVICE_TYPE_SYSTEM); } } diff --git a/IntelPresentMon/Interprocess/source/Interprocess.cpp b/IntelPresentMon/Interprocess/source/Interprocess.cpp index de750f6d..d5882fca 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.cpp +++ b/IntelPresentMon/Interprocess/source/Interprocess.cpp @@ -71,11 +71,12 @@ namespace pmon::ipc deviceId, vendor, deviceName, caps ); const DataStoreSizingInfo sizing{ pRoot_.get().get(), &caps, telemetryRingSamples_ }; + const auto segmentName = namer_.MakeGpuName(deviceId); // allocate map node and create shm segment auto& gpuShm = gpuShms_.emplace( std::piecewise_construct, std::forward_as_tuple(deviceId), - std::forward_as_tuple(namer_.MakeGpuName(deviceId), + std::forward_as_tuple(segmentName, sizing, static_cast(Permissions_{})) ).first->second; @@ -83,6 +84,12 @@ namespace pmon::ipc PopulateTelemetryRings(gpuShm.GetStore().telemetryData, sizing, PM_DEVICE_TYPE_GRAPHICS_ADAPTER); + pmlog_dbg("Shm segment populated (GPU)") + .pmwatch(segmentName) + .pmwatch(deviceId) + .pmwatch(gpuShm.GetBytesTotal()) + .pmwatch(gpuShm.GetBytesUsed()) + .pmwatch(gpuShm.GetBytesFree()); } void FinalizeGpuDevices() override { @@ -102,8 +109,9 @@ namespace pmon::ipc shm_.get_segment_manager(), *pRoot_, vendor, std::move(deviceName), caps ); const DataStoreSizingInfo sizing{ pRoot_.get().get(), &caps, telemetryRingSamples_ }; + const auto segmentName = namer_.MakeSystemName(); if (!systemShm_) { - systemShm_.emplace(namer_.MakeSystemName(), + systemShm_.emplace(segmentName, sizing, static_cast(Permissions_{})); } @@ -111,6 +119,11 @@ namespace pmon::ipc PopulateTelemetryRings(systemShm_->GetStore().telemetryData, sizing, PM_DEVICE_TYPE_SYSTEM); + pmlog_dbg("Shm segment populated (System)") + .pmwatch(segmentName) + .pmwatch(systemShm_->GetBytesTotal()) + .pmwatch(systemShm_->GetBytesUsed()) + .pmwatch(systemShm_->GetBytesFree()); introCpuComplete_ = true; if (introGpuComplete_ && introCpuComplete_) { lck.unlock(); diff --git a/IntelPresentMon/Interprocess/source/OwnedDataSegment.h b/IntelPresentMon/Interprocess/source/OwnedDataSegment.h index a9b3f0dc..bf37f524 100644 --- a/IntelPresentMon/Interprocess/source/OwnedDataSegment.h +++ b/IntelPresentMon/Interprocess/source/OwnedDataSegment.h @@ -2,6 +2,7 @@ #include "SharedMemoryTypes.h" #include "DataStores.h" #include "../../CommonUtilities/log/Log.h" +#include namespace pmon::ipc { @@ -19,10 +20,21 @@ namespace pmon::ipc ResolveSegmentBytes_(segmentName, sizing), nullptr, perms }, pData_{ MakeStore_(sizing) } - {} + { + if constexpr (std::is_same_v) { + pmlog_dbg("Shm segment populated (Frame)") + .pmwatch(segmentName) + .pmwatch(GetBytesTotal()) + .pmwatch(GetBytesUsed()) + .pmwatch(GetBytesFree()); + } + } T& GetStore() { return *pData_; } const T& GetStore() const { return *pData_; } + size_t GetBytesUsed() const { return shm_.get_size() - shm_.get_free_memory(); } + size_t GetBytesFree() const { return shm_.get_free_memory(); } + size_t GetBytesTotal() const { return shm_.get_size(); } private: static constexpr const char* name_ = "seg-dat"; diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 2fbeebb0..cfa91a92 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -71,8 +71,9 @@ namespace InterimBroadcasterTests { static CommonProcessArgs args{ .ctrlPipe = R"(\\.\pipe\pm-intbroad-test-ctrl)", - .shmNamePrefix = "pm_intborad_test_intro", - .logLevel = "debug", + .shmNamePrefix = "pm_intbroad_test", + .logLevel = "verbose", + .logVerboseModules = "ipc_sto", .logFolder = logFolder_, .sampleClientMode = "NONE", }; diff --git a/IntelPresentMon/PresentMonAPI2Tests/Logging.cpp b/IntelPresentMon/PresentMonAPI2Tests/Logging.cpp index 8096d37a..7614d2be 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/Logging.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/Logging.cpp @@ -10,6 +10,7 @@ #include "../CommonUtilities/log/ErrorCodeResolvePolicy.h" #include "../CommonUtilities/log/ErrorCodeResolver.h" #include "../CommonUtilities/log/IdentificationTable.h" +#include "../CommonUtilities/log/Verbose.h" #include "../CommonUtilities/str/String.h" #include "../CommonUtilities/win/HrErrorCodeProvider.h" #include "../CommonUtilities/win/WinAPI.h" @@ -17,6 +18,7 @@ #include "../PresentMonAPIWrapperCommon/PmErrorCodeProvider.h" #include "../PresentMonAPI2/Internal.h" +#include #include #include #include @@ -88,11 +90,54 @@ namespace pmon::test { return std::format("test-harness-{}.txt", GetCurrentProcessId()); } + + std::vector SplitVerboseModules_(const std::string& raw) + { + std::vector tokens; + std::string token; + for (unsigned char ch : raw) { + if (ch == ',' || std::isspace(ch)) { + if (!token.empty()) { + tokens.push_back(token); + token.clear(); + } + continue; + } + token.push_back(static_cast(ch)); + } + if (!token.empty()) { + tokens.push_back(token); + } + return tokens; + } + + std::vector ParseVerboseModules_(const std::string& raw) + { + std::vector modules; + if (raw.empty()) { + return modules; + } + const auto map = util::log::GetVerboseModuleMapNarrow(); + for (const auto& token : SplitVerboseModules_(raw)) { + const auto key = util::str::ToLower(token); + if (auto it = map.find(key); it != map.end()) { + modules.push_back(it->second); + } + } + return modules; + } } - void SetupTestLogging(const std::string& logFolder, const std::string& logLevel) noexcept + void SetupTestLogging(const std::string& logFolder, + const std::string& logLevel, + const std::optional& logVerboseModules) noexcept { try { + util::log::IdentificationTable::AddThisProcess("ms-test"); + util::log::IdentificationTable::AddThisThread("exec"); + + const auto verboseModules = + logVerboseModules ? ParseVerboseModules_(*logVerboseModules) : std::vector{}; auto pChannel = util::log::GetDefaultChannel(); if (!pChannel) { return; @@ -103,6 +148,9 @@ namespace pmon::test policy.SetLogLevel(level); policy.SetTraceLevel(util::log::Level::Error); policy.SetExceptionTrace(false); + for (auto mod : verboseModules) { + policy.ActivateVerboseModule(mod); + } if (!logFolder.empty()) { std::filesystem::path folderPath{ logFolder }; @@ -134,6 +182,9 @@ namespace pmon::test dllPolicy.SetLogLevel(level); dllPolicy.SetTraceLevel(util::log::Level::Error); dllPolicy.SetExceptionTrace(false); + for (auto mod : verboseModules) { + dllPolicy.ActivateVerboseModule(mod); + } } } catch (...) { diff --git a/IntelPresentMon/PresentMonAPI2Tests/Logging.h b/IntelPresentMon/PresentMonAPI2Tests/Logging.h index 53a64194..ac7ab98d 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/Logging.h +++ b/IntelPresentMon/PresentMonAPI2Tests/Logging.h @@ -1,9 +1,12 @@ #pragma once +#include #include namespace pmon::test { - void SetupTestLogging(const std::string& logFolder, const std::string& logLevel) noexcept; + void SetupTestLogging(const std::string& logFolder, + const std::string& logLevel, + const std::optional& logVerboseModules) noexcept; struct LogChannelManager { diff --git a/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h b/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h index 7b22bd6f..345ec8d7 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h +++ b/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h @@ -10,8 +10,10 @@ #include "../CommonUtilities/pipe/Pipe.h" #include #include +#include #include #include +#include #include #include @@ -28,10 +30,46 @@ struct CommonProcessArgs std::string ctrlPipe; std::string shmNamePrefix; std::string logLevel; + std::optional logVerboseModules; std::string logFolder; std::string sampleClientMode; }; +inline std::vector SplitVerboseModulesArgs_(const std::string& raw) +{ + std::vector modules; + std::string token; + for (unsigned char ch : raw) { + if (ch == ',' || std::isspace(ch)) { + if (!token.empty()) { + modules.push_back(token); + token.clear(); + } + continue; + } + token.push_back(static_cast(ch)); + } + if (!token.empty()) { + modules.push_back(token); + } + return modules; +} + +inline void AppendVerboseModulesArgs_(std::vector& args, + const std::optional& modules, + const char* flag) +{ + if (!modules || modules->empty()) { + return; + } + const auto values = SplitVerboseModulesArgs_(*modules); + if (values.empty()) { + return; + } + args.push_back(flag); + args.insert(args.end(), values.begin(), values.end()); +} + // base class to represent child processes launched by test cases class TestProcess { @@ -170,6 +208,7 @@ class ServiceProcess : public ConnectedTestProcess "--log-level"s, common.logLevel, "--enable-debugger-log"s, }; + AppendVerboseModulesArgs_(allArgs, common.logVerboseModules, "--log-verbose-modules"); allArgs.append_range(customArgs); return allArgs; } @@ -205,6 +244,7 @@ class ClientProcess : public ConnectedTestProcess "--log-level"s, common.logLevel, "--mode"s, common.sampleClientMode, }; + AppendVerboseModulesArgs_(allArgs, common.logVerboseModules, "--log-verbose-modules"); allArgs.append_range(customArgs); return allArgs; } @@ -261,7 +301,8 @@ class CommonTestFixture if (!logManager_) { logManager_.emplace(); } - pmon::test::SetupTestLogging(GetCommonArgs().logFolder, GetCommonArgs().logLevel); + pmon::test::SetupTestLogging(GetCommonArgs().logFolder, GetCommonArgs().logLevel, + GetCommonArgs().logVerboseModules); StartService_(args, GetCommonArgs()); svcArgs_ = std::move(args); } diff --git a/IntelPresentMon/SampleClient/CliOptions.h b/IntelPresentMon/SampleClient/CliOptions.h index 8276e3ad..c38020f0 100644 --- a/IntelPresentMon/SampleClient/CliOptions.h +++ b/IntelPresentMon/SampleClient/CliOptions.h @@ -1,6 +1,7 @@ -#pragma once +#pragma once #include "../CommonUtilities/cli/CliFramework.h" #include "../CommonUtilities/log/Level.h" +#include "../CommonUtilities/log/Verbose.h" #include "../CommonUtilities/ref/StaticReflection.h" #include @@ -58,6 +59,7 @@ namespace clio Option logAllowList{ this, "--log-allow-list", "", "Path to log allow list (with trace overrides)", CLI::ExistingFile }; Option logFolder{ this, "--log-folder", "", "Folder to create log file(s) in" }; Flag logNamePid{ this, "--log-name-pid", "Append PID to the log file name" }; + Option> logVerboseModules{ this, "--log-verbose-modules", {}, "Verbose logging modules to enable", CLI::CheckedTransformer{ log::GetVerboseModuleMapNarrow(), CLI::ignore_case } }; private: Group gv_{ this, "Service", "Control service options" }; public: Flag servicePacePlayback{ this, "--service-pace-playback", "Pace ETL playback on the service" }; Option serviceEtlPath{ this, "--service-etl-path", "", "Path of the ETL file to pass to the service for playback" }; @@ -71,4 +73,4 @@ namespace clio MutualExclusion logListExclusion_{ logDenyList, logAllowList }; Mandatory mandatoryMode_{ mode }; }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/SampleClient/LogSetup.cpp b/IntelPresentMon/SampleClient/LogSetup.cpp index b3ae3eaa..8135c51e 100644 --- a/IntelPresentMon/SampleClient/LogSetup.cpp +++ b/IntelPresentMon/SampleClient/LogSetup.cpp @@ -1,4 +1,4 @@ -#include "../CommonUtilities/log/Log.h" +#include "../CommonUtilities/log/Log.h" #include "../CommonUtilities/log/Channel.h" #include "../CommonUtilities/log/MsvcDebugDriver.h" #include "../CommonUtilities/log/BasicFileDriver.h" @@ -83,6 +83,12 @@ namespace p2sam GlobalPolicy::Get().SetLogLevel(*opt.logLevel); getters.getGlobalPolicy().SetLogLevel(*opt.logLevel); } + if (opt.logVerboseModules) { + for (auto mod : *opt.logVerboseModules) { + GlobalPolicy::Get().ActivateVerboseModule(mod); + getters.getGlobalPolicy().ActivateVerboseModule(mod); + } + } if (opt.logTraceLevel) { GlobalPolicy::Get().SetTraceLevel(*opt.logTraceLevel); getters.getGlobalPolicy().SetTraceLevel(*opt.logTraceLevel); @@ -114,4 +120,4 @@ namespace p2sam pmFlushEntryPoint_(); FlushEntryPoint(); } -} \ No newline at end of file +} From e25c4884e3a5196f13c324d448b75ed159e12815 Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 29 Dec 2025 16:49:40 +0900 Subject: [PATCH 084/205] wrapping tests for ipc w/o middleware --- .../PresentMonAPI2Tests/IpcComponentTests.cpp | 159 +++++++++++++++++- .../PresentMonAPI2Tests/TestProcess.h | 23 ++- IntelPresentMon/SampleClient/CliOptions.h | 5 + .../SampleClient/IpcComponentServer.cpp | 37 ++-- 4 files changed, 205 insertions(+), 19 deletions(-) diff --git a/IntelPresentMon/PresentMonAPI2Tests/IpcComponentTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/IpcComponentTests.cpp index 15a7c169..f469d40e 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/IpcComponentTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/IpcComponentTests.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2025 Intel Corporation +// Copyright (C) 2022-2025 Intel Corporation // SPDX-License-Identifier: MIT #include "../CommonUtilities/win/WinAPI.h" @@ -8,10 +8,12 @@ #include "JobManager.h" #include "../Interprocess/source/ViewedDataSegment.h" +#include "../Interprocess/source/OwnedDataSegment.h" #include "../Interprocess/source/DataStores.h" #include "../PresentMonAPI2/PresentMonAPI.h" +#include #include #include #include @@ -47,6 +49,7 @@ namespace IpcComponentTests .logLevel = "debug", .logFolder = logFolder_, .sampleClientMode = "IpcComponentServer", + .suppressService = true, }; return args; } @@ -113,7 +116,15 @@ namespace IpcComponentTests return 50.0 + 2.0 * static_cast(i); } - TEST_CLASS(SystemDataStoreHistoryRingInterfaceTests) + static void AssertScalarSampleMatches_(const ipc::HistoryRing& ring, size_t serial) + { + const auto& sample = ring.At(serial); + const uint64_t expectedTs = kBaseTs + static_cast(serial); + Assert::AreEqual(expectedTs, sample.timestamp); + Assert::AreEqual(ExpectedScalarValue_(expectedTs), sample.value, 1e-9); + } + + TEST_CLASS(HistoryRingStoreBasicAccessTests) { TestFixture fixture_; @@ -367,4 +378,148 @@ namespace IpcComponentTests Assert::AreEqual(expectedSum, sum, 1e-9); } }; + + TEST_CLASS(HistoryRingStoreWrappingTests) + { + TestFixture fixture_; + + public: + TEST_METHOD_INITIALIZE(Setup) + { + fixture_.Setup(); + } + + TEST_METHOD_CLEANUP(Cleanup) + { + fixture_.Cleanup(); + } + + TEST_METHOD(RingWrapNoMissingFrames) + { + constexpr size_t ringCapacity = 16; + constexpr size_t samplesPerPush = 10; + auto server = fixture_.LaunchClient({ + "--ipc-system-ring-capacity", std::to_string(ringCapacity), + "--ipc-system-samples-per-push", std::to_string(samplesPerPush), + }); + std::this_thread::sleep_for(25ms); + + ipc::ViewedDataSegment view{ kSystemSegName }; + const auto& store = view.GetStore(); + const auto& ring = store.telemetryData.FindRing(kScalarMetric).at(0); + + size_t lastProcessed = 0; + size_t totalPushed = samplesPerPush; + + const auto consumeRange = [&](const std::pair& range) { + const size_t start = (std::max)(lastProcessed, range.first); + for (size_t serial = start; serial < range.second; ++serial) { + AssertScalarSampleMatches_(ring, serial); + } + lastProcessed = range.second; + }; + + const auto range1 = ring.GetSerialRange(); + Logger::WriteMessage(std::format("wrap-no-miss: range1 [{}, {})\n", + range1.first, range1.second).c_str()); + consumeRange(range1); + Assert::AreEqual(0, range1.first); + Assert::AreEqual(samplesPerPush, range1.second); + + Assert::AreEqual("push-more-ok"s, server.Command("push-more")); + totalPushed += samplesPerPush; + + const auto range2 = ring.GetSerialRange(); + Logger::WriteMessage(std::format("wrap-no-miss: range2 [{}, {}), lastProcessed={}\n", + range2.first, range2.second, lastProcessed).c_str()); + Assert::IsTrue(range2.first > 0); + Assert::IsTrue(range2.first <= lastProcessed); + consumeRange(range2); + + Logger::WriteMessage(std::format("wrap-no-miss: processed={}, newest-ts={}\n", + lastProcessed, ring.Newest().timestamp).c_str()); + Assert::AreEqual(totalPushed, lastProcessed); + const auto& newest = ring.Newest(); + Assert::AreEqual(kBaseTs + static_cast(totalPushed - 1), + newest.timestamp); + } + + TEST_METHOD(RingWrapMissingFrames) + { + constexpr size_t ringCapacity = 16; + constexpr size_t samplesPerPush = 10; + auto server = fixture_.LaunchClient({ + "--ipc-system-ring-capacity", std::to_string(ringCapacity), + "--ipc-system-samples-per-push", std::to_string(samplesPerPush), + }); + std::this_thread::sleep_for(25ms); + + ipc::ViewedDataSegment view{ kSystemSegName }; + const auto& store = view.GetStore(); + const auto& ring = store.telemetryData.FindRing(kScalarMetric).at(0); + + Assert::AreEqual("push-more-ok"s, server.Command("push-more")); + const size_t totalPushed = samplesPerPush * 2; + + const auto range = ring.GetSerialRange(); + Logger::WriteMessage(std::format("wrap-miss: range [{}, {}), totalPushed={}\n", + range.first, range.second, totalPushed).c_str()); + Assert::AreEqual(totalPushed, range.second); + Assert::IsTrue(range.first > 0); + + const size_t missed = range.first; + Logger::WriteMessage(std::format("wrap-miss: missed={} samples\n", missed).c_str()); + Assert::IsTrue(missed > 0); + + const auto& firstSample = ring.At(range.first); + Assert::AreEqual(kBaseTs + static_cast(range.first), + firstSample.timestamp); + + for (size_t serial = range.first; serial < range.second; ++serial) { + AssertScalarSampleMatches_(ring, serial); + } + + Logger::WriteMessage(std::format("wrap-miss: newest-ts={}\n", + ring.Newest().timestamp).c_str()); + const auto& newest = ring.Newest(); + Assert::AreEqual(kBaseTs + static_cast(totalPushed - 1), + newest.timestamp); + } + + TEST_METHOD(RingBackpressureBlocksAndResumes) + { + constexpr size_t ringSamples = 8; + ipc::DataStoreSizingInfo sizing{}; + sizing.ringSamples = ringSamples; + sizing.backpressured = true; + sizing.overrideBytes = 256 * 1024; + + const auto segName = std::format("pm_ipc_backpressure_test_seg_{}", + static_cast(::GetCurrentProcessId())); + ipc::OwnedDataSegment seg{ segName, sizing }; + auto& ring = seg.GetStore().frameData; + + ipc::FrameData sample{}; + size_t pushed = 0; + bool sawTimeout = false; + for (size_t i = 0; i < ringSamples + 4; ++i) { + if (!ring.Push(sample, 30)) { + sawTimeout = true; + break; + } + ++pushed; + } + + Assert::IsTrue(sawTimeout, L"Expected backpressure to block writes when full"); + const auto rangeBefore = ring.GetSerialRange(); + Assert::AreEqual(pushed, rangeBefore.second); + Assert::AreEqual(0, rangeBefore.first); + + ring.MarkNextRead(rangeBefore.second); + + Assert::IsTrue(ring.Push(sample, 30), L"Expected push after MarkNextRead"); + const auto rangeAfter = ring.GetSerialRange(); + Assert::AreEqual(pushed + 1, rangeAfter.second); + } + }; } diff --git a/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h b/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h index 345ec8d7..6c9518e6 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h +++ b/IntelPresentMon/PresentMonAPI2Tests/TestProcess.h @@ -33,6 +33,7 @@ struct CommonProcessArgs std::optional logVerboseModules; std::string logFolder; std::string sampleClientMode; + bool suppressService = false; }; inline std::vector SplitVerboseModulesArgs_(const std::string& raw) @@ -303,22 +304,37 @@ class CommonTestFixture } pmon::test::SetupTestLogging(GetCommonArgs().logFolder, GetCommonArgs().logLevel, GetCommonArgs().logVerboseModules); - StartService_(args, GetCommonArgs()); svcArgs_ = std::move(args); + if (!GetCommonArgs().suppressService) { + StartService_(svcArgs_, GetCommonArgs()); + serviceStarted_ = true; + } + else { + serviceStarted_ = false; + } } void Cleanup() { - StopService_(GetCommonArgs()); - ioctxRunThread_.join(); + if (serviceStarted_) { + StopService_(GetCommonArgs()); + serviceStarted_ = false; + } + if (ioctxRunThread_.joinable()) { + ioctxRunThread_.join(); + } logManager_.reset(); } void RebootService(std::optional> newArgs = {}) { auto& common = GetCommonArgs(); + if (common.suppressService) { + return; + } auto& svcArgs = newArgs ? *newArgs : svcArgs_; StopService_(common); StartService_(svcArgs, common); svcArgs_ = std::move(svcArgs); + serviceStarted_ = true; } ClientProcess LaunchClient(const std::vector& args = {}) { @@ -368,6 +384,7 @@ class CommonTestFixture static constexpr int svcPipeTimeout_ = 250; std::vector svcArgs_; JobManager jobMan_; + bool serviceStarted_ = false; std::thread ioctxRunThread_; as::io_context ioctx_; std::optional logManager_; diff --git a/IntelPresentMon/SampleClient/CliOptions.h b/IntelPresentMon/SampleClient/CliOptions.h index c38020f0..e4ae973d 100644 --- a/IntelPresentMon/SampleClient/CliOptions.h +++ b/IntelPresentMon/SampleClient/CliOptions.h @@ -3,6 +3,7 @@ #include "../CommonUtilities/log/Level.h" #include "../CommonUtilities/log/Verbose.h" #include "../CommonUtilities/ref/StaticReflection.h" +#include #include namespace clio @@ -65,6 +66,10 @@ namespace clio Option serviceEtlPath{ this, "--service-etl-path", "", "Path of the ETL file to pass to the service for playback" }; private: Group gt_{ this, "Testing", "Control testing support options" }; public: Flag testExpectError{ this, "--test-expect-error", "Indicates to test modes that fail state is being tested" }; + Option ipcSystemRingCapacity{ this, "--ipc-system-ring-capacity", 32, + "System telemetry ring capacity for the IPC component server" }; + Option ipcSystemSamplesPerPush{ this, "--ipc-system-samples-per-push", 12, + "Samples per push batch for the IPC component server" }; static constexpr const char* description = "Minimal Sample Client for Intel PresentMon service"; static constexpr const char* name = "SampleClient.exe"; diff --git a/IntelPresentMon/SampleClient/IpcComponentServer.cpp b/IntelPresentMon/SampleClient/IpcComponentServer.cpp index b8664800..433f1e1d 100644 --- a/IntelPresentMon/SampleClient/IpcComponentServer.cpp +++ b/IntelPresentMon/SampleClient/IpcComponentServer.cpp @@ -7,6 +7,7 @@ #include "../CommonUtilities/Exception.h" #include "../CommonUtilities/log/Log.h" #include "../PresentMonAPI2/PresentMonAPI.h" +#include "CliOptions.h" #include #include @@ -28,19 +29,22 @@ static constexpr const char* kSystemSegName = "pm_ipc_system_store_test_seg"; // The test goal is ring push/read plumbing, not capability validation. static constexpr PM_METRIC kScalarMetric = PM_METRIC_CPU_FREQUENCY; static constexpr PM_METRIC kArrayMetric = PM_METRIC_CPU_UTILIZATION; -static constexpr size_t kSystemRingCapacity = 32; +static constexpr size_t kDefaultSystemRingCapacity = 32; +static constexpr size_t kDefaultSamplesPerPush = 12; +static constexpr uint64_t kBaseTimestamp = 10'000ull; static constexpr size_t kSystemSegmentBytes = 512 * 1024; -static void BuildRings_(ipc::SystemDataStore& store) +static void BuildRings_(ipc::SystemDataStore& store, size_t ringCapacity) { // Scalar metric - store.telemetryData.AddRing(kScalarMetric, kSystemRingCapacity, 1, PM_DATA_TYPE_DOUBLE); + store.telemetryData.AddRing(kScalarMetric, ringCapacity, 1, PM_DATA_TYPE_DOUBLE); // Array metric with 2 elements - store.telemetryData.AddRing(kArrayMetric, kSystemRingCapacity, 2, PM_DATA_TYPE_DOUBLE); + store.telemetryData.AddRing(kArrayMetric, ringCapacity, 2, PM_DATA_TYPE_DOUBLE); } -static void PushDeterministicSamples_(ipc::SystemDataStore& store) +static void PushDeterministicSamples_(ipc::SystemDataStore& store, size_t sampleCount, + size_t& nextIndex) { auto& scalar = store.telemetryData.FindRing(kScalarMetric); @@ -55,28 +59,32 @@ static void PushDeterministicSamples_(ipc::SystemDataStore& store) auto& arr0 = array.at(0); auto& arr1 = array.at(1); - constexpr size_t sampleCount = 12; - for (size_t i = 0; i < sampleCount; ++i) { - const uint64_t ts = 10'000ull + static_cast(i); + const size_t sampleIndex = nextIndex + i; + const uint64_t ts = kBaseTimestamp + static_cast(sampleIndex); // Scalar sequence - const double freq = 3000.0 + 10.0 * static_cast(i); + const double freq = 3000.0 + 10.0 * static_cast(sampleIndex); scalarRing.Push(freq, ts); // Array element 0 sequence - const double util0 = 5.0 + static_cast(i); + const double util0 = 5.0 + static_cast(sampleIndex); arr0.Push(util0, ts); // Array element 1 sequence (offset so we can tell them apart) - const double util1 = 50.0 + static_cast(i) * 2.0; + const double util1 = 50.0 + static_cast(sampleIndex) * 2.0; arr1.Push(util1, ts); } + + nextIndex += sampleCount; } // Submode entry point. int IpcComponentServer() { + const auto& opt = clio::Options::Get(); + const size_t ringCapacity = *opt.ipcSystemRingCapacity; + const size_t samplesPerPush = *opt.ipcSystemSamplesPerPush; ipc::DataStoreSizingInfo sizing{}; sizing.overrideBytes = kSystemSegmentBytes; @@ -88,7 +96,7 @@ int IpcComponentServer() auto& store = seg.GetStore(); // Only build the two test rings. - BuildRings_(store); + BuildRings_(store, ringCapacity); // Ping gate to sync "server ready" with the test harness. std::string line; @@ -100,7 +108,8 @@ int IpcComponentServer() std::cout << "%%{ping-ok}%%" << std::endl; // Push a deterministic batch after ping. - PushDeterministicSamples_(store); + size_t nextIndex = 0; + PushDeterministicSamples_(store, samplesPerPush, nextIndex); // Command loop. while (std::getline(std::cin, line)) { @@ -110,7 +119,7 @@ int IpcComponentServer() return 0; } else if (line == "%push-more") { - PushDeterministicSamples_(store); + PushDeterministicSamples_(store, samplesPerPush, nextIndex); std::cout << "%%{push-more-ok}%%" << std::endl; } else { From 13e5d89d6fe60ea67715a2a67de2b4f02c7d4eb4 Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 29 Dec 2025 17:17:37 +0900 Subject: [PATCH 085/205] wrapping tests w/ middleware realtime and playback --- .../InterimBroadcasterTests.cpp | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index cfa91a92..abcdff83 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -685,6 +685,96 @@ namespace InterimBroadcasterTests } }; + TEST_CLASS(FrameStoreRealtimeWrapTests) + { + TestFixture fixture_; + public: + TEST_METHOD_INITIALIZE(Setup) + { + fixture_.Setup({ + "--frame-ring-samples"s, "16"s, + }); + } + TEST_METHOD_CLEANUP(Cleanup) + { + fixture_.Cleanup(); + } + TEST_METHOD(WrapNoMissingFrames) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + + auto pres = fixture_.LaunchPresenter(); + client.DispatchSync(svc::acts::SetEtwFlushPeriod::Params{ .etwFlushPeriodMs = 8 }); + std::this_thread::sleep_for(1ms); + client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pres.GetId() }); + + pComms->OpenFrameDataStore(pres.GetId()); + auto& ring = pComms->GetFrameDataStore(pres.GetId()).frameData; + + std::this_thread::sleep_for(200ms); + + size_t lastProcessed = 0; + bool missed = false; + bool sawWrap = false; + bool hasTimestamp = false; + uint64_t lastTimestamp = 0; + + for (size_t i = 0; i < 60; ++i) { + std::this_thread::sleep_for(25ms); + const auto range = ring.GetSerialRange(); + Logger::WriteMessage(std::format( + "rt-wrap-no-miss: range [{}, {}), lastProcessed={}\n", + range.first, range.second, lastProcessed).c_str()); + if (range.first > 0) { + sawWrap = true; + } + if (range.first > lastProcessed) { + missed = true; + } + const size_t start = (std::max)(lastProcessed, range.first); + for (size_t s = start; s < range.second; ++s) { + const auto& frame = ring.At(s); + const uint64_t stamp = frame.presentStartTime + frame.timeInPresent; + if (hasTimestamp) { + Assert::IsTrue(stamp >= lastTimestamp); + } + lastTimestamp = stamp; + hasTimestamp = true; + } + lastProcessed = range.second; + } + + Assert::IsTrue(sawWrap, L"Expected ring to wrap"); + Assert::IsFalse(missed, L"Expected no missing frames with frequent reads"); + Assert::IsTrue(lastProcessed > 0); + } + TEST_METHOD(WrapMissingFrames) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + + auto pres = fixture_.LaunchPresenter(); + client.DispatchSync(svc::acts::SetEtwFlushPeriod::Params{ .etwFlushPeriodMs = 8 }); + std::this_thread::sleep_for(1ms); + client.DispatchSync(svc::acts::StartTracking::Params{ .targetPid = pres.GetId() }); + + pComms->OpenFrameDataStore(pres.GetId()); + auto& ring = pComms->GetFrameDataStore(pres.GetId()).frameData; + + auto range = ring.GetSerialRange(); + for (size_t i = 0; i < 20 && range.first == 0; ++i) { + std::this_thread::sleep_for(100ms); + range = ring.GetSerialRange(); + } + Logger::WriteMessage(std::format( + "rt-wrap-miss: range [{}, {})\n", range.first, range.second).c_str()); + + Assert::IsTrue(range.first > 0, L"Expected missing frames after delay"); + Assert::IsTrue(range.second > range.first); + } + }; + TEST_CLASS(FrameStorePacedPlaybackTests) { TestFixture fixture_; @@ -874,6 +964,67 @@ namespace InterimBroadcasterTests } }; + TEST_CLASS(FrameStorePlaybackBackpressureWrapTests) + { + TestFixture fixture_; + public: + TEST_METHOD_INITIALIZE(Setup) + { + fixture_.Setup({ + "--etl-test-file"s, R"(..\..\Tests\AuxData\Data\P01TimeSpyDemoFS2080.etl)"s, + "--disable-legacy-backpressure"s, + "--frame-ring-samples"s, "32"s, + }); + } + TEST_METHOD_CLEANUP(Cleanup) + { + fixture_.Cleanup(); + } + TEST_METHOD(BackpressurePreventsMissingFrames) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + + client.DispatchSync(svc::acts::SetEtwFlushPeriod::Params{ .etwFlushPeriodMs = 8 }); + std::this_thread::sleep_for(1ms); + + const uint32_t pid = 19736; + client.DispatchSync(svc::acts::StartTracking::Params{ + .targetPid = pid, .isPlayback = true, .isBackpressured = true }); + + pComms->OpenFrameDataStore(pid); + auto& ring = pComms->GetFrameDataStore(pid).frameData; + + size_t lastProcessed = 0; + bool missed = false; + bool sawWrap = false; + + for (size_t i = 0; i < 10; ++i) { + std::this_thread::sleep_for(300ms); + const auto range = ring.GetSerialRange(); + Logger::WriteMessage(std::format( + "pb-wrap-backpressure: range [{}, {}), lastProcessed={}\n", + range.first, range.second, lastProcessed).c_str()); + if (range.first > 0) { + sawWrap = true; + } + if (range.first > lastProcessed) { + missed = true; + } + const size_t start = (std::max)(lastProcessed, range.first); + for (size_t s = start; s < range.second; ++s) { + (void)ring.At(s); + } + ring.MarkNextRead(range.second); + lastProcessed = range.second; + } + + Assert::IsTrue(sawWrap, L"Expected ring to wrap during playback"); + Assert::IsFalse(missed, L"Expected backpressure to prevent missing frames"); + Assert::IsTrue(lastProcessed > 0); + } + }; + TEST_CLASS(FrameStoreBackpressuredPlayback3DMTests) { TestFixture fixture_; From dd6361b3c07ff41c6a4ba9877382386532253fef Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 30 Dec 2025 07:59:23 +0900 Subject: [PATCH 086/205] static constexpr enum bridge in Meta.h --- IntelPresentMon/CommonUtilities/Meta.h | 47 +++++++++++- .../Interprocess/source/DataStores.cpp | 20 ++++- .../source/IntrospectionCapsLookup.h | 11 ++- .../source/MetricCapabilitiesShim.cpp | 76 +++++-------------- 4 files changed, 91 insertions(+), 63 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/Meta.h b/IntelPresentMon/CommonUtilities/Meta.h index 4ea9cea5..13ff7742 100644 --- a/IntelPresentMon/CommonUtilities/Meta.h +++ b/IntelPresentMon/CommonUtilities/Meta.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include #include @@ -64,4 +64,47 @@ namespace pmon::util } template struct FunctionPtrTraits : impl::FunctionPtrTraitsImpl_> {}; -} \ No newline at end of file + + namespace impl { + template MaxValue, + std::underlying_type_t Value, typename Functor> + constexpr void ForEachEnumValueRecursive_(Functor& func) + { + if constexpr (Value < MaxValue) { + func.template operator()(Value)>(); + ForEachEnumValueRecursive_(func); + } + } + + template MaxValue, + std::underlying_type_t Value, typename Functor, typename ReturnT> + constexpr ReturnT DispatchEnumValueRecursive_(std::underlying_type_t target, + Functor& func, ReturnT defaultValue) + { + if constexpr (Value < MaxValue) { + if (target == Value) { + return func.template operator()(Value)>(); + } + return DispatchEnumValueRecursive_( + target, func, defaultValue); + } + return defaultValue; + } + } + + template MaxValue, typename Functor> + constexpr void ForEachEnumValue(Functor&& func) + { + auto& fn = func; + impl::ForEachEnumValueRecursive_(fn); + } + + template MaxValue, typename Functor, typename ReturnT> + constexpr ReturnT DispatchEnumValue(Enum value, Functor&& func, ReturnT defaultValue) + { + auto& fn = func; + return impl::DispatchEnumValueRecursive_( + static_cast>(value), + fn, defaultValue); + } +} diff --git a/IntelPresentMon/Interprocess/source/DataStores.cpp b/IntelPresentMon/Interprocess/source/DataStores.cpp index e660775d..0e0fa616 100644 --- a/IntelPresentMon/Interprocess/source/DataStores.cpp +++ b/IntelPresentMon/Interprocess/source/DataStores.cpp @@ -2,6 +2,8 @@ #include "MetricCapabilities.h" #include "IntrospectionTransfer.h" #include "IntrospectionDataTypeMapping.h" +#include "IntrospectionCapsLookup.h" +#include "../../CommonUtilities/Meta.h" #include "../../CommonUtilities/Memory.h" #include "../../CommonUtilities/log/Verbose.h" #include @@ -47,14 +49,28 @@ namespace pmon::ipc return safeValueBytes + pad + sizeof(uint64_t); } + using MetricEnum_ = PM_METRIC; + using MetricUnderlying_ = std::underlying_type_t; + constexpr MetricUnderlying_ kMaxMetricUnderlying_ = 256; + + struct MiddlewareDerivedLookup_ + { + template + constexpr bool operator()() const + { + using Lookup = intro::IntrospectionCapsLookup; + return intro::IsMiddlewareDerivedMetric; + } + }; + bool ShouldAllocateTelemetryRing_(PM_METRIC metricId, const intro::IntrospectionMetric& metric) { if (metric.GetMetricType() == PM_METRIC_TYPE_STATIC) { return false; } - if (metricId == PM_METRIC_GPU_FAN_SPEED_PERCENT || - metricId == PM_METRIC_GPU_MEM_UTILIZATION) { + if (util::DispatchEnumValue( + metricId, MiddlewareDerivedLookup_{}, false)) { return false; } return true; diff --git a/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h b/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h index f282db01..07892961 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h +++ b/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "IntrospectionMetadata.h" #include #include "../../ControlLib/PresentMonPowerTelemetry.h" @@ -34,11 +34,15 @@ namespace pmon::ipc::intro GpuTelemetryCapBits::fan_speed_2, GpuTelemetryCapBits::fan_speed_3, GpuTelemetryCapBits::fan_speed_4, }; }; template<> struct IntrospectionCapsLookup { + using MiddlewareDerived = std::true_type; static constexpr auto gpuCapBitArray = std::array{ GpuTelemetryCapBits::max_fan_speed_0, GpuTelemetryCapBits::max_fan_speed_1, GpuTelemetryCapBits::max_fan_speed_2, GpuTelemetryCapBits::max_fan_speed_3, GpuTelemetryCapBits::max_fan_speed_4, }; }; template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_mem_used; }; - template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_mem_used; }; + template<> struct IntrospectionCapsLookup { + using MiddlewareDerived = std::true_type; + static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_mem_used; + }; template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_mem_write_bandwidth; }; template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_mem_read_bandwidth; }; template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_power_limited; }; @@ -78,4 +82,5 @@ namespace pmon::ipc::intro template concept IsGpuDeviceStaticMetric = requires { typename T::GpuDeviceStatic; }; template concept IsCpuMetric = requires { T::cpuCapBit; }; template concept IsManualDisableMetric = requires { typename T::ManualDisable; }; -} \ No newline at end of file + template concept IsMiddlewareDerivedMetric = requires { typename T::MiddlewareDerived; }; +} diff --git a/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp b/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp index d5b6157c..403e86b4 100644 --- a/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp +++ b/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp @@ -1,8 +1,6 @@ -#include "MetricCapabilitiesShim.h" +#include "MetricCapabilitiesShim.h" #include "IntrospectionCapsLookup.h" -#include "../../CommonUtilities/third/reflect.hpp" - -#include +#include "../../CommonUtilities/Meta.h" namespace pmon::ipc::intro { @@ -14,15 +12,6 @@ namespace pmon::ipc::intro // Probe underlying values in [0, MaxMetricUnderlying) constexpr MetricUnderlying MaxMetricUnderlying = 256; - // Is this underlying value actually one of the enum's declared enumerators? - template - consteval bool IsValidMetricEnum() - { - constexpr auto e = static_cast(Value); - constexpr auto name = reflect::enum_name(e); - return !name.empty(); - } - // xxxCapBits is std::bitset template bool HasCap(const BitsType& bits, Index index) @@ -31,17 +20,15 @@ namespace pmon::ipc::intro } // GPU per-metric accumulation (only instantiated for valid enum values) - template - void AccumulateGpuCapability(MetricCapabilities& caps, - const GpuTelemetryBitset& bits) + template + void AccumulateGpuCapability(MetricCapabilities& caps, const GpuTelemetryBitset& bits) { - constexpr auto metricEnum = static_cast(Value); - using Lookup = IntrospectionCapsLookup; + using Lookup = IntrospectionCapsLookup; // Single GPU capability bit -> metric present if bit set if constexpr (IsGpuDeviceMetric) { if (HasCap(bits, Lookup::gpuCapBit)) { - caps.Set(metricEnum, 1); + caps.Set(Metric, 1); } } @@ -54,56 +41,27 @@ namespace pmon::ipc::intro } } if (count > 0) { - caps.Set(metricEnum, count); + caps.Set(Metric, count); } } // Static GPU metrics: name/vendor/etc. if constexpr (IsGpuDeviceStaticMetric) { - caps.Set(metricEnum, 1); + caps.Set(Metric, 1); } } // CPU per-metric accumulation (only instantiated for valid enum values) - template - void AccumulateCpuCapability(MetricCapabilities& caps, - const CpuTelemetryBitset& bits) + template + void AccumulateCpuCapability(MetricCapabilities& caps, const CpuTelemetryBitset& bits) { - constexpr auto metricEnum = static_cast(Value); - using Lookup = IntrospectionCapsLookup; + using Lookup = IntrospectionCapsLookup; // CPU metrics gated by a capability bit if constexpr (IsCpuMetric && !IsManualDisableMetric) { if (HasCap(bits, Lookup::cpuCapBit)) { - caps.Set(metricEnum, 1); - } - } - } - - // Compile-time recursion over underlying values [0, MaxMetricUnderlying) - // GPU: only call AccumulateGpuCapability when Value is a real enumerator - template - void ConvertGpuBitsRecursive(MetricCapabilities& caps, - const GpuTelemetryBitset& bits) - { - if constexpr (Value < MaxMetricUnderlying) { - if constexpr (IsValidMetricEnum()) { - AccumulateGpuCapability(caps, bits); - } - ConvertGpuBitsRecursive(caps, bits); - } - } - - // CPU: same pattern - template - void ConvertCpuBitsRecursive(MetricCapabilities& caps, - const CpuTelemetryBitset& bits) - { - if constexpr (Value < MaxMetricUnderlying) { - if constexpr (IsValidMetricEnum()) { - AccumulateCpuCapability(caps, bits); + caps.Set(Metric, 1); } - ConvertCpuBitsRecursive(caps, bits); } } } // namespace detail @@ -111,14 +69,20 @@ namespace pmon::ipc::intro MetricCapabilities ConvertBitset(const GpuTelemetryBitset& bits) { MetricCapabilities caps; - detail::ConvertGpuBitsRecursive<0>(caps, bits); + util::ForEachEnumValue( + [&]() { + detail::AccumulateGpuCapability(caps, bits); + }); return caps; } MetricCapabilities ConvertBitset(const CpuTelemetryBitset& bits) { MetricCapabilities caps; - detail::ConvertCpuBitsRecursive<0>(caps, bits); + util::ForEachEnumValue( + [&]() { + detail::AccumulateCpuCapability(caps, bits); + }); return caps; } } From b0b2ada529cb1777ba68ad6c360f77d1b07af9b1 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Mon, 29 Dec 2025 14:59:28 -0800 Subject: [PATCH 087/205] Various fixes for ETL ULT test failures. --- .../CommonUtilities/mc/MetricsCalculator.cpp | 30 ++++++++++++------- .../CommonUtilities/mc/SwapChainState.cpp | 12 ++++---- PresentMon/CsvOutput.cpp | 7 ++++- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 73268582..313c88b4 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -260,6 +260,11 @@ namespace pmon::util::metrics double simElapsed = qpc.DeltaUnsignedMilliSeconds(chain.lastDisplayedSimStartTime, currentSimStart); double displayElapsed = qpc.DeltaUnsignedMilliSeconds(chain.lastDisplayedAppScreenTime, screenTime); + if(simElapsed == 0.0 || displayElapsed == 0.0) { + out.msAnimationError = std::nullopt; + return; + } + out.msAnimationError = simElapsed - displayElapsed; } @@ -282,18 +287,13 @@ namespace pmon::util::metrics if (isFirstProviderSimTime) { // Seed only: no animation time yet. UpdateAfterPresent will flip us // into AppProvider/PCL and latch firstAppSimStartTime. - // TODO: Once ETL tests are passing, change to std::nullopt. - // out.msAnimationTime = std::nullopt; - out.msAnimationTime = 0.0; + out.msAnimationTime = std::nullopt; return; } uint64_t currentSimStart = CalculateAnimationErrorSimStartTime(chain, present, chain.animationErrorSource); if (currentSimStart == 0) { - // TODO: Once ETL tests are passing, change to std::nullopt. out.msAnimationTime = std::nullopt; - //out.msAnimationTime = 0.0; - return; } @@ -553,7 +553,8 @@ namespace pmon::util::metrics else if (present.appSimStartTime != 0) { currentSimStartTime = present.appSimStartTime; } - if (chain.lastSimStartTime != 0 && currentSimStartTime != 0) { + if (chain.lastSimStartTime != 0 && currentSimStartTime != 0 && + currentSimStartTime > chain.lastSimStartTime) { return qpc.DeltaUnsignedMilliSeconds( chain.lastSimStartTime, currentSimStartTime); @@ -692,8 +693,16 @@ namespace pmon::util::metrics const uint64_t screenTime = 0; const uint64_t nextScreenTime = 0; const bool isDisplayed = false; - const bool isAppFrame = true; - const FrameType frameType = FrameType::NotSet; + + // Legacy-equivalent attribution: compute displayIndex/appIndex and derive isAppFrame. + const auto indexing = DisplayIndexing::Calculate(present, nextDisplayed); + const size_t displayIndex = indexing.startIndex; // Case 1 => 0 + const size_t appIndex = indexing.appIndex; + + const bool isAppFrame = (displayIndex == appIndex); + const FrameType frameType = (displayCount > 0) + ? present.getDisplayedFrameType(displayIndex) + : FrameType::NotSet; auto metrics = ComputeFrameMetrics( qpc, @@ -708,7 +717,7 @@ namespace pmon::util::metrics ApplyStateDeltas(chainState, metrics.stateDeltas); results.push_back(std::move(metrics)); - + chainState.UpdateAfterPresent(present); return results; @@ -767,6 +776,7 @@ namespace pmon::util::metrics return results; } + void CalculateBasePresentMetrics( const QpcConverter& qpc, const FrameData& present, diff --git a/IntelPresentMon/CommonUtilities/mc/SwapChainState.cpp b/IntelPresentMon/CommonUtilities/mc/SwapChainState.cpp index e87e6c74..f4773f80 100644 --- a/IntelPresentMon/CommonUtilities/mc/SwapChainState.cpp +++ b/IntelPresentMon/CommonUtilities/mc/SwapChainState.cpp @@ -20,17 +20,15 @@ namespace pmon::util::metrics { if (lastIsAppFrm) { const uint64_t lastScreenTime = present.getDisplayedScreenTime(lastIdx); - // This block is the port of the big "if (p->Displayed.back().first ...)" from UpdateChain if (animationErrorSource == AnimationErrorSource::AppProvider) { - if (present.getAppSimStartTime() != 0) { - lastDisplayedSimStartTime = present.getAppSimStartTime(); - if (firstAppSimStartTime == 0) { - firstAppSimStartTime = present.getAppSimStartTime(); - } - lastDisplayedAppScreenTime = lastScreenTime; + lastDisplayedSimStartTime = present.getAppSimStartTime(); + if (firstAppSimStartTime == 0) { + firstAppSimStartTime = present.getAppSimStartTime(); } + lastDisplayedAppScreenTime = lastScreenTime; } else if (animationErrorSource == AnimationErrorSource::PCLatency) { + // In the case of PCLatency only set values if PCL sim start time is non-zero if (present.getPclSimStartTime() != 0) { lastDisplayedSimStartTime = present.getPclSimStartTime(); if (firstAppSimStartTime == 0) { diff --git a/PresentMon/CsvOutput.cpp b/PresentMon/CsvOutput.cpp index 023f60ec..fef14c65 100644 --- a/PresentMon/CsvOutput.cpp +++ b/PresentMon/CsvOutput.cpp @@ -1210,7 +1210,12 @@ void WriteCsvRow( fwprintf(fp, L",%.4lf", metrics.msAnimationTime.value()); } else { - fwprintf(fp, L",NA"); + if (metrics.msDisplayedTime == 0.0 || (metrics.frameType != FrameType::Application && metrics.frameType != FrameType::NotSet)) { + fwprintf(fp, L",NA"); + } + else { + fwprintf(fp, L",%.4lf", 0.0); + } } if (metrics.msFlipDelay.has_value()) { fwprintf(fp, L",%.4lf", metrics.msFlipDelay.value()); From ca0c0c0bae65c47fa9ba076932bd6fdde2beddae Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Mon, 29 Dec 2025 15:00:03 -0800 Subject: [PATCH 088/205] Additional tests added based on ULT ETL failures --- IntelPresentMon/UnitTests/MetricsCore.cpp | 59 +++++++++++++++++------ 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index f198e15d..ae7088dd 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -1239,10 +1239,8 @@ namespace MetricsCoreTests // Should process 1 Assert::AreEqual(size_t(1), metrics.size()); // Chain update postponed until nextDisplayed - Assert::IsFalse(chain.lastPresent.has_value()); - Assert::IsFalse(chain.lastAppPresent.has_value()); - Assert::AreEqual(uint64_t(0), chain.lastDisplayedScreenTime); - Assert::AreEqual(uint64_t(0), chain.lastDisplayedFlipDelay); + const auto& m = metrics[0].metrics; + Assert::IsTrue(FrameType::Intel_XEFG == m.frameType, L"FrameType should be Intel_XEFG"); } TEST_METHOD(ComputeMetricsForPresent_AmdAfmf_WithNext_AppProcessedAndUpdatesChain) { @@ -5709,10 +5707,7 @@ namespace MetricsCoreTests frame3.displayed.push_back({ FrameType::Application, 3000 }); auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); - Assert::IsTrue(results[0].metrics.msAnimationError.has_value()); - double simElapsed = qpc.DeltaUnsignedMilliSeconds(100, 150); // 0.005 ms - Assert::AreEqual(simElapsed, results[0].metrics.msAnimationError.value(), 0.0001, - L"msAnimationError should equal simElapsed when display delta is zero"); + Assert::IsFalse(results[0].metrics.msAnimationError.has_value()); } // Section C: Animation Error – PCLatency Source @@ -5966,6 +5961,42 @@ namespace MetricsCoreTests Assert::AreEqual(expected, results[0].metrics.msAnimationError.value(), 0.0001); } + TEST_METHOD(AnimationError_CpuStart_Frame2DisplayIsGreaterThanFrame1Display) + { + // Scenario: CpuStart source, CPU-derived sim times + // Frame 2 has a display time earlier than Frame 1 + // Expected: NA reported for animation error + // Note: Need baseline frame to establish lastDisplayedSimStartTime + // and lastDisplayedScreenTime + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState state{}; + state.animationErrorSource = AnimationErrorSource::CpuStart; + state.lastDisplayedScreenTime = 55454524262; + state.lastDisplayedSimStartTime = 55454168764; + state.lastDisplayedAppScreenTime = 55454524262; + FrameData frame3{}; + frame3.presentStartTime = 55454299820; + frame3.timeInPresent = 24537; + state.lastPresent = frame3; + + FrameData frame1{}; + frame1.presentStartTime = 55454457377; + frame1.timeInPresent = 2411; + frame1.finalState = PresentResult::Presented; + frame1.displayed.push_back({ FrameType::Application, 55454512384 }); + + FrameData frame2{}; + frame2.presentStartTime = 55454612236; + frame2.timeInPresent = 3056; + frame2.finalState = PresentResult::Presented; + frame2.displayed.push_back({ FrameType::Application, 55454615330 }); + + ComputeMetricsForPresent(qpc, frame1, nullptr, state); + auto results = ComputeMetricsForPresent(qpc, frame1, &frame2, state); + + Assert::IsFalse(results[0].metrics.msAnimationError.has_value()); + } + TEST_METHOD(AnimationError_CpuStart_TransitionToAppProvider_Nullopt) { // Scenario: Source switches from CpuStart to AppProvider mid-stream @@ -6063,8 +6094,8 @@ namespace MetricsCoreTests TEST_METHOD(AnimationError_BackwardsScreenTime_ErrorStillComputed) { - // Scenario: Screen time goes backward (unusual but allowed) - // Expected: Error still computed (reflects raw cadence mismatch) + // Scenario: Screen time goes backward + // Expected: nullopt for animation error QpcConverter qpc(10'000'000, 0); SwapChainCoreState state{}; state.animationErrorSource = AnimationErrorSource::AppProvider; @@ -6093,12 +6124,8 @@ namespace MetricsCoreTests frame3.displayed.push_back({ FrameType::Application, 3000 }); auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); - Assert::IsTrue(results[0].metrics.msAnimationError.has_value(), - L"Error should still be computed even with backwards screen time"); - double simElapsed = qpc.DeltaUnsignedMilliSeconds(100, 150); // 0.005 ms - double displayElapsed = qpc.DeltaUnsignedMilliSeconds(1100, 1050); // -0.005 ms (negative!) - double expected = simElapsed - displayElapsed; // 0.010 ms - Assert::AreEqual(expected, results[0].metrics.msAnimationError.value(), 0.0001); + Assert::IsFalse(results[0].metrics.msAnimationError.has_value(), + L"Error should be nullopt with backwards screen time"); } TEST_METHOD(AnimationError_VeryLargeCadenceMismatch_LargeError) From d8bfd75fbb2798db9d1842db523d7460c3457f53 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Mon, 29 Dec 2025 15:00:36 -0800 Subject: [PATCH 089/205] Fixing bogus animation error of tests --- Tests/Gold/test_case_4.csv | 2 +- Tests/Gold/test_case_4_v2.csv | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Gold/test_case_4.csv b/Tests/Gold/test_case_4.csv index 6068a997..382da396 100644 --- a/Tests/Gold/test_case_4.csv +++ b/Tests/Gold/test_case_4.csv @@ -91,7 +91,7 @@ dwm.exe,10376,0x1BF098B1ED0,DXGI,1,0,0,Hardware: Legacy Flip,Application,5545385 dwm.exe,10376,0x1BF098B1ED0,DXGI,1,0,0,Hardware: Legacy Flip,Application,55454017095,NA,16.06800000000000,16.66520000000000,0.10630000000000,1.91440000000000,16.0088,NA,55453857940,16.0218,15.9155,0.1063,0.0000,17.8749,2.6085,15.2664,0.0000,0.4262,2237.2199,NA,NA,NA,NA dwm.exe,10376,0x1BF098B1ED0,DXGI,1,0,0,Hardware: Legacy Flip,Application,55454220315,NA,20.32200000000000,16.67040000000000,0.07480000000000,0.45110000000000,12.3572,NA,55454018158,20.2905,20.2157,0.0748,0.0000,20.6730,1.8226,18.8504,0.0000,-0.6486,2253.2417,NA,NA,NA,NA Presenter.exe,5236,0x285B4C0D1B0,DXGI,0,0,0,Composed: Flip,Application,55454299820,NA,15.65140000000000,18.03750000000000,2.45370000000000,-11.33040000000000,22.4442,NA,55454168764,15.5593,13.1056,2.4537,0.8807,0.8945,0.8945,0.0000,0.0000,NA,2268.3023,NA,NA,NA,NA -Presenter.exe,5236,0x285B4C0D1B0,DXGI,0,0,1,Hardware: Independent Flip,Application,55454457377,NA,15.75570000000000,NA,0.24110000000000,5.50070000000000,5.5007,NA,55454324357,13.5431,13.3020,0.2411,0.1296,18.6731,0.9864,17.6867,0.0000,-1844674407370938.5000,2283.8616,NA,NA,NA,NA +Presenter.exe,5236,0x285B4C0D1B0,DXGI,0,0,1,Hardware: Independent Flip,Application,55454457377,NA,15.75570000000000,NA,0.24110000000000,5.50070000000000,5.5007,NA,55454324357,13.5431,13.3020,0.2411,0.1296,18.6731,0.9864,17.6867,0.0000,NA,2283.8616,NA,NA,NA,NA Presenter.exe,5236,0x285B4C0D1B0,DXGI,0,0,1,Hardware: Independent Flip,Application,55454612236,NA,15.48590000000000,10.29460000000000,0.30560000000000,-7.97160000000000,0.3094,NA,55454459788,15.5504,15.2448,0.3056,6.5527,0.7205,0.7205,0.0000,0.0000,3.2485,2297.4047,NA,NA,NA,NA Presenter.exe,5236,0x285B4C0D1B0,DXGI,0,0,1,Hardware: Independent Flip,Application,55454769193,NA,15.69570000000000,15.62230000000000,0.20100000000000,-13.73470000000000,0.2360,NA,55454615292,15.5911,15.3901,0.2010,0.8622,0.7932,0.7932,0.0000,0.0000,-0.0719,2312.9551,NA,NA,NA,NA Presenter.exe,5236,0x285B4C0D1B0,DXGI,0,0,1,Hardware: Independent Flip,Application,55454924961,NA,15.57680000000000,15.82180000000000,0.44410000000000,-14.07780000000000,0.4810,NA,55454771203,15.8199,15.3758,0.4441,0.4673,0.8307,0.8307,0.0000,0.0000,-0.2307,2328.5462,NA,NA,NA,NA diff --git a/Tests/Gold/test_case_4_v2.csv b/Tests/Gold/test_case_4_v2.csv index 3719e455..679972a7 100644 --- a/Tests/Gold/test_case_4_v2.csv +++ b/Tests/Gold/test_case_4_v2.csv @@ -91,7 +91,7 @@ dwm.exe,10376,0x1BF098B1ED0,DXGI,1,0,0,Hardware: Legacy Flip,Application,5545368 dwm.exe,10376,0x1BF098B1ED0,DXGI,1,0,0,Hardware: Legacy Flip,Application,55453857940,16.0218,15.9155,0.1063,0.0000,17.8749,2.6085,15.2664,0.0000,31.9243,16.6704,0.4262,2237.2199,NA,NA,NA,NA dwm.exe,10376,0x1BF098B1ED0,DXGI,1,0,0,Hardware: Legacy Flip,Application,55454018158,20.2905,20.2157,0.0748,0.0000,20.6730,1.8226,18.8504,0.0000,32.5729,18.0375,-0.6486,2253.2417,NA,NA,NA,NA Presenter.exe,5236,0x285B4C0D1B0,DXGI,0,0,0,Composed: Flip,Application,55454168764,15.5593,13.1056,2.4537,0.8807,0.8945,0.8945,0.0000,0.0000,NA,NA,NA,2268.3023,NA,NA,NA,NA -Presenter.exe,5236,0x285B4C0D1B0,DXGI,0,0,1,Hardware: Independent Flip,Application,55454324357,13.5431,13.3020,0.2411,0.1296,18.6731,0.9864,17.6867,0.0000,18.8027,10.2946,-1844674407370938.5000,2283.8616,NA,NA,NA,NA +Presenter.exe,5236,0x285B4C0D1B0,DXGI,0,0,1,Hardware: Independent Flip,Application,55454324357,13.5431,13.3020,0.2411,0.1296,18.6731,0.9864,17.6867,0.0000,18.8027,10.2946,NA,2283.8616,NA,NA,NA,NA Presenter.exe,5236,0x285B4C0D1B0,DXGI,0,0,1,Hardware: Independent Flip,Application,55454459788,15.5504,15.2448,0.3056,6.5527,0.7205,0.7205,0.0000,0.0000,15.5542,15.6223,3.2485,2297.4047,NA,NA,NA,NA Presenter.exe,5236,0x285B4C0D1B0,DXGI,0,0,1,Hardware: Independent Flip,Application,55454615292,15.5911,15.3901,0.2010,0.8622,0.7932,0.7932,0.0000,0.0000,15.6261,15.8218,-0.0719,2312.9551,NA,NA,NA,NA Presenter.exe,5236,0x285B4C0D1B0,DXGI,0,0,1,Hardware: Independent Flip,Application,55454771203,15.8199,15.3758,0.4441,0.4673,0.8307,0.8307,0.0000,0.0000,15.8568,15.4013,-0.2307,2328.5462,NA,NA,NA,NA From 2502db058ca2c04ca0af4c2d47b5fbce50c2313c Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 30 Dec 2025 09:56:56 +0900 Subject: [PATCH 090/205] testing activation isolation --- .../InterimBroadcasterTests.cpp | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index abcdff83..d161da49 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -588,6 +588,152 @@ namespace InterimBroadcasterTests } }; + TEST_CLASS(NewActivationIsolationTests) + { + TestFixture fixture_; + public: + TEST_METHOD_INITIALIZE(Setup) + { + fixture_.Setup({ "--new-telemetry-activation"s }); + } + TEST_METHOD_CLEANUP(Cleanup) + { + fixture_.Cleanup(); + } + TEST_METHOD(SystemOnlyLeavesGpuEmpty) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + + client.DispatchSync(svc::acts::SetTelemetryPeriod::Params{ .telemetrySamplePeriodMs = 350 }); + Logger::WriteMessage("SystemOnlyLeavesGpuEmpty: telemetry period set to 350ms\n"); + + const uint32_t SystemDeviceID = 65536; + const uint32_t TargetDeviceID = 1; + + client.DispatchSync(svc::acts::ReportMetricUse::Params{ { + { PM_METRIC_CPU_UTILIZATION, SystemDeviceID, 0 }, + { PM_METRIC_CPU_FREQUENCY, SystemDeviceID, 0 }, + } }); + Logger::WriteMessage("SystemOnlyLeavesGpuEmpty: reported CPU utilization/frequency usage\n"); + + auto& sys = pComms->GetSystemDataStore(); + auto& gpu = pComms->GetGpuDataStore(TargetDeviceID); + + auto& sysRing = sys.telemetryData.FindRing(PM_METRIC_CPU_UTILIZATION).at(0); + auto& sysFreqRing = sys.telemetryData.FindRing(PM_METRIC_CPU_FREQUENCY).at(0); + auto& gpuRing = gpu.telemetryData.FindRing(PM_METRIC_GPU_TEMPERATURE).at(0); + std::this_thread::sleep_for(1500ms); + const auto logRing = [](const char* label, const ipc::HistoryRing& ring) { + const auto range = ring.GetSerialRange(); + Logger::WriteMessage(std::format( + "{}: serial [{}, {}) count={}\n", + label, range.first, range.second, range.second - range.first).c_str()); + for (size_t s = range.first; s < range.second; ++s) { + const auto& sample = ring.At(s); + Logger::WriteMessage(std::format( + "{}[{}]: val={} ts={}\n", + label, s, sample.value, sample.timestamp).c_str()); + } + }; + Logger::WriteMessage(std::format( + "SystemOnlyLeavesGpuEmpty: sizes cpu_util={} cpu_freq={} gpu_temp={}\n", + sysRing.Size(), sysFreqRing.Size(), gpuRing.Size()).c_str()); + logRing("cpu_util", sysRing); + logRing("cpu_freq", sysFreqRing); + logRing("gpu_temp", gpuRing); + Assert::IsTrue(sysRing.Size() >= 3, + std::format(L"Expected cpu utilization ring to have >= 3 samples, got {}", + sysRing.Size()).c_str()); + Assert::IsTrue(sysFreqRing.Size() >= 3, + std::format(L"Expected cpu frequency ring to have >= 3 samples, got {}", + sysFreqRing.Size()).c_str()); + const auto sysRange = sysRing.GetSerialRange(); + const auto sysFreqRange = sysFreqRing.GetSerialRange(); + const auto sysSample = sysRing.At(sysRange.second - 1); + const auto sysFreqSample = sysFreqRing.At(sysFreqRange.second - 1); + Logger::WriteMessage(std::format( + "SystemOnlyLeavesGpuEmpty: cpu_util val={} ts={} cpu_freq val={} ts={}\n", + sysSample.value, sysSample.timestamp, sysFreqSample.value, sysFreqSample.timestamp).c_str()); + Assert::IsTrue(sysSample.value > 1., + std::format(L"Expected cpu utilization > 1, got {}", sysSample.value).c_str()); + Assert::IsTrue(sysSample.value < 100., + std::format(L"Expected cpu utilization < 100, got {}", sysSample.value).c_str()); + Assert::IsTrue(sysFreqSample.value > 1500., + std::format(L"Expected cpu frequency > 1500, got {}", sysFreqSample.value).c_str()); + Assert::IsTrue(sysFreqSample.value < 6000., + std::format(L"Expected cpu frequency < 6000, got {}", sysFreqSample.value).c_str()); + Assert::AreEqual(0ull, gpuRing.Size(), + std::format(L"Expected gpu temperature ring size == 0, got {}", gpuRing.Size()).c_str()); + } + TEST_METHOD(GpuOnlyLeavesSystemEmpty) + { + mid::ActionClient client{ fixture_.GetCommonArgs().ctrlPipe }; + auto pComms = ipc::MakeMiddlewareComms(client.GetShmPrefix(), client.GetShmSalt()); + + client.DispatchSync(svc::acts::SetTelemetryPeriod::Params{ .telemetrySamplePeriodMs = 350 }); + Logger::WriteMessage("GpuOnlyLeavesSystemEmpty: telemetry period set to 350ms\n"); + + const uint32_t SystemDeviceID = 65536; + const uint32_t TargetDeviceID = 1; + + client.DispatchSync(svc::acts::ReportMetricUse::Params{ { + { PM_METRIC_GPU_TEMPERATURE, TargetDeviceID, 0 }, + { PM_METRIC_GPU_POWER, TargetDeviceID, 0 }, + } }); + Logger::WriteMessage("GpuOnlyLeavesSystemEmpty: reported GPU temperature/power usage\n"); + + auto& sys = pComms->GetSystemDataStore(); + auto& gpu = pComms->GetGpuDataStore(TargetDeviceID); + + auto& gpuRing = gpu.telemetryData.FindRing(PM_METRIC_GPU_TEMPERATURE).at(0); + auto& gpuPowerRing = gpu.telemetryData.FindRing(PM_METRIC_GPU_POWER).at(0); + auto& sysRing = sys.telemetryData.FindRing(PM_METRIC_CPU_UTILIZATION).at(0); + std::this_thread::sleep_for(1500ms); + const auto logRing = [](const char* label, const ipc::HistoryRing& ring) { + const auto range = ring.GetSerialRange(); + Logger::WriteMessage(std::format( + "{}: serial [{}, {}) count={}\n", + label, range.first, range.second, range.second - range.first).c_str()); + for (size_t s = range.first; s < range.second; ++s) { + const auto& sample = ring.At(s); + Logger::WriteMessage(std::format( + "{}[{}]: val={} ts={}\n", + label, s, sample.value, sample.timestamp).c_str()); + } + }; + Logger::WriteMessage(std::format( + "GpuOnlyLeavesSystemEmpty: sizes gpu_temp={} gpu_power={} cpu_util={}\n", + gpuRing.Size(), gpuPowerRing.Size(), sysRing.Size()).c_str()); + logRing("gpu_temp", gpuRing); + logRing("gpu_power", gpuPowerRing); + logRing("cpu_util", sysRing); + Assert::IsTrue(gpuRing.Size() >= 3, + std::format(L"Expected gpu temperature ring to have >= 3 samples, got {}", + gpuRing.Size()).c_str()); + Assert::IsTrue(gpuPowerRing.Size() >= 3, + std::format(L"Expected gpu power ring to have >= 3 samples, got {}", + gpuPowerRing.Size()).c_str()); + const auto gpuRange = gpuRing.GetSerialRange(); + const auto gpuPowerRange = gpuPowerRing.GetSerialRange(); + const auto gpuSample = gpuRing.At(gpuRange.second - 1); + const auto gpuPowerSample = gpuPowerRing.At(gpuPowerRange.second - 1); + Logger::WriteMessage(std::format( + "GpuOnlyLeavesSystemEmpty: gpu_temp val={} ts={} gpu_power val={} ts={}\n", + gpuSample.value, gpuSample.timestamp, gpuPowerSample.value, gpuPowerSample.timestamp).c_str()); + Assert::IsTrue(gpuSample.value > 10., + std::format(L"Expected gpu temperature > 10, got {}", gpuSample.value).c_str()); + Assert::IsTrue(gpuSample.value < 120., + std::format(L"Expected gpu temperature < 120, got {}", gpuSample.value).c_str()); + Assert::IsTrue(gpuPowerSample.value > 1., + std::format(L"Expected gpu power > 1, got {}", gpuPowerSample.value).c_str()); + Assert::IsTrue(gpuPowerSample.value < 600., + std::format(L"Expected gpu power < 600, got {}", gpuPowerSample.value).c_str()); + Assert::AreEqual(0ull, sysRing.Size(), + std::format(L"Expected cpu utilization ring size == 0, got {}", sysRing.Size()).c_str()); + } + }; + TEST_CLASS(FrameStoreRealtimeTests) { TestFixture fixture_; From a0e6a9792dca9244a3ff5b1eabee378f0f26bd1d Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 30 Dec 2025 15:04:11 +0900 Subject: [PATCH 091/205] define system device id ipc --- .../AppCef/ipm-ui-vue/src/core/api.ts | 11 +++++---- .../AppCef/ipm-ui-vue/src/core/metric.ts | 4 +--- .../ipm-ui-vue/src/stores/introspection.ts | 9 ++++--- .../ipm-ui-vue/src/stores/preferences.ts | 11 +++++---- .../Core/source/pmon/RawFrameDataMetricList.h | 11 +++++---- .../Interprocess/source/Interprocess.cpp | 2 +- .../source/IntrospectionPopulators.cpp | 8 +++---- .../source/IntrospectionPopulators.h | 6 ++--- .../Interprocess/source/SystemDeviceId.h | 7 ++++++ .../KernelProcess/kact/Introspect.h | 8 +++++-- .../InterimBroadcasterTests.cpp | 24 ++++++------------- .../ConcreteMiddleware.cpp | 7 +++--- .../PresentMonMiddleware/FrameEventQuery.cpp | 7 +++--- .../PresentMonService/PMMainThread.cpp | 5 ++-- 14 files changed, 64 insertions(+), 56 deletions(-) create mode 100644 IntelPresentMon/Interprocess/source/SystemDeviceId.h diff --git a/IntelPresentMon/AppCef/ipm-ui-vue/src/core/api.ts b/IntelPresentMon/AppCef/ipm-ui-vue/src/core/api.ts index b0627da4..08800f58 100644 --- a/IntelPresentMon/AppCef/ipm-ui-vue/src/core/api.ts +++ b/IntelPresentMon/AppCef/ipm-ui-vue/src/core/api.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT import { type Metric } from '@/core/metric' import { type Stat } from './stat' @@ -54,11 +54,12 @@ export class Api { static async loadEnvVars(): Promise { return await this.invokeEndpointFuture('loadEnvVars', {}); } - static async introspect(): Promise<{metrics: Metric[], stats: Stat[], units: Unit[]}> { + static async introspect(): Promise<{metrics: Metric[], stats: Stat[], units: Unit[], systemDeviceId: number}> { const introData = await this.invokeEndpointFuture('Introspect', {}); - if (!Array.isArray(introData.metrics) || !Array.isArray(introData.stats) || !Array.isArray(introData.units)) { + if (!Array.isArray(introData.metrics) || !Array.isArray(introData.stats) || + !Array.isArray(introData.units) || typeof introData.systemDeviceId !== 'number') { console.log("error intro call"); - throw new Error('Bad (non-array) member type returned from introspect'); + throw new Error('Bad member type returned from introspect'); } return introData; } @@ -152,4 +153,4 @@ export class Api { static registerStalePidHandler(callback: () => void) { this.core.registerSignalHandler('stalePid', callback); } -} \ No newline at end of file +} diff --git a/IntelPresentMon/AppCef/ipm-ui-vue/src/core/metric.ts b/IntelPresentMon/AppCef/ipm-ui-vue/src/core/metric.ts index fe588c30..414a1d08 100644 --- a/IntelPresentMon/AppCef/ipm-ui-vue/src/core/metric.ts +++ b/IntelPresentMon/AppCef/ipm-ui-vue/src/core/metric.ts @@ -1,7 +1,5 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT -export const SYSTEM_DEVICE_ID = 65536; - export interface Metric { id: number, name: string, diff --git a/IntelPresentMon/AppCef/ipm-ui-vue/src/stores/introspection.ts b/IntelPresentMon/AppCef/ipm-ui-vue/src/stores/introspection.ts index fb7a1142..75b6a772 100644 --- a/IntelPresentMon/AppCef/ipm-ui-vue/src/stores/introspection.ts +++ b/IntelPresentMon/AppCef/ipm-ui-vue/src/stores/introspection.ts @@ -1,4 +1,4 @@ -import { ref, computed } from 'vue'; +import { ref, computed } from 'vue'; import { defineStore } from 'pinia'; import { Api } from '@/core/api'; import type { Metric } from '@/core/metric'; @@ -11,6 +11,7 @@ export const useIntrospectionStore = defineStore('introspection', () => { const metrics = ref([]); const stats = ref([]); const units = ref([]); + const systemDeviceId = ref(0); // === Computed === const metricOptions = computed(() => { @@ -25,11 +26,12 @@ export const useIntrospectionStore = defineStore('introspection', () => { // === Actions === async function load() { - if (metrics.value.length === 0) { + if (metrics.value.length === 0 || systemDeviceId.value === 0) { const intro = await Api.introspect(); metrics.value = intro.metrics; stats.value = intro.stats; units.value = intro.units; + systemDeviceId.value = intro.systemDeviceId; } } @@ -38,7 +40,8 @@ export const useIntrospectionStore = defineStore('introspection', () => { metrics, stats, units, + systemDeviceId, metricOptions, load }; -}); \ No newline at end of file +}); diff --git a/IntelPresentMon/AppCef/ipm-ui-vue/src/stores/preferences.ts b/IntelPresentMon/AppCef/ipm-ui-vue/src/stores/preferences.ts index a8a82a26..d6870ee4 100644 --- a/IntelPresentMon/AppCef/ipm-ui-vue/src/stores/preferences.ts +++ b/IntelPresentMon/AppCef/ipm-ui-vue/src/stores/preferences.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT import { ref, computed } from 'vue'; import { defineStore } from 'pinia'; @@ -14,7 +14,6 @@ import { useLoadoutStore } from './loadout'; import { useIntrospectionStore } from './introspection'; import { deepToRaw } from '@/core/vue-utils'; import { useNotificationsStore } from './notifications'; -import { SYSTEM_DEVICE_ID } from '@/core/metric'; export const usePreferencesStore = defineStore('preferences', () => { // === Dependent Stores === @@ -115,8 +114,10 @@ export const usePreferencesStore = defineStore('preferences', () => { } async function pushSpecification() { + await intro.load(); // we need to get a non-proxy object for the call const widgets = deepToRaw(loadout.widgets); + const systemDeviceId = intro.systemDeviceId; for (const widget of widgets) { // Filter out the widgetMetrics that do not meet the condition, modify those that do widget.metrics = widget.metrics.filter(widgetMetric => { @@ -130,8 +131,8 @@ export const usePreferencesStore = defineStore('preferences', () => { // Check whether metric is a gpu metric, then we need non-universal device id if (!metric.availableDeviceIds.includes(0)) { // if this is a system metric, deviceId needs to be set to fixed id - if (metric.availableDeviceIds.includes(SYSTEM_DEVICE_ID)) { - widgetMetric.metric.deviceId = SYSTEM_DEVICE_ID; + if (metric.availableDeviceIds.includes(systemDeviceId)) { + widgetMetric.metric.deviceId = systemDeviceId; } else { // if no specific adapter id set, assume adapter id = 1 is active @@ -185,4 +186,4 @@ export const usePreferencesStore = defineStore('preferences', () => { initPreferences, resetPreferences }; -}); \ No newline at end of file +}); diff --git a/IntelPresentMon/Core/source/pmon/RawFrameDataMetricList.h b/IntelPresentMon/Core/source/pmon/RawFrameDataMetricList.h index 5fe04d0a..62f1820f 100644 --- a/IntelPresentMon/Core/source/pmon/RawFrameDataMetricList.h +++ b/IntelPresentMon/Core/source/pmon/RawFrameDataMetricList.h @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2024 Intel Corporation +// Copyright (C) 2017-2024 Intel Corporation // SPDX-License-Identifier: MIT #pragma once // must include PresentMonAPI.h before including this file @@ -6,6 +6,7 @@ #include #include #include +#include "../../../Interprocess/source/SystemDeviceId.h" namespace p2c::pmon { @@ -91,10 +92,10 @@ namespace p2c::pmon Element{.metricId = PM_METRIC_GPU_MEM_VOLTAGE_LIMITED, .deviceId = activeDeviceId }, Element{.metricId = PM_METRIC_GPU_MEM_UTILIZATION_LIMITED, .deviceId = activeDeviceId }, - Element{.metricId = PM_METRIC_CPU_UTILIZATION, .deviceId = 65536 }, - Element{.metricId = PM_METRIC_CPU_POWER, .deviceId = 65536 }, - Element{.metricId = PM_METRIC_CPU_TEMPERATURE, .deviceId = 65536 }, - Element{.metricId = PM_METRIC_CPU_FREQUENCY, .deviceId = 65536 }, + Element{.metricId = PM_METRIC_CPU_UTILIZATION, .deviceId = ::pmon::ipc::kSystemDeviceId }, + Element{.metricId = PM_METRIC_CPU_POWER, .deviceId = ::pmon::ipc::kSystemDeviceId }, + Element{.metricId = PM_METRIC_CPU_TEMPERATURE, .deviceId = ::pmon::ipc::kSystemDeviceId }, + Element{.metricId = PM_METRIC_CPU_FREQUENCY, .deviceId = ::pmon::ipc::kSystemDeviceId }, }; if (enableTimestamp) { diff --git a/IntelPresentMon/Interprocess/source/Interprocess.cpp b/IntelPresentMon/Interprocess/source/Interprocess.cpp index d5882fca..efb3fb4b 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.cpp +++ b/IntelPresentMon/Interprocess/source/Interprocess.cpp @@ -366,7 +366,7 @@ namespace pmon::ipc std::vector ids; for (auto& p : result.first->GetDevices()) { // GPU device IDs live in the range (0, kSystemDeviceId) - if (auto id = p->GetId(); id > 0 && id < intro::kSystemDeviceId) { + if (auto id = p->GetId(); id > 0 && id < kSystemDeviceId) { ids.push_back(id); } } diff --git a/IntelPresentMon/Interprocess/source/IntrospectionPopulators.cpp b/IntelPresentMon/Interprocess/source/IntrospectionPopulators.cpp index cc8beb4d..d5d03949 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionPopulators.cpp +++ b/IntelPresentMon/Interprocess/source/IntrospectionPopulators.cpp @@ -1,4 +1,4 @@ -#include "IntrospectionHelpers.h" +#include "IntrospectionHelpers.h" #include "IntrospectionMetadata.h" #include "IntrospectionTransfer.h" #include "IntrospectionCapsLookup.h" @@ -112,9 +112,9 @@ namespace pmon::ipc::intro { // add the device auto charAlloc = pSegmentManager->get_allocator(); - root.AddDevice(ShmMakeUnique(pSegmentManager, kSystemDeviceId, + root.AddDevice(ShmMakeUnique(pSegmentManager, ::pmon::ipc::kSystemDeviceId, PM_DEVICE_TYPE_SYSTEM, vendor, ShmString{ "System", charAlloc})); - PopulateDeviceMetrics_(root, caps, kSystemDeviceId); + PopulateDeviceMetrics_(root, caps, ::pmon::ipc::kSystemDeviceId); } -} \ No newline at end of file +} diff --git a/IntelPresentMon/Interprocess/source/IntrospectionPopulators.h b/IntelPresentMon/Interprocess/source/IntrospectionPopulators.h index 40684db6..455213e8 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionPopulators.h +++ b/IntelPresentMon/Interprocess/source/IntrospectionPopulators.h @@ -1,13 +1,13 @@ -#pragma once +#pragma once #include "../../PresentMonAPI2/PresentMonAPI.h" #include "SharedMemoryTypes.h" #include "MetricCapabilities.h" +#include "SystemDeviceId.h" // TODO: forward declare the segment manager (or type erase) namespace pmon::ipc::intro { - constexpr uint32_t kSystemDeviceId = 65536; void PopulateEnums(ShmSegmentManager* pSegmentManager, struct IntrospectionRoot& root); void PopulateMetrics(ShmSegmentManager* pSegmentManager, struct IntrospectionRoot& root); void PopulateUnits(ShmSegmentManager* pSegmentManager, struct IntrospectionRoot& root); @@ -15,4 +15,4 @@ namespace pmon::ipc::intro PM_DEVICE_VENDOR vendor, const std::string& deviceName, const MetricCapabilities& caps); void PopulateCpu(ShmSegmentManager* pSegmentManager, IntrospectionRoot& root, PM_DEVICE_VENDOR vendor, const std::string& deviceName, const MetricCapabilities& caps); -} \ No newline at end of file +} diff --git a/IntelPresentMon/Interprocess/source/SystemDeviceId.h b/IntelPresentMon/Interprocess/source/SystemDeviceId.h new file mode 100644 index 00000000..7de46287 --- /dev/null +++ b/IntelPresentMon/Interprocess/source/SystemDeviceId.h @@ -0,0 +1,7 @@ +#pragma once +#include + +namespace pmon::ipc +{ + inline constexpr uint32_t kSystemDeviceId = 65536; +} diff --git a/IntelPresentMon/KernelProcess/kact/Introspect.h b/IntelPresentMon/KernelProcess/kact/Introspect.h index c18da60b..4930e76e 100644 --- a/IntelPresentMon/KernelProcess/kact/Introspect.h +++ b/IntelPresentMon/KernelProcess/kact/Introspect.h @@ -1,9 +1,10 @@ -#pragma once +#pragma once #include "../../Interprocess/source/act/ActionHelper.h" #include "KernelExecutionContext.h" #include #include "../../Core/source/kernel/Kernel.h" #include "../../PresentMonAPIWrapper/PresentMonAPIWrapper.h" +#include "../../Interprocess/source/SystemDeviceId.h" #include #include @@ -84,11 +85,13 @@ namespace ACT_NS std::vector metrics; std::vector stats; std::vector units; + uint32_t systemDeviceId; template void serialize(A& ar) { ar(CEREAL_NVP(metrics), CEREAL_NVP(stats), - CEREAL_NVP(units)); + CEREAL_NVP(units), + CEREAL_NVP(systemDeviceId)); } }; @@ -123,6 +126,7 @@ namespace ACT_NS // generate the response Response res; + res.systemDeviceId = ::pmon::ipc::kSystemDeviceId; // reserve space for the actual number of metrics res.metrics.reserve(rn::distance(intro.GetMetrics() | vi::filter(filterPred))); diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index d161da49..44887ca2 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -12,6 +12,7 @@ #include "../PresentMonMiddleware/ActionClient.h" #include "../Interprocess/source/Interprocess.h" +#include "../Interprocess/source/SystemDeviceId.h" #include "../PresentMonAPIWrapperCommon/EnumMap.h" #include "../PresentMonAPIWrapper/PresentMonAPIWrapper.h" #include "../PresentMonAPIWrapper/FixedQuery.h" @@ -163,9 +164,6 @@ namespace InterimBroadcasterTests } Assert::AreEqual(2ull, (size_t)rn::distance(sys.telemetryData.Rings())); - // system device id constant - const uint32_t SystemDeviceID = 65536; - // allow warm-up period std::this_thread::sleep_for(650ms); @@ -207,14 +205,11 @@ namespace InterimBroadcasterTests } Assert::AreEqual(2ull, (size_t)rn::distance(sys.telemetryData.Rings())); - // system device id constant - const uint32_t SystemDeviceID = 65536; - // update server with metric/device usage information // this will trigger system telemetry collection client.DispatchSync(svc::acts::ReportMetricUse::Params{ { - { PM_METRIC_CPU_UTILIZATION, SystemDeviceID, 0 }, - { PM_METRIC_CPU_FREQUENCY, SystemDeviceID, 0 }, + { PM_METRIC_CPU_UTILIZATION, ipc::kSystemDeviceId, 0 }, + { PM_METRIC_CPU_FREQUENCY, ipc::kSystemDeviceId, 0 }, } }); // allow warm-up period @@ -280,9 +275,6 @@ namespace InterimBroadcasterTests // allow a short warmup std::this_thread::sleep_for(500ms); - // system device id constant - const uint32_t SystemDeviceID = 65536; - // build the set of expected rings from the store, and cross-check against introspection Logger::WriteMessage("Store Metrics\n=============\n"); std::map storeRings; @@ -299,7 +291,7 @@ namespace InterimBroadcasterTests bool matchedDevice = false; size_t introArraySize = 0; for (auto&& di : m.GetDeviceMetricInfo()) { - if (di.GetDevice().GetId() != SystemDeviceID) { + if (di.GetDevice().GetId() != ipc::kSystemDeviceId) { // skip over non-matching devices continue; } @@ -323,7 +315,7 @@ namespace InterimBroadcasterTests std::unordered_set uses; for (auto&& [met, siz] : storeRings) { if (siz > 0) { - uses.insert({ met, SystemDeviceID, 0 }); + uses.insert({ met, ipc::kSystemDeviceId, 0 }); } } // update server with metric/device usage information @@ -608,12 +600,11 @@ namespace InterimBroadcasterTests client.DispatchSync(svc::acts::SetTelemetryPeriod::Params{ .telemetrySamplePeriodMs = 350 }); Logger::WriteMessage("SystemOnlyLeavesGpuEmpty: telemetry period set to 350ms\n"); - const uint32_t SystemDeviceID = 65536; const uint32_t TargetDeviceID = 1; client.DispatchSync(svc::acts::ReportMetricUse::Params{ { - { PM_METRIC_CPU_UTILIZATION, SystemDeviceID, 0 }, - { PM_METRIC_CPU_FREQUENCY, SystemDeviceID, 0 }, + { PM_METRIC_CPU_UTILIZATION, ipc::kSystemDeviceId, 0 }, + { PM_METRIC_CPU_FREQUENCY, ipc::kSystemDeviceId, 0 }, } }); Logger::WriteMessage("SystemOnlyLeavesGpuEmpty: reported CPU utilization/frequency usage\n"); @@ -674,7 +665,6 @@ namespace InterimBroadcasterTests client.DispatchSync(svc::acts::SetTelemetryPeriod::Params{ .telemetrySamplePeriodMs = 350 }); Logger::WriteMessage("GpuOnlyLeavesSystemEmpty: telemetry period set to 350ms\n"); - const uint32_t SystemDeviceID = 65536; const uint32_t TargetDeviceID = 1; client.DispatchSync(svc::acts::ReportMetricUse::Params{ { diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index 05239859..4d57d040 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2024 Intel Corporation +// Copyright (C) 2017-2024 Intel Corporation // SPDX-License-Identifier: MIT #include "ConcreteMiddleware.h" #include @@ -18,6 +18,7 @@ #include "../Interprocess/source/IntrospectionTransfer.h" #include "../Interprocess/source/IntrospectionHelpers.h" #include "../Interprocess/source/IntrospectionCloneAllocators.h" +#include "../Interprocess/source/SystemDeviceId.h" #include "../Interprocess/source/PmStatusError.h" #include "DynamicQuery.h" #include "../ControlLib/PresentMonPowerTelemetry.h" @@ -235,8 +236,8 @@ namespace pmon::mid uint64_t offset = 0u; for (auto& qe : queryElements) { - // A device of zero is NOT a graphics adapter; 65536 is sys and not gpu either - if (qe.deviceId != 0 && qe.deviceId != 65536) { + // A device of zero is NOT a graphics adapter; system is not GPU either + if (qe.deviceId != 0 && qe.deviceId != ipc::kSystemDeviceId) { // If we have already set a device id in this query, check to // see if it's the same device id as previously set. Currently // we don't support querying multiple gpu devices in the one diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index b78da514..8235120d 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -1,9 +1,10 @@ -// Copyright (C) 2017-2024 Intel Corporation +// Copyright (C) 2017-2024 Intel Corporation // SPDX-License-Identifier: MIT #define NOMINMAX #include "../PresentMonUtils/StreamFormat.h" #include "FrameEventQuery.h" #include "../PresentMonAPIWrapperCommon/Introspection.h" +#include "../Interprocess/source/SystemDeviceId.h" #include "../CommonUtilities/Memory.h" #include "../CommonUtilities/Meta.h" #include "../CommonUtilities/log/Log.h" @@ -1309,8 +1310,8 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements) // current release: only 1 gpu device maybe be polled at a time for (auto& q : queryElements) { // validate that maximum 1 device (gpu) id is specified throughout the query - // universal (0) and system (65536) device metrics are exempt from this limit - if (q.deviceId != 0 && q.deviceId != 65536) { + // universal (0) and system (kSystemDeviceId) device metrics are exempt from this limit + if (q.deviceId != 0 && q.deviceId != ipc::kSystemDeviceId) { if (!referencedDevice_) { referencedDevice_ = q.deviceId; } diff --git a/IntelPresentMon/PresentMonService/PMMainThread.cpp b/IntelPresentMon/PresentMonService/PMMainThread.cpp index 84037104..d80f63c0 100644 --- a/IntelPresentMon/PresentMonService/PMMainThread.cpp +++ b/IntelPresentMon/PresentMonService/PMMainThread.cpp @@ -12,6 +12,7 @@ #include "../Interprocess/source/Interprocess.h" #include "../Interprocess/source/ShmNamer.h" #include "../Interprocess/source/MetricCapabilitiesShim.h" +#include "../Interprocess/source/SystemDeviceId.h" #include "CliOptions.h" #include "Registry.h" #include "GlobalIdentifiers.h" @@ -461,7 +462,7 @@ void CpuTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, ipc::Ser } else { // if system telemetry metrics active enter active polling loop - if (pm->CheckDeviceMetricUsage(65536)) { + if (pm->CheckDeviceMetricUsage(ipc::kSystemDeviceId)) { pmlog_dbg("detected system active"); } else { @@ -521,7 +522,7 @@ void CpuTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, ipc::Ser waiter.Wait(); // conditions for ending active poll and returning to idle state if (newActivation) { - if (!pm->CheckDeviceMetricUsage(65536)) { + if (!pm->CheckDeviceMetricUsage(ipc::kSystemDeviceId)) { break; } } From e0deb7315c19e1ff182e242e3b3905d27821aa27 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Tue, 30 Dec 2025 13:12:32 -0800 Subject: [PATCH 092/205] Adding in version 1 metrics to calculator --- .../CommonUtilities/mc/MetricsCalculator.cpp | 8 +++++ .../CommonUtilities/mc/MetricsTypes.h | 4 +++ IntelPresentMon/UnitTests/MetricsCore.cpp | 32 +++++++++++++++++-- PresentMon/OutputThread.cpp | 5 +-- 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 313c88b4..9ad8f724 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -796,9 +796,17 @@ namespace pmon::util::metrics } out.msInPresentApi = qpc.DurationMilliSeconds(present.getTimeInPresent()); + out.msUntilRenderStart = qpc.DeltaSignedMilliSeconds( + present.getPresentStartTime(), + present.gpuStartTime); out.msUntilRenderComplete = qpc.DeltaSignedMilliSeconds( present.getPresentStartTime(), present.getReadyTime()); + out.msGpuDuration = qpc.DurationMilliSeconds(present.gpuDuration); + out.msVideoDuration = qpc.DurationMilliSeconds(present.gpuVideoDuration); + out.msSinceInput = (present.inputTime == 0) + ? 0.0 + : qpc.DurationMilliSeconds(present.presentStartTime - present.inputTime); } void CalculateDisplayMetrics( diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h index 26e84bc3..bfcde272 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h @@ -148,7 +148,11 @@ namespace pmon::util::metrics { uint64_t timeInSeconds = 0; double msBetweenPresents = 0; double msInPresentApi = 0; + double msUntilRenderStart = 0; double msUntilRenderComplete = 0; + double msGpuDuration = 0; + double msVideoDuration = 0; + double msSinceInput = 0; // Display Metrics (displayed frames only) double msDisplayLatency = 0; diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index ae7088dd..160a68a4 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -1401,6 +1401,7 @@ namespace MetricsCoreTests /*readyTime*/ 1'500'000, // 0.15s → 50 ms after start /*displayed*/{} // no displays => "not displayed" path ); + first.gpuStartTime = 1'200'000; // 0.12s auto firstMetricsList = ComputeMetricsForPresent(qpc, first, nullptr, chain); Assert::AreEqual( @@ -1440,6 +1441,15 @@ namespace MetricsCoreTests 0.0001, L"msUntilRenderComplete should equal delta from PresentStartTime to ReadyTime."); + // msUntilRenderStart = delta between PresentStart and GPU start + double expectedMsUntilRenderStart = + qpc.DeltaUnsignedMilliSeconds(first.presentStartTime, first.gpuStartTime); + Assert::AreEqual( + expectedMsUntilRenderStart, + firstMetrics.msUntilRenderStart, + 0.0001, + L"msUntilRenderStart should equal delta from PresentStartTime to GPUStartTime."); + // With no prior present, CalculateCPUStart should return 0 → cpuStartQpc == 0 Assert::AreEqual( uint64_t(0), @@ -1470,6 +1480,7 @@ namespace MetricsCoreTests /*readyTime*/ 1'516'000, // 0.5s after first start /*displayed*/{} // still "not displayed" path ); + second.gpuStartTime = 1'220'000; // 0.122s auto secondMetricsList = ComputeMetricsForPresent(qpc, second, nullptr, chain); Assert::AreEqual( @@ -1488,10 +1499,12 @@ namespace MetricsCoreTests 0.0001, L"msBetweenPresents should equal delta between lastPresent and current presentStart."); - // msInPresentApi / msUntilRenderComplete for second + // msInPresentApi / msUntilRenderComplete / msUntilRenderStart for second double expectedMsInPresentSecond = qpc.DurationMilliSeconds(second.timeInPresent); double expectedMsUntilRenderCompleteSecond = qpc.DeltaUnsignedMilliSeconds(second.presentStartTime, second.readyTime); + double expectedMsUntilRenderStartSecond = + qpc.DeltaUnsignedMilliSeconds(second.presentStartTime, second.gpuStartTime); Assert::AreEqual( expectedMsInPresentSecond, @@ -1503,6 +1516,11 @@ namespace MetricsCoreTests secondMetrics.msUntilRenderComplete, 0.0001, L"Second frame msUntilRenderComplete should match start→ready delta."); + Assert::AreEqual( + expectedMsUntilRenderStartSecond, + secondMetrics.msUntilRenderStart, + 0.0001, + L"Second frame msUntilRenderStart should match start→GPU start delta."); // cpuStartQpc for second should come from CalculateCPUStart: // lastAppPresent == first (no propagated times) → first.start + first.timeInPresent @@ -1525,7 +1543,7 @@ namespace MetricsCoreTests /*timeInPresent*/ 200'000, /*readyTime*/ 1'500'000, /*displayed*/{}); // no displays - + auto firstMetricsList = ComputeMetricsForPresent(qpc, first, nullptr, chain); Assert::AreEqual( size_t(1), @@ -1548,6 +1566,7 @@ namespace MetricsCoreTests std::vector>{ { FrameType::Application, 2'000'000 } // one displayed instance }); + second.gpuStartTime = 1'200'000; // Dummy nextDisplayed with at least one display so the "with next" path is taken FrameData nextDisplayed = MakeFrame( @@ -1601,6 +1620,15 @@ namespace MetricsCoreTests 0.0001, L"msUntilRenderComplete should match start→ready delta for displayed frame."); + // msUntilRenderStart from start → GPU start + double expectedMsUntilRenderStartSecond = + qpc.DeltaUnsignedMilliSeconds(second.presentStartTime, second.gpuStartTime); + Assert::AreEqual( + expectedMsUntilRenderStartSecond, + secondMetrics.msUntilRenderStart, + 0.0001, + L"msUntilRenderStart should match start→GPU start delta for displayed frame."); + // cpuStartQpc should come from CalculateCPUStart using baseline frame as lastAppPresent: // (no propagated times) → first.start + first.timeInPresent uint64_t expectedCpuStartSecond = first.presentStartTime + first.timeInPresent; diff --git a/PresentMon/OutputThread.cpp b/PresentMon/OutputThread.cpp index 8ba95006..5ff7691e 100644 --- a/PresentMon/OutputThread.cpp +++ b/PresentMon/OutputThread.cpp @@ -270,9 +270,6 @@ static void PruneOldSwapChainData( // Don't prune DWM swap chains with address 0x0 bool shouldSkipPruning = isDwmProcess && swapChainAddress == 0x0; - - // Unified pruning: never prune while the unified swapchain still has - // buffered work (e.g., waiting for nextDisplayed). bool shouldPrune = false; if (!shouldSkipPruning) { shouldPrune = chain->mUnifiedSwapChain.IsPrunableBefore(minTimestamp); @@ -571,7 +568,7 @@ static void ProcessEvents( if (args.mUseV1Metrics) { // V1 emitter reads unified state BEFORE it advances. - if (emit) { + if (emit && it.nextDisplayed.has_value()) { ReportMetrics1Unified(pmSession, processInfo, chain, frame, isRecording, computeAvg); } From a794f352655e4b3d7d54b0562bc1863431726ed5 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Tue, 30 Dec 2025 16:27:24 -0800 Subject: [PATCH 093/205] Added special V1 processing into unified metrics calculator. --- .../CommonUtilities/mc/MetricsCalculator.cpp | 32 ++++- .../CommonUtilities/mc/MetricsCalculator.h | 3 +- .../CommonUtilities/mc/MetricsTypes.h | 6 + .../CommonUtilities/mc/UnifiedSwapChain.cpp | 10 +- .../CommonUtilities/mc/UnifiedSwapChain.h | 2 +- PresentMon/OutputThread.cpp | 131 ++++++------------ 6 files changed, 89 insertions(+), 95 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 9ad8f724..6b252472 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -681,7 +681,8 @@ namespace pmon::util::metrics const QpcConverter& qpc, const FrameData& present, FrameData* nextDisplayed, - SwapChainCoreState& chainState) + SwapChainCoreState& chainState, + MetricsVersion version) { std::vector results; @@ -723,6 +724,35 @@ namespace pmon::util::metrics return results; } + // V1: displayed presents are computed immediately (no look-ahead / no postponing). + // Emit exactly one row per present (legacy V1 behavior). + if (version == MetricsVersion::V1) { + const size_t displayIndex = 0; + uint64_t screenTime = present.getDisplayedScreenTime(displayIndex); + const bool isDisplayedInstance = (screenTime != 0); + uint64_t nextScreenTime = isDisplayedInstance ? screenTime : 0; // avoids bogus msDisplayedTime without requiring next present + + const auto indexing = DisplayIndexing::Calculate(present, nullptr); + const bool isAppFrame = (displayIndex == indexing.appIndex); + const FrameType frameType = isDisplayedInstance ? present.getDisplayedFrameType(displayIndex) : FrameType::NotSet; + + auto metrics = ComputeFrameMetrics( + qpc, + present, + screenTime, + nextScreenTime, + isDisplayedInstance, + isAppFrame, + frameType, + chainState); + + ApplyStateDeltas(chainState, metrics.stateDeltas); + results.push_back(std::move(metrics)); + + chainState.UpdateAfterPresent(present); + return results; + } + // There is at least one displayed frame to process const auto indexing = DisplayIndexing::Calculate(present, nextDisplayed); diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h index 4efd0e68..7bb65302 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h @@ -42,7 +42,8 @@ namespace pmon::util::metrics const QpcConverter& qpc, const FrameData& present, FrameData* nextDisplayed, - SwapChainCoreState& chainState); + SwapChainCoreState& chainState, + MetricsVersion version = MetricsVersion::V2); // === Pure Calculation Functions === diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h index bfcde272..01a73246 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h @@ -17,6 +17,12 @@ struct PresentEvent; // From PresentMonTraceConsumer namespace pmon::util::metrics { + // Metrics pipeline mode + enum class MetricsVersion { + V1, + V2, + }; + // What the animation error calculation is based on enum class AnimationErrorSource { CpuStart, diff --git a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp index 4cd85929..72f345df 100644 --- a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp +++ b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp @@ -55,12 +55,20 @@ namespace pmon::util::metrics // UnifiedSwapChain.cpp std::vector - UnifiedSwapChain::Enqueue(FrameData present) + UnifiedSwapChain::Enqueue(FrameData present, MetricsVersion version) { SanitizeDisplayedRepeatedPresents(present); std::vector out; + // V1: FIFO (no buffering / no look-ahead). Every present is ready immediately. + if (version == MetricsVersion::V1) { + waitingDisplayed_.reset(); + blocked_.clear(); + out.push_back(ReadyItem{ std::move(present), std::nullopt }); + return out; + } + // Seed baseline if (!swapChain.lastPresent.has_value()) { SeedFromFirstPresent(std::move(present)); diff --git a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h index 6663a9e7..7df9a0de 100644 --- a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h +++ b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h @@ -25,7 +25,7 @@ namespace pmon::util::metrics // Seed without needing a QPC converter (needed for console GetPresentProcessInfo() early-return). void SeedFromFirstPresent(FrameData present); - std::vector Enqueue(FrameData present); + std::vector Enqueue(FrameData present, MetricsVersion version); uint64_t GetLastPresentQpc() const; bool IsPrunableBefore(uint64_t minTimestampQpc) const; diff --git a/PresentMon/OutputThread.cpp b/PresentMon/OutputThread.cpp index 5ff7691e..7cecca61 100644 --- a/PresentMon/OutputThread.cpp +++ b/PresentMon/OutputThread.cpp @@ -381,90 +381,21 @@ static void ProcessRecordingToggle( } } -static void AdjustScreenTimeForCollapsedPresentNV1_Unified( - pmon::util::metrics::SwapChainCoreState const& s, - pmon::util::metrics::FrameData const& p, - uint64_t& screenTime, - uint64_t& flipDelayQpc) +static FrameMetrics1 ToFrameMetrics1(pmon::util::metrics::FrameMetrics const& m) { - // Port of AdjustScreenTimeForCollapsedPresentNV1(), but without mutating PresentEvent. - // Uses unified state instead of legacy chain fields. - if (s.lastDisplayedFlipDelay > 0 && (s.lastDisplayedScreenTime > screenTime)) { - if (!p.displayed.empty()) { - flipDelayQpc += (s.lastDisplayedScreenTime - screenTime); - screenTime = s.lastDisplayedScreenTime; - } - } -} - -static void ReportMetrics1Unified( - PMTraceSession const& pmSession, - ProcessInfo* processInfo, - SwapChainData* chain, - pmon::util::metrics::FrameData const& p, - bool isRecording, - bool computeAvg) -{ - auto const& s = chain->mUnifiedSwapChain.swapChain; - - const bool displayed = (p.finalState == PresentResult::Presented); - uint64_t screenTime = p.displayed.empty() ? 0 : p.displayed[0].second; - - uint64_t flipDelayQpc = p.flipDelay; - AdjustScreenTimeForCollapsedPresentNV1_Unified(s, p, screenTime, flipDelayQpc); - - FrameMetrics1 metrics{}; - - // Between presents (use unified lastPresent) - if (s.lastPresent.has_value()) { - metrics.msBetweenPresents = pmSession.TimestampDeltaToUnsignedMilliSeconds( - s.lastPresent->presentStartTime, - p.presentStartTime); - } - else { - metrics.msBetweenPresents = 0; - } - - metrics.msInPresentApi = pmSession.TimestampDeltaToMilliSeconds(p.timeInPresent); - metrics.msUntilRenderComplete = pmSession.TimestampDeltaToMilliSeconds(p.presentStartTime, p.readyTime); - - metrics.msUntilDisplayed = (!displayed || screenTime == 0) - ? 0 - : pmSession.TimestampDeltaToUnsignedMilliSeconds(p.presentStartTime, screenTime); - - metrics.msBetweenDisplayChange = (!displayed || s.lastDisplayedScreenTime == 0 || screenTime == 0) - ? 0 - : pmSession.TimestampDeltaToUnsignedMilliSeconds(s.lastDisplayedScreenTime, screenTime); - - metrics.msUntilRenderStart = pmSession.TimestampDeltaToMilliSeconds(p.presentStartTime, p.gpuStartTime); - metrics.msGPUDuration = pmSession.TimestampDeltaToMilliSeconds(p.gpuDuration); - metrics.msVideoDuration = pmSession.TimestampDeltaToMilliSeconds(p.gpuVideoDuration); - - metrics.msSinceInput = (p.inputTime == 0) - ? 0 - : pmSession.TimestampDeltaToMilliSeconds(p.presentStartTime - p.inputTime); - - metrics.qpcScreenTime = screenTime; - - metrics.msFlipDelay = (flipDelayQpc != 0) - ? pmSession.TimestampDeltaToMilliSeconds(flipDelayQpc) - : 0; - - if (isRecording) { - UpdateCsv(pmSession, processInfo, p, metrics); - } - - if (computeAvg) { - UpdateAverage(&chain->mUnifiedSwapChain.avgCPUDuration, metrics.msBetweenPresents); - UpdateAverage(&chain->mUnifiedSwapChain.avgGPUDuration, metrics.msGPUDuration); - - if (metrics.msUntilDisplayed > 0) { - UpdateAverage(&chain->mUnifiedSwapChain.avgDisplayLatency, metrics.msUntilDisplayed); - if (metrics.msBetweenDisplayChange > 0) { - UpdateAverage(&chain->mUnifiedSwapChain.avgDisplayedTime, metrics.msBetweenDisplayChange); - } - } - } + FrameMetrics1 out{}; + out.msBetweenPresents = m.msBetweenPresents; + out.msInPresentApi = m.msInPresentApi; + out.msUntilRenderComplete = m.msUntilRenderComplete; + out.msUntilDisplayed = m.msUntilDisplayed; + out.msBetweenDisplayChange = m.msBetweenDisplayChange; + out.msUntilRenderStart = m.msUntilRenderStart; + out.msGPUDuration = m.msGpuDuration; + out.msVideoDuration = m.msVideoDuration; + out.msSinceInput = m.msSinceInput; + out.qpcScreenTime = m.screenTimeQpc; + out.msFlipDelay = m.msFlipDelay.has_value() ? m.msFlipDelay.value() : 0.0; + return out; } static void ProcessEvents( @@ -548,7 +479,8 @@ static void ProcessEvents( continue; } - auto ready = chain->mUnifiedSwapChain.Enqueue(pmon::util::metrics::FrameData::CopyFrameData(presentEvent)); + auto ready = chain->mUnifiedSwapChain.Enqueue(pmon::util::metrics::FrameData::CopyFrameData(presentEvent), + args.mUseV1Metrics ? pmon::util::metrics::MetricsVersion::V1 : pmon::util::metrics::MetricsVersion::V2); // Do we need to emit metrics for this present? const bool emit = (isRecording || computeAvg); @@ -567,17 +499,34 @@ static void ProcessEvents( } if (args.mUseV1Metrics) { - // V1 emitter reads unified state BEFORE it advances. - if (emit && it.nextDisplayed.has_value()) { - ReportMetrics1Unified(pmSession, processInfo, chain, frame, isRecording, computeAvg); - } + // V1: compute immediately (no look-ahead) and emit legacy V1 CSV. + auto computed = ComputeMetricsForPresent(qpc, frame, nullptr, chain->mUnifiedSwapChain.swapChain, MetricsVersion::V1); + + if (emit) { + for (auto const& cm : computed) { + auto const m1 = ToFrameMetrics1(cm.metrics); - // Advance unified swapchain state using the canonical unified calculator. - (void)ComputeMetricsForPresent(qpc, frame, nextPtr, chain->mUnifiedSwapChain.swapChain); + if (isRecording) { + UpdateCsv(pmSession, processInfo, frame, m1); + } + + if (computeAvg) { + UpdateAverage(&chain->mUnifiedSwapChain.avgCPUDuration, m1.msBetweenPresents); + UpdateAverage(&chain->mUnifiedSwapChain.avgGPUDuration, m1.msGPUDuration); + + if (m1.msUntilDisplayed > 0) { + UpdateAverage(&chain->mUnifiedSwapChain.avgDisplayLatency, m1.msUntilDisplayed); + if (m1.msBetweenDisplayChange > 0) { + UpdateAverage(&chain->mUnifiedSwapChain.avgDisplayedTime, m1.msBetweenDisplayChange); + } + } + } + } + } } else { // V2 unified metrics: compute + advance together - auto computed = ComputeMetricsForPresent(qpc, frame, nextPtr, chain->mUnifiedSwapChain.swapChain); + auto computed = ComputeMetricsForPresent(qpc, frame, nextPtr, chain->mUnifiedSwapChain.swapChain, MetricsVersion::V2); if (emit) { for (auto const& cm : computed) { From 6f8444922c8abd3c495b82a9294e6aa32f7b73ec Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Wed, 31 Dec 2025 06:50:13 -0800 Subject: [PATCH 094/205] NV V2 collapsed present handling Fix NV V2 collapsed/runt present handling by persisting nextDisplayed adjustments via swapchain-owned ReadyItem pointers. --- .../CommonUtilities/mc/UnifiedSwapChain.cpp | 43 +++++++++++-------- .../CommonUtilities/mc/UnifiedSwapChain.h | 9 ++-- PresentMon/OutputThread.cpp | 10 +---- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp index 72f345df..6e56351c 100644 --- a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp +++ b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp @@ -63,9 +63,9 @@ namespace pmon::util::metrics // V1: FIFO (no buffering / no look-ahead). Every present is ready immediately. if (version == MetricsVersion::V1) { - waitingDisplayed_.reset(); - blocked_.clear(); - out.push_back(ReadyItem{ std::move(present), std::nullopt }); + waitingDisplayed.reset(); + blocked.clear(); + out.push_back(ReadyItem{ std::move(present), nullptr, nullptr }); return out; } @@ -80,31 +80,36 @@ namespace pmon::util::metrics (present.getDisplayedCount() > 0); if (isDisplayed) { - // 1) Finalize previously waiting displayed - if (waitingDisplayed_.has_value()) { - out.push_back(ReadyItem{ std::move(*waitingDisplayed_), present /* nextDisplayed */ }); - waitingDisplayed_.reset(); + // 1) Finalize previously waiting displayed (if any), pointing at swapchain-owned next displayed. + if (waitingDisplayed.has_value()) { + FrameData prev = std::move(*waitingDisplayed); + waitingDisplayed = std::move(present); + + out.push_back(ReadyItem{ std::move(prev), nullptr, &*waitingDisplayed }); + } + else { + // First displayed: becomes the waitingDisplayed_. + waitingDisplayed = std::move(present); } - - // 2) Release blocked not-displayed frames - while (!blocked_.empty()) { - out.push_back(ReadyItem{ std::move(blocked_.front()), std::nullopt }); - blocked_.pop_front(); + + // 2) Release blocked not-displayed frames (owned, no look-ahead). + while (!blocked.empty()) { + out.push_back(ReadyItem{ std::move(blocked.front()), nullptr, nullptr }); + blocked.pop_front(); } - // 3) Current displayed is ready (all-but-last); becomes the new waitingDisplayed - out.push_back(ReadyItem{ present /* copy */, std::nullopt }); - waitingDisplayed_ = std::move(present); + // 3) Current displayed is ready (all-but-last); provide a pointer so NV adjustments persist. + out.push_back(ReadyItem{ FrameData{}, &*waitingDisplayed, nullptr }); return out; } // Not displayed - if (waitingDisplayed_.has_value()) { - blocked_.push_back(std::move(present)); + if (waitingDisplayed.has_value()) { + blocked.push_back(std::move(present)); return out; // nothing ready yet } - out.push_back(ReadyItem{ std::move(present), std::nullopt }); + out.push_back(ReadyItem{ std::move(present), nullptr, nullptr }); return out; } -} \ No newline at end of file +} diff --git a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h index 7df9a0de..71677df8 100644 --- a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h +++ b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.h @@ -16,8 +16,9 @@ namespace pmon::util::metrics { struct ReadyItem { - FrameData present; - std::optional nextDisplayed; // populated when flushing pending + FrameData present; // owned (used when presentPtr==nullptr) + FrameData* presentPtr = nullptr; // points into waitingDisplayed_ (optional) + FrameData* nextDisplayedPtr = nullptr; // points into waitingDisplayed_ (optional) }; SwapChainCoreState swapChain; @@ -42,7 +43,7 @@ namespace pmon::util::metrics private: static void SanitizeDisplayedRepeatedPresents(FrameData& present); - std::optional waitingDisplayed_; - std::deque blocked_; + std::optional waitingDisplayed; + std::deque blocked; }; } diff --git a/PresentMon/OutputThread.cpp b/PresentMon/OutputThread.cpp index 7cecca61..a82997b2 100644 --- a/PresentMon/OutputThread.cpp +++ b/PresentMon/OutputThread.cpp @@ -489,14 +489,8 @@ static void ProcessEvents( // Build FrameData copies for the unified calculator state-advance (and V2 metrics). using namespace pmon::util::metrics; - FrameData frame = std::move(it.present); - - FrameData nextFrame{}; - FrameData* nextPtr = nullptr; - if (it.nextDisplayed.has_value()) { - nextFrame = std::move(*it.nextDisplayed); - nextPtr = &nextFrame; - } + FrameData& frame = (it.presentPtr != nullptr) ? *it.presentPtr : it.present; + FrameData* nextPtr = it.nextDisplayedPtr; if (args.mUseV1Metrics) { // V1: compute immediately (no look-ahead) and emit legacy V1 CSV. From 3335ae6ee1bf0308842a209847312f9d01647f8b Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Wed, 31 Dec 2025 08:43:39 -0800 Subject: [PATCH 095/205] Updating NV collapsed present support to include V1 metrics. --- .../CommonUtilities/mc/MetricsCalculator.cpp | 43 +++++++++++++++--- .../CommonUtilities/mc/MetricsCalculator.h | 2 +- IntelPresentMon/UnitTests/MetricsCore.cpp | 45 +++++++++++++++++++ 3 files changed, 83 insertions(+), 7 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 6b252472..ca59355e 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -301,11 +301,30 @@ namespace pmon::util::metrics } void AdjustScreenTimeForCollapsedPresentNV( - const FrameData& present, + FrameData& present, FrameData* nextDisplayedPresent, + const uint64_t& lastDisplayedFlipDelay, + const uint64_t& lastDisplayedScreenTime, uint64_t& screenTime, - uint64_t& nextScreenTime) + uint64_t& nextScreenTime, + MetricsVersion version) { + if (version == MetricsVersion::V1) { + // NV1 collapsed/runt correction: legacy V1 adjusts the *current* present using the + // previous displayed state when the last displayed screen time (adjusted by flip delay) + // is greater than this present's screen time. + if (lastDisplayedFlipDelay > 0 && + lastDisplayedScreenTime > screenTime && + !present.displayed.empty()) { + + const uint64_t diff = lastDisplayedScreenTime - screenTime; + present.flipDelay += diff; + present.displayed[0].second = lastDisplayedScreenTime; + screenTime = present.displayed[0].second; + } + return; + } + // nextDisplayedPresent should always be non-null for NV GPU. if (present.flipDelay && screenTime > nextScreenTime && nextDisplayedPresent) { // If screenTime that is adjusted by flipDelay is larger than nextScreenTime, @@ -679,7 +698,7 @@ namespace pmon::util::metrics std::vector ComputeMetricsForPresent( const QpcConverter& qpc, - const FrameData& present, + FrameData& present, FrameData* nextDisplayed, SwapChainCoreState& chainState, MetricsVersion version) @@ -729,11 +748,23 @@ namespace pmon::util::metrics if (version == MetricsVersion::V1) { const size_t displayIndex = 0; uint64_t screenTime = present.getDisplayedScreenTime(displayIndex); - const bool isDisplayedInstance = (screenTime != 0); - uint64_t nextScreenTime = isDisplayedInstance ? screenTime : 0; // avoids bogus msDisplayedTime without requiring next present + uint64_t nextScreenTime = 0; + AdjustScreenTimeForCollapsedPresentNV( + present, + nextDisplayed, + chainState.lastDisplayedFlipDelay, + chainState.lastDisplayedScreenTime, + screenTime, + nextScreenTime, + version); + + // This is so msDisplayedTime comes back as 0 instead of garbage for V1 single-row output. + // TODO: Better option is to have display metrics be optional. Update metrics struct accordingly. + nextScreenTime = screenTime; const auto indexing = DisplayIndexing::Calculate(present, nullptr); const bool isAppFrame = (displayIndex == indexing.appIndex); + const bool isDisplayedInstance = isDisplayed && screenTime != 0; const FrameType frameType = isDisplayedInstance ? present.getDisplayedFrameType(displayIndex) : FrameType::NotSet; auto metrics = ComputeFrameMetrics( @@ -775,7 +806,7 @@ namespace pmon::util::metrics break; // No next screen time available } - AdjustScreenTimeForCollapsedPresentNV(present, nextDisplayed, screenTime, nextScreenTime); + AdjustScreenTimeForCollapsedPresentNV(present, nextDisplayed, 0, 0, screenTime, nextScreenTime, version); const bool isAppFrame = (displayIndex == indexing.appIndex); const bool isDisplayedInstance = isDisplayed && screenTime != 0 && nextScreenTime != 0; diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h index 7bb65302..720f4ac1 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.h @@ -40,7 +40,7 @@ namespace pmon::util::metrics std::vector ComputeMetricsForPresent( const QpcConverter& qpc, - const FrameData& present, + FrameData& present, FrameData* nextDisplayed, SwapChainCoreState& chainState, MetricsVersion version = MetricsVersion::V2); diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index 160a68a4..c9aa781c 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -2252,6 +2252,51 @@ namespace MetricsCoreTests L"NV2: when no collapse, flipDelay should remain unchanged"); } } + + + TEST_METHOD(NvCollapsedPresent_V1_AdjustsCurrentScreenTimeAndFlipDelay) + { + // Legacy PresentMon V1 behavior: when the previous displayed screen time (adjusted by flipDelay) + // is greater than the current present's screen time, treat the current as a collapsed/runt frame + // and adjust *this* present's screen time + flipDelay. + + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + chain.lastDisplayedScreenTime = 5'500'000; + chain.lastDisplayedFlipDelay = 50'000; + + FrameData current{}; + current.presentStartTime = 4'000'000; + current.timeInPresent = 50'000; + current.readyTime = 4'100'000; + current.flipDelay = 100'000; + current.setFinalState(PresentResult::Presented); + current.displayed.push_back({ FrameType::Application, 5'000'000 }); + + auto results = ComputeMetricsForPresent(qpc, current, nullptr, chain, MetricsVersion::V1); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + Assert::AreEqual(uint64_t(5'500'000), m.screenTimeQpc, + L"NV1 should adjust current screenTime to lastDisplayedScreenTime"); + + const uint64_t expectedFlipDelay = 100'000 + (5'500'000 - 5'000'000); + Assert::IsTrue(m.msFlipDelay.has_value(), L"msFlipDelay should be set for displayed frame"); + if (m.msFlipDelay.has_value()) { + Assert::AreEqual(qpc.DurationMilliSeconds(expectedFlipDelay), m.msFlipDelay.value(), 0.0001, + L"NV1 should adjust current flipDelay to account for screenTime catch-up"); + } + + // Validate the legacy-style mutation of the current present and that chain advanced using adjusted values. + Assert::AreEqual(uint64_t(5'500'000), current.displayed[0].second, + L"NV1 should update current.displayed[0].second"); + Assert::AreEqual(expectedFlipDelay, current.flipDelay, + L"NV1 should update current.flipDelay"); + Assert::AreEqual(uint64_t(5'500'000), chain.lastDisplayedScreenTime, + L"Chain should latch adjusted screenTime"); + } + }; TEST_CLASS(DisplayLatencyTests) { From 1701cb86e9abda756439fe84e34a9e035e409cf0 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Wed, 31 Dec 2025 10:04:51 -0800 Subject: [PATCH 096/205] Adding in unified swap chain ULTs --- IntelPresentMon/UnitTests/MetricsCore.cpp | 289 +++++++++++++++++++++- 1 file changed, 288 insertions(+), 1 deletion(-) diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index c9aa781c..cd622fd8 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -872,7 +873,293 @@ namespace MetricsCoreTests return f; } - TEST_CLASS(ComputeMetricsForPresentTests) + +TEST_CLASS(UnifiedSwapChainTests) +{ +public: + TEST_METHOD(Enqueue_V2_SeedsFirstPresent_ReturnsNoReady) + { + UnifiedSwapChain u{}; + + // First present seeds baseline (no pipeline output). + auto seed = MakeFrame(PresentResult::Presented, 1'000'000, 10, 1'000'010, {}); + auto out = u.Enqueue(std::move(seed), MetricsVersion::V2); + + Assert::AreEqual(size_t(0), out.size()); + Assert::IsTrue(u.swapChain.lastPresent.has_value()); + Assert::AreEqual(uint64_t(1'000'000), u.GetLastPresentQpc()); + } + + TEST_METHOD(Enqueue_V2_NotDisplayed_NoWaiting_ReturnsSingleOwnedItem) + { + UnifiedSwapChain u{}; + (void)u.Enqueue(MakeFrame(PresentResult::Presented, 1'000'000, 10, 1'000'010, {}), MetricsVersion::V2); // seed + + auto p = MakeFrame(static_cast(9999), 2'000'000, 10, 2'000'010, {}); // not displayed + auto out = u.Enqueue(std::move(p), MetricsVersion::V2); + + Assert::AreEqual(size_t(1), out.size()); + Assert::IsNull(out[0].presentPtr); + Assert::IsNull(out[0].nextDisplayedPtr); + Assert::AreEqual(uint64_t(2'000'000), out[0].present.presentStartTime); + } + + TEST_METHOD(Enqueue_V2_Displayed_FirstDisplayed_ReturnsCurrentDisplayedPtrItemOnly) + { + UnifiedSwapChain u{}; + (void)u.Enqueue(MakeFrame(PresentResult::Presented, 1'000'000, 10, 1'000'010, {}), MetricsVersion::V2); // seed + + auto displayed = MakeFrame(PresentResult::Presented, 2'000'000, 10, 2'000'010, + { { FrameType::Application, 2'500'000 } }); + + auto out = u.Enqueue(std::move(displayed), MetricsVersion::V2); + + Assert::AreEqual(size_t(1), out.size()); + Assert::IsNotNull(out[0].presentPtr); + Assert::IsNull(out[0].nextDisplayedPtr); + Assert::AreEqual(uint64_t(2'000'000), out[0].presentPtr->presentStartTime); + } + + TEST_METHOD(Enqueue_V2_NotDisplayed_WithWaiting_IsBufferedUntilNextDisplayed) + { + UnifiedSwapChain u{}; + (void)u.Enqueue(MakeFrame(PresentResult::Presented, 1'000'000, 10, 1'000'010, {}), MetricsVersion::V2); // seed + + // First displayed => enters waitingDisplayed, produces current item (but may postpone metrics). + (void)u.Enqueue(MakeFrame(PresentResult::Presented, 2'000'000, 10, 2'000'010, + { { FrameType::Application, 2'500'000 } }), MetricsVersion::V2); + + // Not displayed while waiting => no ready work. + auto out1 = u.Enqueue(MakeFrame(static_cast(9999), 2'200'000, 10, 2'200'010, {}), MetricsVersion::V2); + Assert::AreEqual(size_t(0), out1.size()); + + // Next displayed => releases blocked. + auto out2 = u.Enqueue(MakeFrame(PresentResult::Presented, 3'000'000, 10, 3'000'010, + { { FrameType::Application, 3'500'000 } }), MetricsVersion::V2); + + // finalize previous + blocked + current + Assert::AreEqual(size_t(3), out2.size()); + Assert::AreEqual(uint64_t(2'000'000), out2[0].present.presentStartTime); // finalize previous + Assert::AreEqual(uint64_t(2'200'000), out2[1].present.presentStartTime); // released blocked + Assert::IsNotNull(out2[2].presentPtr); // current displayed + Assert::AreEqual(uint64_t(3'000'000), out2[2].presentPtr->presentStartTime); + } + + TEST_METHOD(Enqueue_V2_Displayed_WithWaiting_OrdersFinalizeThenBlockedThenCurrent) + { + UnifiedSwapChain u{}; + (void)u.Enqueue(MakeFrame(PresentResult::Presented, 1'000'000, 10, 1'000'010, {}), MetricsVersion::V2); // seed + + // Displayed A enters waiting + (void)u.Enqueue(MakeFrame(PresentResult::Presented, 2'000'000, 10, 2'000'010, + { { FrameType::Application, 2'500'000 } }), MetricsVersion::V2); + + // Buffer B and C + (void)u.Enqueue(MakeFrame(static_cast(9999), 2'100'000, 10, 2'100'010, {}), MetricsVersion::V2); + (void)u.Enqueue(MakeFrame(static_cast(9999), 2'200'000, 10, 2'200'010, {}), MetricsVersion::V2); + + // Next displayed D triggers: finalize A, then B,C, then current D + auto out = u.Enqueue(MakeFrame(PresentResult::Presented, 3'000'000, 10, 3'000'010, + { { FrameType::Application, 3'500'000 } }), MetricsVersion::V2); + + Assert::AreEqual(size_t(4), out.size()); + Assert::AreEqual(uint64_t(2'000'000), out[0].present.presentStartTime); + Assert::IsNotNull(out[0].nextDisplayedPtr); + Assert::AreEqual(uint64_t(3'000'000), out[0].nextDisplayedPtr->presentStartTime); + + Assert::AreEqual(uint64_t(2'100'000), out[1].present.presentStartTime); + Assert::AreEqual(uint64_t(2'200'000), out[2].present.presentStartTime); + + Assert::IsNotNull(out[3].presentPtr); + Assert::AreEqual(uint64_t(3'000'000), out[3].presentPtr->presentStartTime); + } + + TEST_METHOD(Enqueue_V2_SanitizeDisplayed_RemovesAppThenRepeated) + { + UnifiedSwapChain u{}; + (void)u.Enqueue(MakeFrame(PresentResult::Presented, 1'000'000, 10, 1'000'010, {}), MetricsVersion::V2); // seed + + auto p = MakeFrame(PresentResult::Presented, 2'000'000, 10, 2'000'010, + { { FrameType::Application, 2'500'000 }, + { FrameType::Repeated, 2'700'000 } }); + + auto out = u.Enqueue(std::move(p), MetricsVersion::V2); + + Assert::AreEqual(size_t(1), out.size()); + Assert::IsNotNull(out[0].presentPtr); + + Assert::AreEqual(size_t(1), out[0].presentPtr->displayed.size()); + Assert::AreEqual((int)FrameType::Application, (int)out[0].presentPtr->displayed[0].first); + } + + TEST_METHOD(Enqueue_V2_SanitizeDisplayed_RemovesRepeatedThenApp) + { + UnifiedSwapChain u{}; + (void)u.Enqueue(MakeFrame(PresentResult::Presented, 1'000'000, 10, 1'000'010, {}), MetricsVersion::V2); // seed + + auto p = MakeFrame(PresentResult::Presented, 2'000'000, 10, 2'000'010, + { { FrameType::Repeated, 2'400'000 }, + { FrameType::Application, 2'500'000 } }); + + auto out = u.Enqueue(std::move(p), MetricsVersion::V2); + + Assert::AreEqual(size_t(1), out.size()); + Assert::IsNotNull(out[0].presentPtr); + + Assert::AreEqual(size_t(1), out[0].presentPtr->displayed.size()); + Assert::AreEqual((int)FrameType::Application, (int)out[0].presentPtr->displayed[0].first); + } + + TEST_METHOD(Pipeline_V2_PostponedLastDisplayInstance_EmittedOnFinalize) + { + QpcConverter qpc(10'000'000, 0); + UnifiedSwapChain u{}; + + // Seed history + (void)u.Enqueue(MakeFrame(PresentResult::Presented, 1'000'000, 10, 1'000'010, {}), MetricsVersion::V2); + + // First displayed has two instances: app then repeated. + auto outA = u.Enqueue(MakeFrame(PresentResult::Presented, 2'000'000, 10, 2'000'010, + { { FrameType::Intel_XEFG, 2'500'000 }, + { FrameType::Application, 2'700'000 } }), + MetricsVersion::V2); + + Assert::AreEqual(size_t(1), outA.size()); + Assert::IsNotNull(outA[0].presentPtr); + + // Processing current displayed without next: should produce all-but-last => one result at 2'500'000. + auto resA = ComputeMetricsForPresent(qpc, *outA[0].presentPtr, nullptr, u.swapChain, MetricsVersion::V2); + Assert::AreEqual(size_t(1), resA.size()); + Assert::AreEqual(uint64_t(2'500'000), resA[0].metrics.screenTimeQpc); + + // Next displayed triggers finalize of the previous displayed (postponed last instance at 2'700'000). + auto outB = u.Enqueue(MakeFrame(PresentResult::Presented, 3'000'000, 10, 3'000'010, + { { FrameType::Application, 3'500'000 } }), + MetricsVersion::V2); + + Assert::AreEqual(size_t(2), outB.size()); + Assert::IsNotNull(outB[0].nextDisplayedPtr); + + auto resFinalize = ComputeMetricsForPresent(qpc, outB[0].present, outB[0].nextDisplayedPtr, u.swapChain, MetricsVersion::V2); + Assert::AreEqual(size_t(1), resFinalize.size()); + Assert::AreEqual(uint64_t(2'700'000), resFinalize[0].metrics.screenTimeQpc); + } + + TEST_METHOD(Pipeline_V2_NvCollapsed_AdjustmentPersistsViaNextDisplayedPtr) + { + QpcConverter qpc(10'000'000, 0); + UnifiedSwapChain u{}; + + (void)u.Enqueue(MakeFrame(PresentResult::Presented, 1'000'000, 10, 1'000'010, {}), MetricsVersion::V2); + + // First displayed: collapsed/runt-style (flipDelay set), screenTime is later than next's raw screenTime. + (void)u.Enqueue(MakeFrame(PresentResult::Presented, 4'000'000, 50'000, 4'100'000, + { { FrameType::Application, 5'500'000 } }, + 0, 0, 200'000), + MetricsVersion::V2); + + // Second displayed: raw screenTime earlier -> should be adjusted upward by NV2 when finalizing first. + auto out = u.Enqueue(MakeFrame(PresentResult::Presented, 5'000'000, 40'000, 5'100'000, + { { FrameType::Application, 5'000'000 } }, + 0, 0, 100'000), + MetricsVersion::V2); + + // Expect: finalize previous + current displayed + Assert::AreEqual(size_t(2), out.size()); + Assert::IsNotNull(out[0].nextDisplayedPtr); + Assert::IsNotNull(out[1].presentPtr); + + // Finalize first with look-ahead to second => mutates second via pointer. + (void)ComputeMetricsForPresent(qpc, out[0].present, out[0].nextDisplayedPtr, u.swapChain, MetricsVersion::V2); + + // Mutation must persist on swapchain-owned second frame. + Assert::AreEqual(uint64_t(5'500'000), out[1].presentPtr->displayed[0].second); + Assert::AreEqual(uint64_t(100'000 + (5'500'000 - 5'000'000)), out[1].presentPtr->flipDelay); + } + + TEST_METHOD(Pipeline_V1_NvCollapsed_AdjustsCurrentPresent) + { + QpcConverter qpc(10'000'000, 0); + UnifiedSwapChain u{}; + + // Establish previous displayed state (lastDisplayedScreenTime/flipDelay) via first displayed. + auto out1 = u.Enqueue(MakeFrame(PresentResult::Presented, 4'000'000, 50'000, 4'100'000, + { { FrameType::Application, 5'500'000 } }, + 0, 0, 200'000), + MetricsVersion::V1); + + Assert::AreEqual(size_t(1), out1.size()); + + (void)ComputeMetricsForPresent(qpc, out1[0].present, nullptr, u.swapChain, MetricsVersion::V1); + + // Second present has earlier raw screenTime; NV1 should adjust *current* present to lastDisplayedScreenTime. + auto out2 = u.Enqueue(MakeFrame(PresentResult::Presented, 5'000'000, 40'000, 5'100'000, + { { FrameType::Application, 5'000'000 } }, + 0, 0, 100'000), + MetricsVersion::V1); + + Assert::AreEqual(size_t(1), out2.size()); + + (void)ComputeMetricsForPresent(qpc, out2[0].present, nullptr, u.swapChain, MetricsVersion::V1); + + Assert::AreEqual(uint64_t(5'500'000), out2[0].present.displayed[0].second); + Assert::AreEqual(uint64_t(100'000 + (5'500'000 - 5'000'000)), out2[0].present.flipDelay); + } + + TEST_METHOD(Pipeline_V1_NoNvCollapse_DoesNotModifyCurrentPresent) + { + QpcConverter qpc(10'000'000, 0); + UnifiedSwapChain u{}; + + // Prior displayed state via first displayed. + auto out1 = u.Enqueue(MakeFrame(PresentResult::Presented, 4'000'000, 50'000, 4'100'000, + { { FrameType::Application, 5'000'000 } }, + 0, 0, 200'000), + MetricsVersion::V1); + (void)ComputeMetricsForPresent(qpc, out1[0].present, nullptr, u.swapChain, MetricsVersion::V1); + + // Current has later screenTime => no collapse. + auto out2 = u.Enqueue(MakeFrame(PresentResult::Presented, 5'000'000, 40'000, 5'100'000, + { { FrameType::Application, 6'000'000 } }, + 0, 0, 100'000), + MetricsVersion::V1); + + // Preserve originals for comparison. + const uint64_t origScreen = out2[0].present.displayed[0].second; + const uint64_t origFlipDelay = out2[0].present.flipDelay; + + (void)ComputeMetricsForPresent(qpc, out2[0].present, nullptr, u.swapChain, MetricsVersion::V1); + + Assert::AreEqual(origScreen, out2[0].present.displayed[0].second); + Assert::AreEqual(origFlipDelay, out2[0].present.flipDelay); + } + + TEST_METHOD(Enqueue_V1_ClearsV2Buffers_AndIsAlwaysReady) + { + UnifiedSwapChain u{}; + (void)u.Enqueue(MakeFrame(PresentResult::Presented, 1'000'000, 10, 1'000'010, {}), MetricsVersion::V2); // seed + + // Create V2 waitingDisplayed + blocked. + (void)u.Enqueue(MakeFrame(PresentResult::Presented, 2'000'000, 10, 2'000'010, + { { FrameType::Application, 2'500'000 } }), MetricsVersion::V2); + (void)u.Enqueue(MakeFrame(static_cast(9999), 2'200'000, 10, 2'200'010, {}), MetricsVersion::V2); + + // V1 enqueue must clear V2 buffers and return one ready item. + auto outV1 = u.Enqueue(MakeFrame(static_cast(9999), 2'300'000, 10, 2'300'010, {}), MetricsVersion::V1); + Assert::AreEqual(size_t(1), outV1.size()); + + // Next V2 displayed should behave as "no waiting/no blocked": returns only current displayed item. + auto outV2 = u.Enqueue(MakeFrame(PresentResult::Presented, 3'000'000, 10, 3'000'010, + { { FrameType::Application, 3'500'000 } }), MetricsVersion::V2); + + Assert::AreEqual(size_t(1), outV2.size()); + Assert::IsNotNull(outV2[0].presentPtr); + Assert::IsNull(outV2[0].nextDisplayedPtr); + } +}; + + +TEST_CLASS(ComputeMetricsForPresentTests) { public: TEST_METHOD(ComputeMetricsForPresent_NotDisplayed_NoDisplays_ProducesSingleMetricsAndUpdatesChain) From 392f8e031bf9ebe9fdd7e96675e7766bc566a83c Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Fri, 2 Jan 2026 14:01:42 -0800 Subject: [PATCH 097/205] Removed unused pending presents --- .../CommonUtilities/mc/SwapChainState.h | 6 +- .../CommonUtilities/mc/UnifiedSwapChain.cpp | 2 - .../PresentMonAPI2Tests/SwapChainTests.cpp | 71 +------------------ IntelPresentMon/UnitTests/MetricsCore.cpp | 15 ---- 4 files changed, 3 insertions(+), 91 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/SwapChainState.h b/IntelPresentMon/CommonUtilities/mc/SwapChainState.h index 6e7d6957..74965d06 100644 --- a/IntelPresentMon/CommonUtilities/mc/SwapChainState.h +++ b/IntelPresentMon/CommonUtilities/mc/SwapChainState.h @@ -3,17 +3,13 @@ #pragma once #include -#include #include "MetricsTypes.h" namespace pmon::util::metrics { struct SwapChainCoreState { - // Pending and Historical Presents - - // Pending presents waiting for the next displayed present. - std::vector pendingPresents; + // Historical Presents // The most recent present that has been processed (e.g., output into CSV and/or used for frame // statistics). diff --git a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp index 6e56351c..48763bc4 100644 --- a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp +++ b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp @@ -46,10 +46,8 @@ namespace pmon::util::metrics void UnifiedSwapChain::SeedFromFirstPresent(FrameData present) { - // Mirror console baseline behavior: // first present just seeds history (no pending pipeline). - swapChain.pendingPresents.clear(); swapChain.UpdateAfterPresent(present); } diff --git a/IntelPresentMon/PresentMonAPI2Tests/SwapChainTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/SwapChainTests.cpp index 36dfefa1..f9149ccb 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/SwapChainTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/SwapChainTests.cpp @@ -44,55 +44,6 @@ namespace SwapChainTests // Verify optional presents are empty Assert::IsFalse(swapChain.lastPresent.has_value()); Assert::IsFalse(swapChain.lastAppPresent.has_value()); - - // Verify pending presents vector is empty - Assert::AreEqual(size_t(0), swapChain.pendingPresents.size()); - } - - TEST_METHOD(SwapChainInstantiation_ConsolePattern) - { - // Test with a simple type to verify default initialization - pmon::util::metrics::SwapChainCoreState swapChain{}; - - // Create a mock present event - pmon::util::metrics::FrameData present{}; - present.presentStartTime = 1122; - present.appFrameId = 3344; - - // Add to pending presents - swapChain.pendingPresents.push_back(present); - Assert::AreEqual(size_t(1), swapChain.pendingPresents.size()); - - // Set as last present - swapChain.lastPresent = present; - Assert::IsTrue(swapChain.lastPresent.has_value()); - Assert::AreEqual(uint64_t(1122), swapChain.lastPresent.value().presentStartTime); - Assert::AreEqual(uint32_t(3344), swapChain.lastPresent.value().appFrameId); - } - - TEST_METHOD(PendingPresents_VectorOperations) - { - pmon::util::metrics::SwapChainCoreState swapChain{}; - - // Create some mock presents - pmon::util::metrics::FrameData present[3]{}; - present[0].appFrameId = 1; - present[1].appFrameId = 2; - present[2].appFrameId = 3; - - // Add multiple items - swapChain.pendingPresents.push_back(present[0]); - swapChain.pendingPresents.push_back(present[1]); - swapChain.pendingPresents.push_back(present[2]); - - Assert::AreEqual(size_t(3), swapChain.pendingPresents.size()); - Assert::AreEqual(uint32_t(1), swapChain.pendingPresents[0].appFrameId); - Assert::AreEqual(uint32_t(2), swapChain.pendingPresents[1].appFrameId); - Assert::AreEqual(uint32_t(3), swapChain.pendingPresents[2].appFrameId); - - // Clear pending presents - swapChain.pendingPresents.clear(); - Assert::AreEqual(size_t(0), swapChain.pendingPresents.size()); } TEST_METHOD(OptionalPresents_HasValue) @@ -222,11 +173,10 @@ namespace SwapChainTests present.appFrameId = 7777; present.presentStartTime = 5555; present.timeInPresent = 2000; - + // Store in core state - swapChain.pendingPresents.push_back(present); swapChain.lastPresent = present; - + // Verify access Assert::IsTrue(swapChain.lastPresent.has_value()); Assert::AreEqual(uint32_t(7777), swapChain.lastPresent.value().appFrameId); @@ -243,40 +193,28 @@ namespace SwapChainTests presentOne.presentStartTime = 1000; presentOne.appFrameId = 1; - swapChain.pendingPresents.push_back(presentOne); swapChain.lastPresent = presentOne; swapChain.lastSimStartTime = 1000; - - Assert::AreEqual(size_t(1), swapChain.pendingPresents.size()); Assert::AreEqual(true, swapChain.lastPresent.has_value()); // Frame 2: Next frame received pmon::util::metrics::FrameData presentTwo; int frame2 = 2000; - swapChain.pendingPresents.push_back(presentTwo); swapChain.lastPresent = presentTwo; swapChain.lastSimStartTime = 2000; - Assert::AreEqual(size_t(2), swapChain.pendingPresents.size()); // Frame 2 displayed: Update display state swapChain.lastDisplayedSimStartTime = 2000; swapChain.lastDisplayedScreenTime = 2016; // +16ms latency swapChain.lastDisplayedAppScreenTime = 2016; - // Clear pending presents (they've been processed) - swapChain.pendingPresents.clear(); - - Assert::AreEqual(size_t(0), swapChain.pendingPresents.size()); Assert::AreEqual(uint64_t(2016), swapChain.lastDisplayedScreenTime); // Frame 3 pmon::util::metrics::FrameData presentThree; - swapChain.pendingPresents.push_back(presentThree); swapChain.lastPresent = presentThree; swapChain.lastReceivedNotDisplayedAllInputTime = 2990; - - Assert::AreEqual(size_t(1), swapChain.pendingPresents.size()); Assert::AreEqual(uint64_t(2990), swapChain.lastReceivedNotDisplayedAllInputTime); } @@ -288,8 +226,6 @@ namespace SwapChainTests // Set state in core1 swapChainOne.lastSimStartTime = 1234; - swapChainOne.pendingPresents.push_back(presents[0]); - swapChainOne.pendingPresents.push_back(presents[1]); swapChainOne.lastPresent = presents[1]; swapChainOne.accumulatedInput2FrameStartTime = 16.7; @@ -300,18 +236,15 @@ namespace SwapChainTests // Verify core2 has same values Assert::AreEqual(uint64_t(1234), swapChainTwo.lastSimStartTime); - Assert::AreEqual(size_t(2), swapChainTwo.pendingPresents.size()); //Assert::AreEqual(presents[1], *swapChainTwo.lastPresent); Assert::AreEqual(16.7, swapChainTwo.accumulatedInput2FrameStartTime, 0.001); // Modify SwapChainTwo swapChainTwo.lastSimStartTime = 5678; - swapChainTwo.pendingPresents.push_back(presents[2]); swapChainTwo.lastPresent = presents[2]; // Verify core1 is unchanged Assert::AreEqual(uint64_t(1234), swapChainOne.lastSimStartTime); - Assert::AreEqual(size_t(2), swapChainOne.pendingPresents.size()); //Assert::AreEqual(presents[1], *swapChainOne.lastPresent); } }; diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index cd622fd8..d11afa67 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -381,21 +381,6 @@ namespace MetricsCoreTests Assert::IsFalse(swapChain.lastAppPresent.has_value()); } - TEST_METHOD(PendingPresents_CanStoreMultiplePendingPresents) - { - SwapChainCoreState swapChain; - - FrameData p1{}; - FrameData p2{}; - FrameData p3{}; - - swapChain.pendingPresents.push_back(p1); - swapChain.pendingPresents.push_back(p2); - swapChain.pendingPresents.push_back(p3); - - Assert::AreEqual(size_t(3), swapChain.pendingPresents.size()); - } - TEST_METHOD(LastPresent_CanBeAssigned) { SwapChainCoreState swapChain; From 89bd4ec03a3f3712ee146d0f4db0cc58678e1553 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Fri, 2 Jan 2026 14:45:07 -0800 Subject: [PATCH 098/205] Updated swap chain test; removed unnecessary getters and setters --- .../CommonUtilities/mc/MetricsCalculator.cpp | 66 +-- .../CommonUtilities/mc/MetricsTypes.h | 54 -- .../CommonUtilities/mc/SwapChainState.cpp | 46 +- .../CommonUtilities/mc/UnifiedSwapChain.cpp | 4 +- .../PresentMonAPI2Tests.vcxproj | 1 - .../PresentMonAPI2Tests.vcxproj.filters | 3 - IntelPresentMon/UnitTests/MetricsCore.cpp | 516 +++++++++--------- .../SwapChainTests.cpp | 9 +- IntelPresentMon/UnitTests/UnitTests.vcxproj | 1 + .../UnitTests/UnitTests.vcxproj.filters | 1 + 10 files changed, 322 insertions(+), 379 deletions(-) rename IntelPresentMon/{PresentMonAPI2Tests => UnitTests}/SwapChainTests.cpp (99%) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index ca59355e..f0d51199 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -20,7 +20,7 @@ namespace pmon::util::metrics { if (isDisplayed) { out.msUntilDisplayed = qpc.DeltaUnsignedMilliSeconds( - present.getPresentStartTime(), + present.presentStartTime, screenTime); } else { @@ -70,8 +70,8 @@ namespace pmon::util::metrics bool isDisplayed, FrameMetrics& out) { - if (isDisplayed && present.getFlipDelay() != 0) { - out.msFlipDelay = qpc.DurationMilliSeconds(present.getFlipDelay()); + if (isDisplayed && present.flipDelay != 0) { + out.msFlipDelay = qpc.DurationMilliSeconds(present.flipDelay); } else { out.msFlipDelay = std::nullopt; @@ -101,8 +101,8 @@ namespace pmon::util::metrics uint64_t screenTime, FrameMetrics& out) { - if (isDisplayed && present.getReadyTime() != 0) { - out.msReadyTimeToDisplayLatency = qpc.DeltaUnsignedMilliSeconds(present.getReadyTime(), screenTime); + if (isDisplayed && present.readyTime != 0) { + out.msReadyTimeToDisplayLatency = qpc.DeltaUnsignedMilliSeconds(present.readyTime, screenTime); } else { out.msReadyTimeToDisplayLatency = std::nullopt; @@ -283,7 +283,7 @@ namespace pmon::util::metrics bool isFirstProviderSimTime = chain.animationErrorSource == AnimationErrorSource::CpuStart && - (present.getAppSimStartTime() != 0 || present.getPclSimStartTime() != 0); + (present.appSimStartTime != 0 || present.pclSimStartTime != 0); if (isFirstProviderSimTime) { // Seed only: no animation time yet. UpdateAfterPresent will flip us // into AppProvider/PCL and latch firstAppSimStartTime. @@ -649,10 +649,10 @@ namespace pmon::util::metrics DisplayIndexing result{}; // Get display count - auto displayCount = present.getDisplayedCount(); // ConsoleAdapter/PresentSnapshot method + auto displayCount = present.displayed.size(); // ConsoleAdapter/PresentSnapshot method // Check if displayed - bool displayed = present.getFinalState() == PresentResult::Presented && displayCount > 0; + bool displayed = present.finalState == PresentResult::Presented && displayCount > 0; // hasNextDisplayed result.hasNextDisplayed = (nextDisplayed != nullptr); @@ -683,7 +683,7 @@ namespace pmon::util::metrics result.appIndex = std::numeric_limits::max(); if (displayCount > 0) { for (size_t i = result.startIndex; i < displayCount; ++i) { - auto frameType = present.getDisplayedFrameType(i); + auto frameType = present.displayed[i].first; if (frameType == FrameType::NotSet || frameType == FrameType::Application) { result.appIndex = i; break; @@ -705,8 +705,8 @@ namespace pmon::util::metrics { std::vector results; - const auto displayCount = present.getDisplayedCount(); - const bool isDisplayed = present.getFinalState() == PresentResult::Presented && displayCount > 0; + const auto displayCount = present.displayed.size(); + const bool isDisplayed = present.finalState == PresentResult::Presented && displayCount > 0; // Case 1: not displayed, return single not-displayed metrics if (!isDisplayed || displayCount == 0) { @@ -721,7 +721,7 @@ namespace pmon::util::metrics const bool isAppFrame = (displayIndex == appIndex); const FrameType frameType = (displayCount > 0) - ? present.getDisplayedFrameType(displayIndex) + ? present.displayed[displayIndex].first : FrameType::NotSet; auto metrics = ComputeFrameMetrics( @@ -747,7 +747,7 @@ namespace pmon::util::metrics // Emit exactly one row per present (legacy V1 behavior). if (version == MetricsVersion::V1) { const size_t displayIndex = 0; - uint64_t screenTime = present.getDisplayedScreenTime(displayIndex); + uint64_t screenTime = present.displayed[displayIndex].second; uint64_t nextScreenTime = 0; AdjustScreenTimeForCollapsedPresentNV( @@ -765,7 +765,7 @@ namespace pmon::util::metrics const auto indexing = DisplayIndexing::Calculate(present, nullptr); const bool isAppFrame = (displayIndex == indexing.appIndex); const bool isDisplayedInstance = isDisplayed && screenTime != 0; - const FrameType frameType = isDisplayedInstance ? present.getDisplayedFrameType(displayIndex) : FrameType::NotSet; + const FrameType frameType = isDisplayedInstance ? present.displayed[displayIndex].first : FrameType::NotSet; auto metrics = ComputeFrameMetrics( qpc, @@ -791,16 +791,16 @@ namespace pmon::util::metrics const bool shouldUpdateSwapChain = (nextDisplayed != nullptr); for (size_t displayIndex = indexing.startIndex; displayIndex < indexing.endIndex; ++displayIndex) { - uint64_t screenTime = present.getDisplayedScreenTime(displayIndex); + uint64_t screenTime = present.displayed[displayIndex].second; uint64_t nextScreenTime = 0; if (displayIndex + 1 < displayCount) { // Next display instance of the same present - nextScreenTime = present.getDisplayedScreenTime(displayIndex + 1); + nextScreenTime = present.displayed[displayIndex + 1].second; } - else if (nextDisplayed != nullptr && nextDisplayed->getDisplayedCount() > 0) { + else if (nextDisplayed != nullptr && !nextDisplayed->displayed.empty()) { // First display of the *next* presented frame - nextScreenTime = nextDisplayed->getDisplayedScreenTime(0); + nextScreenTime = nextDisplayed->displayed[0].second; } else { break; // No next screen time available @@ -810,7 +810,7 @@ namespace pmon::util::metrics const bool isAppFrame = (displayIndex == indexing.appIndex); const bool isDisplayedInstance = isDisplayed && screenTime != 0 && nextScreenTime != 0; - const FrameType frameType = isDisplayedInstance ? present.getDisplayedFrameType(displayIndex) : FrameType::NotSet; + const FrameType frameType = isDisplayedInstance ? present.displayed[displayIndex].first : FrameType::NotSet; auto metrics = ComputeFrameMetrics( qpc, @@ -850,19 +850,19 @@ namespace pmon::util::metrics // to the current present if (swapChain.lastPresent.has_value()) { out.msBetweenPresents = qpc.DeltaUnsignedMilliSeconds( - swapChain.lastPresent->getPresentStartTime(), - present.getPresentStartTime()); + swapChain.lastPresent->presentStartTime, + present.presentStartTime); } else { out.msBetweenPresents = 0.0; } - out.msInPresentApi = qpc.DurationMilliSeconds(present.getTimeInPresent()); + out.msInPresentApi = qpc.DurationMilliSeconds(present.timeInPresent); out.msUntilRenderStart = qpc.DeltaSignedMilliSeconds( - present.getPresentStartTime(), + present.presentStartTime, present.gpuStartTime); out.msUntilRenderComplete = qpc.DeltaSignedMilliSeconds( - present.getPresentStartTime(), - present.getReadyTime()); + present.presentStartTime, + present.readyTime); out.msGpuDuration = qpc.DurationMilliSeconds(present.gpuDuration); out.msVideoDuration = qpc.DurationMilliSeconds(present.gpuVideoDuration); out.msSinceInput = (present.inputTime == 0) @@ -1250,18 +1250,18 @@ namespace pmon::util::metrics uint64_t cpuStart = 0; if (chainState.lastAppPresent.has_value()) { const auto& lastAppPresent = chainState.lastAppPresent.value(); - if (lastAppPresent.getAppPropagatedPresentStartTime() != 0) { - cpuStart = lastAppPresent.getAppPropagatedPresentStartTime() + - lastAppPresent.getAppPropagatedTimeInPresent(); + if (lastAppPresent.appPropagatedPresentStartTime != 0) { + cpuStart = lastAppPresent.appPropagatedPresentStartTime + + lastAppPresent.appPropagatedTimeInPresent; } else { - cpuStart = lastAppPresent.getPresentStartTime() + - lastAppPresent.getTimeInPresent(); + cpuStart = lastAppPresent.presentStartTime + + lastAppPresent.timeInPresent; } } else { cpuStart = chainState.lastPresent.has_value() ? - chainState.lastPresent->getPresentStartTime() + chainState.lastPresent->getTimeInPresent() : 0; + chainState.lastPresent->presentStartTime + chainState.lastPresent->timeInPresent : 0; } return cpuStart; } @@ -1277,10 +1277,10 @@ namespace pmon::util::metrics simStartTime = CalculateCPUStart(chainState, present); } else if (source == AnimationErrorSource::AppProvider) { - simStartTime = present.getAppSimStartTime(); + simStartTime = present.appSimStartTime; } else if (source == AnimationErrorSource::PCLatency) { - simStartTime = present.getPclSimStartTime(); + simStartTime = present.pclSimStartTime; } return simStartTime; } diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h index 01a73246..26f94bcb 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h @@ -89,60 +89,6 @@ namespace pmon::util::metrics { uint32_t appFrameId = 0; uint32_t pclFrameId = 0; - // Setters for test setup - void setFinalState(PresentResult state) { finalState = state; } - - // Inline getters - same interface as ConsoleAdapter (zero cost) - uint64_t getPresentStartTime() const { return presentStartTime; } - uint64_t getReadyTime() const { return readyTime; } - uint64_t getTimeInPresent() const { return timeInPresent; } - uint64_t getGPUStartTime() const { return gpuStartTime; } - uint64_t getGPUDuration() const { return gpuDuration; } - uint64_t getGPUVideoDuration() const { return gpuVideoDuration; } - - // Propagated data - uint64_t getAppPropagatedPresentStartTime() const { return appPropagatedPresentStartTime; } - uint64_t getAppPropagatedTimeInPresent() const { return appPropagatedTimeInPresent; } - uint64_t getAppPropagatedGPUStartTime() const { return appPropagatedGPUStartTime; } - uint64_t getAppPropagatedReadyTime() const { return appPropagatedReadyTime; } - uint64_t getAppPropagatedGPUDuration() const { return appPropagatedGPUDuration; } - uint64_t getAppPropagatedGPUVideoDuration() const { return appPropagatedGPUVideoDuration; } - - // Instrumented data - uint64_t getAppSimStartTime() const { return appSimStartTime; } - uint64_t getAppSleepStartTime() const { return appSleepStartTime; } - uint64_t getAppSleepEndTime() const { return appSleepEndTime; } - uint64_t getAppRenderSubmitStartTime() const { return appRenderSubmitStartTime; } - uint64_t getAppRenderSubmitEndTime() const { return appRenderSubmitEndTime; } - uint64_t getAppPresentStartTime() const { return appPresentStartTime; } - uint64_t getAppPresentEndTime() const { return appPresentEndTime; } - std::pair getAppInputSample() const { return appInputSample; } - - // PC Latency - uint64_t getPclSimStartTime() const { return pclSimStartTime; } - uint64_t getPclInputPingTime() const { return pclInputPingTime; } - - // Input tracking - uint64_t getInputTime() const { return inputTime; } - uint64_t getMouseClickTime() const { return mouseClickTime; } - - // Display data - normalized access - size_t getDisplayedCount() const { return displayed.size(); } - FrameType getDisplayedFrameType(size_t idx) const { return displayed[idx].first; } - uint64_t getDisplayedScreenTime(size_t idx) const { return displayed[idx].second; } - - // Vendor-specific - uint64_t getFlipDelay() const { return flipDelay; } - uint32_t getFlipToken() const { return flipToken; } - - // Metadata - PresentResult getFinalState() const { return finalState; } - uint32_t getProcessId() const { return processId; } - uint32_t getThreadId() const { return threadId; } - uint64_t getSwapChainAddress() const { return swapChainAddress; } - uint32_t getFrameId() const { return frameId; } - uint32_t getAppFrameId() const { return appFrameId; } - // Factory Methods static FrameData CopyFrameData(const PmNsmPresentEvent& p); static FrameData CopyFrameData(const std::shared_ptr& p); diff --git a/IntelPresentMon/CommonUtilities/mc/SwapChainState.cpp b/IntelPresentMon/CommonUtilities/mc/SwapChainState.cpp index f4773f80..c1430903 100644 --- a/IntelPresentMon/CommonUtilities/mc/SwapChainState.cpp +++ b/IntelPresentMon/CommonUtilities/mc/SwapChainState.cpp @@ -7,51 +7,51 @@ namespace pmon::util::metrics { void SwapChainCoreState::UpdateAfterPresent(const FrameData& present) { - const auto finalState = present.getFinalState(); - const size_t displayCnt = present.getDisplayedCount(); + const auto finalState = present.finalState; + const size_t displayCnt = present.displayed.size(); if (finalState == PresentResult::Presented) { if (displayCnt > 0) { const size_t lastIdx = displayCnt - 1; - const auto lastType = present.getDisplayedFrameType(lastIdx); + const auto lastType = present.displayed[lastIdx].first; const bool lastIsAppFrm = (lastType == FrameType::NotSet || lastType == FrameType::Application); if (lastIsAppFrm) { - const uint64_t lastScreenTime = present.getDisplayedScreenTime(lastIdx); + const uint64_t lastScreenTime = present.displayed[lastIdx].second; if (animationErrorSource == AnimationErrorSource::AppProvider) { - lastDisplayedSimStartTime = present.getAppSimStartTime(); + lastDisplayedSimStartTime = present.appSimStartTime; if (firstAppSimStartTime == 0) { - firstAppSimStartTime = present.getAppSimStartTime(); + firstAppSimStartTime = present.appSimStartTime; } lastDisplayedAppScreenTime = lastScreenTime; } else if (animationErrorSource == AnimationErrorSource::PCLatency) { // In the case of PCLatency only set values if PCL sim start time is non-zero - if (present.getPclSimStartTime() != 0) { - lastDisplayedSimStartTime = present.getPclSimStartTime(); + if (present.pclSimStartTime != 0) { + lastDisplayedSimStartTime = present.pclSimStartTime; if (firstAppSimStartTime == 0) { - firstAppSimStartTime = present.getPclSimStartTime(); + firstAppSimStartTime = present.pclSimStartTime; } lastDisplayedAppScreenTime = lastScreenTime; } } else { // AnimationErrorSource::CpuStart // Check for PCL or App sim start and possibly change source. - if (present.getPclSimStartTime() != 0) { + if (present.pclSimStartTime != 0) { animationErrorSource = AnimationErrorSource::PCLatency; - lastDisplayedSimStartTime = present.getPclSimStartTime(); + lastDisplayedSimStartTime = present.pclSimStartTime; if (firstAppSimStartTime == 0) { - firstAppSimStartTime = present.getPclSimStartTime(); + firstAppSimStartTime = present.pclSimStartTime; } lastDisplayedAppScreenTime = lastScreenTime; } - else if (present.getAppSimStartTime() != 0) { + else if (present.appSimStartTime != 0) { animationErrorSource = AnimationErrorSource::AppProvider; - lastDisplayedSimStartTime = present.getAppSimStartTime(); + lastDisplayedSimStartTime = present.appSimStartTime; if (firstAppSimStartTime == 0) { - firstAppSimStartTime = present.getAppSimStartTime(); + firstAppSimStartTime = present.appSimStartTime; } lastDisplayedAppScreenTime = lastScreenTime; } @@ -60,7 +60,7 @@ namespace pmon::util::metrics { if (lastAppPresent.has_value()) { const FrameData& lastApp = lastAppPresent.value(); lastDisplayedSimStartTime = - lastApp.getPresentStartTime() + lastApp.getTimeInPresent(); + lastApp.presentStartTime + lastApp.timeInPresent; } lastDisplayedAppScreenTime = lastScreenTime; } @@ -71,8 +71,8 @@ namespace pmon::util::metrics { // Always track "last displayed" screen time + flip delay when presented. if (displayCnt > 0) { const size_t lastIdx = displayCnt - 1; - lastDisplayedScreenTime = present.getDisplayedScreenTime(lastIdx); - lastDisplayedFlipDelay = present.getFlipDelay(); + lastDisplayedScreenTime = present.displayed[lastIdx].second; + lastDisplayedFlipDelay = present.flipDelay; } else { lastDisplayedScreenTime = 0; @@ -83,7 +83,7 @@ namespace pmon::util::metrics { // Last app present selection (same logic as UpdateChain) if (displayCnt > 0) { const size_t lastIdx = displayCnt - 1; - const auto lastType = present.getDisplayedFrameType(lastIdx); + const auto lastType = present.displayed[lastIdx].first; if (lastType == FrameType::NotSet || lastType == FrameType::Application) { lastAppPresent = present; } @@ -94,11 +94,11 @@ namespace pmon::util::metrics { } // Last simulation start time: PCL wins over App if both exist. - if (present.getPclSimStartTime() != 0) { - lastSimStartTime = present.getPclSimStartTime(); + if (present.pclSimStartTime != 0) { + lastSimStartTime = present.pclSimStartTime; } - else if (present.getAppSimStartTime() != 0) { - lastSimStartTime = present.getAppSimStartTime(); + else if (present.appSimStartTime != 0) { + lastSimStartTime = present.appSimStartTime; } // Always advance lastPresent diff --git a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp index 48763bc4..f5d08adb 100644 --- a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp +++ b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp @@ -74,8 +74,8 @@ namespace pmon::util::metrics } const bool isDisplayed = - (present.getFinalState() == PresentResult::Presented) && - (present.getDisplayedCount() > 0); + (present.finalState == PresentResult::Presented) && + (!present.displayed.empty()); if (isDisplayed) { // 1) Finalize previously waiting displayed (if any), pointing at swapchain-owned next displayed. diff --git a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj index 1b7649c1..0f33d4de 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj +++ b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj @@ -100,7 +100,6 @@ - diff --git a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj.filters b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj.filters index 6bcf81a2..beac5d79 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj.filters +++ b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj.filters @@ -39,9 +39,6 @@ Source Files - - Source Files - diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index d11afa67..b993745e 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -477,7 +477,7 @@ namespace MetricsCoreTests { FrameData present{}; present.displayed.push_back({ FrameType::Application, 1000 }); - present.setFinalState(PresentResult::Presented); + present.finalState = PresentResult::Presented; auto result = DisplayIndexing::Calculate(present, nullptr); @@ -494,7 +494,7 @@ namespace MetricsCoreTests present.displayed.push_back({ FrameType::Application, 1000 }); present.displayed.push_back({ FrameType::Repeated, 2000 }); present.displayed.push_back({ FrameType::Repeated, 3000 }); - present.setFinalState(PresentResult::Presented); + present.finalState = PresentResult::Presented; auto result = DisplayIndexing::Calculate(present, nullptr); @@ -511,7 +511,7 @@ namespace MetricsCoreTests present.displayed.push_back({ FrameType::Application, 1000 }); present.displayed.push_back({ FrameType::Repeated, 2000 }); present.displayed.push_back({ FrameType::Repeated, 3000 }); - present.setFinalState(PresentResult::Presented); + present.finalState = PresentResult::Presented; FrameData next{}; next.displayed.push_back({ FrameType::Application, 4000 }); @@ -547,7 +547,7 @@ namespace MetricsCoreTests present.displayed.push_back({ FrameType::Repeated, 1000 }); present.displayed.push_back({ FrameType::Application, 2000 }); present.displayed.push_back({ FrameType::Repeated, 3000 }); - present.setFinalState(PresentResult::Presented); + present.finalState = PresentResult::Presented; auto result = DisplayIndexing::Calculate(present, nullptr); @@ -578,7 +578,7 @@ namespace MetricsCoreTests present.displayed.push_back({ FrameType::Repeated, 1000 }); present.displayed.push_back({ FrameType::Repeated, 2000 }); present.displayed.push_back({ FrameType::Repeated, 3000 }); - present.setFinalState(PresentResult::Presented); + present.finalState = PresentResult::Presented; auto result = DisplayIndexing::Calculate(present, nullptr); @@ -594,7 +594,7 @@ namespace MetricsCoreTests present.displayed.push_back({ FrameType::Application, 1000 }); present.displayed.push_back({ FrameType::Application, 2000 }); present.displayed.push_back({ FrameType::Repeated, 3000 }); - present.setFinalState(PresentResult::Presented); + present.finalState = PresentResult::Presented; auto result = DisplayIndexing::Calculate(present, nullptr); @@ -609,7 +609,7 @@ namespace MetricsCoreTests // Verify template works with FrameData FrameData present{}; present.displayed.push_back({ FrameType::Application, 1000 }); - present.setFinalState(PresentResult::Presented); + present.finalState = PresentResult::Presented; auto result = DisplayIndexing::Calculate(present, nullptr); @@ -1920,7 +1920,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'000'000; frame.timeInPresent = 10'000; frame.readyTime = 1'010'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; // No displayed entries auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); @@ -1937,18 +1937,18 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 2'000'000; // start frame.timeInPresent = 20'000; frame.readyTime = 2'050'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; // Single displayed; will be postponed unless nextDisplayed provided frame.displayed.push_back({ FrameType::Application, 2'500'000 }); FrameData next{}; // provide nextDisplayed to process postponed - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 3'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; - double expected = qpc.DeltaUnsignedMilliSeconds(frame.getPresentStartTime(), frame.getDisplayedScreenTime(0)); + double expected = qpc.DeltaUnsignedMilliSeconds(frame.presentStartTime, frame.displayed[0].second); Assert::AreEqual(expected, m.msUntilDisplayed, 0.0001); } TEST_METHOD(DisplayedGeneratedFrame_AlsoReturnsDelta) @@ -1960,18 +1960,18 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 5'000'000; frame.timeInPresent = 15'000; frame.readyTime = 5'030'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; // Displayed generated frame (e.g., Repeated/Composed/Desktop depending on enum) frame.displayed.push_back({ FrameType::Intel_XEFG, 5'100'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 6'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); const auto& m = results[0].metrics; - double expected = qpc.DeltaUnsignedMilliSeconds(frame.getPresentStartTime(), frame.getDisplayedScreenTime(0)); + double expected = qpc.DeltaUnsignedMilliSeconds(frame.presentStartTime, frame.displayed[0].second); Assert::AreEqual(expected, m.msUntilDisplayed, 0.0001); } }; @@ -1987,7 +1987,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'000'000; frame.timeInPresent = 10'000; frame.readyTime = 1'010'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); Assert::AreEqual(size_t(1), results.size()); @@ -2004,11 +2004,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 2'000'000; frame.timeInPresent = 20'000; frame.readyTime = 2'050'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 2'500'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 2'800'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -2028,13 +2028,13 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 3'000'000; frame.timeInPresent = 30'000; frame.readyTime = 3'050'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 3'100'000 }); frame.displayed.push_back({ FrameType::Repeated, 3'400'000 }); frame.displayed.push_back({ FrameType::Repeated, 3'700'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 4'000'000 }); auto results1 = ComputeMetricsForPresent(qpc, frame, nullptr, chain); @@ -2066,11 +2066,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 5'000'000; frame.timeInPresent = 50'000; frame.readyTime = 5'100'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 5'500'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 6'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -2090,11 +2090,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 5'000'000; frame.timeInPresent = 50'000; frame.readyTime = 5'100'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 5'500'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 6'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -2115,7 +2115,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 5'000'000; frame.timeInPresent = 50'000; frame.readyTime = 5'100'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); Assert::AreEqual(size_t(1), results.size()); @@ -2134,13 +2134,13 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 5'000'000; frame.timeInPresent = 50'000; frame.readyTime = 5'100'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 5'500'000 }); frame.displayed.push_back({ FrameType::Repeated, 5'800'000 }); frame.displayed.push_back({ FrameType::Repeated, 6'100'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 6'400'000 }); auto results1 = ComputeMetricsForPresent(qpc, frame, nullptr, chain); @@ -2173,7 +2173,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 70'000; frame.readyTime = 7'100'000; frame.flipDelay = 5'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); Assert::AreEqual(size_t(1), results.size()); @@ -2194,11 +2194,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 70'000; frame.readyTime = 7'100'000; frame.flipDelay = 100'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 7'500'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 8'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -2221,11 +2221,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 70'000; frame.readyTime = 7'100'000; frame.flipDelay = 0; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 7'500'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 8'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -2247,11 +2247,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 70'000; frame.readyTime = 7'100'000; frame.flipDelay = 50'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Repeated, 7'500'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 8'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -2277,7 +2277,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 9'000'000; frame.timeInPresent = 90'000; frame.readyTime = 9'100'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); Assert::AreEqual(size_t(1), results.size()); @@ -2295,11 +2295,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 9'000'000; frame.timeInPresent = 90'000; frame.readyTime = 9'100'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 9'500'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 10'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -2318,13 +2318,13 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 9'000'000; frame.timeInPresent = 90'000; frame.readyTime = 9'100'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 9'500'000 }); frame.displayed.push_back({ FrameType::Repeated, 9'800'000 }); frame.displayed.push_back({ FrameType::Repeated, 10'100'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 10'400'000 }); auto results1 = ComputeMetricsForPresent(qpc, frame, nullptr, chain); @@ -2346,11 +2346,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 9'000'000; frame.timeInPresent = 90'000; frame.readyTime = 9'100'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Repeated, 9'700'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 10'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -2379,7 +2379,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) first.timeInPresent = 50'000; first.readyTime = 4'100'000; first.flipDelay = 200'000; // 0.02ms at 10MHz - first.setFinalState(PresentResult::Presented); + first.finalState = PresentResult::Presented; // First's screen time is 5'500'000 first.displayed.push_back({ FrameType::Application, 5'500'000 }); @@ -2389,7 +2389,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) second.timeInPresent = 40'000; second.readyTime = 5'100'000; second.flipDelay = 100'000; // Original flip delay for second frame - second.setFinalState(PresentResult::Presented); + second.finalState = PresentResult::Presented; // Second's raw screen time is 5'000'000, which is EARLIER than first's (5'500'000) // This triggers NV2 adjustment second.displayed.push_back({ FrameType::Application, 5'000'000 }); @@ -2400,7 +2400,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) // Now process second frame (which should have been adjusted by NV2) FrameData third{}; - third.setFinalState(PresentResult::Presented); + third.finalState = PresentResult::Presented; third.displayed.push_back({ FrameType::Application, 6'000'000 }); auto resultsSecond = ComputeMetricsForPresent(qpc, second, &third, chain); @@ -2443,12 +2443,12 @@ TEST_CLASS(ComputeMetricsForPresentTests) current.timeInPresent = 50'000; current.readyTime = 4'100'000; current.flipDelay = 75'000; - current.setFinalState(PresentResult::Presented); + current.finalState = PresentResult::Presented; // Current screen time is LATER than lastDisplayedScreenTime, so no NV1 adjustment current.displayed.push_back({ FrameType::Application, 4'000'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 5'000'000 }); auto results = ComputeMetricsForPresent(qpc, current, &next, chain); @@ -2485,7 +2485,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) first.timeInPresent = 50'000; first.readyTime = 4'100'000; first.flipDelay = 100'000; - first.setFinalState(PresentResult::Presented); + first.finalState = PresentResult::Presented; // First screen time is 5'000'000 first.displayed.push_back({ FrameType::Application, 5'000'000 }); @@ -2495,7 +2495,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) second.timeInPresent = 40'000; second.readyTime = 5'100'000; second.flipDelay = 50'000; - second.setFinalState(PresentResult::Presented); + second.finalState = PresentResult::Presented; // Second screen time is equal to first (5'000'000), so NV2 should NOT adjust second.displayed.push_back({ FrameType::Application, 5'000'000 }); @@ -2503,7 +2503,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) Assert::AreEqual(size_t(1), resultsFirst.size()); FrameData third{}; - third.setFinalState(PresentResult::Presented); + third.finalState = PresentResult::Presented; third.displayed.push_back({ FrameType::Application, 6'000'000 }); auto resultsSecond = ComputeMetricsForPresent(qpc, second, &third, chain); @@ -2543,7 +2543,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) current.timeInPresent = 50'000; current.readyTime = 4'100'000; current.flipDelay = 100'000; - current.setFinalState(PresentResult::Presented); + current.finalState = PresentResult::Presented; current.displayed.push_back({ FrameType::Application, 5'000'000 }); auto results = ComputeMetricsForPresent(qpc, current, nullptr, chain, MetricsVersion::V1); @@ -2587,11 +2587,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'000'000; frame.timeInPresent = 50'000; frame.readyTime = 1'100'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 2'000'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 2'500'000 }); // Set up chain with prior app present to establish cpuStart @@ -2623,11 +2623,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'000'000; frame.timeInPresent = 50'000; frame.readyTime = 1'100'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 2'000'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 2'500'000 }); FrameData priorApp{}; @@ -2656,7 +2656,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'000'000; frame.timeInPresent = 50'000; frame.readyTime = 1'100'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; // No displayed entries auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); @@ -2680,11 +2680,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'000'000; frame.timeInPresent = 50'000; frame.readyTime = 1'100'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 3'000'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 3'500'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -2715,11 +2715,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'000'000; frame.timeInPresent = 50'000; frame.readyTime = 1'500'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 2'000'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 2'500'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -2745,11 +2745,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'000'000; frame.timeInPresent = 50'000; frame.readyTime = 2'000'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 2'000'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 2'500'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -2772,7 +2772,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'000'000; frame.timeInPresent = 50'000; frame.readyTime = 1'500'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; // No displayed entries auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); @@ -2795,11 +2795,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'000'000; frame.timeInPresent = 50'000; frame.readyTime = 70'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 2'000'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 2'500'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -2836,13 +2836,13 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'000'000; frame.timeInPresent = 50'000; frame.readyTime = 1'100'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 2'000'000 }); frame.displayed.push_back({ FrameType::Repeated, 2'100'000 }); frame.displayed.push_back({ FrameType::Repeated, 2'200'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 2'500'000 }); FrameData priorApp{}; @@ -2891,13 +2891,13 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'000'000; frame.timeInPresent = 50'000; frame.readyTime = 1'500'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 2'000'000 }); frame.displayed.push_back({ FrameType::Intel_XEFG, 2'100'000 }); frame.displayed.push_back({ FrameType::Intel_XEFG, 2'200'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 2'500'000 }); FrameData priorApp{}; @@ -2949,7 +2949,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 50'000; frame.readyTime = 1'100'000; frame.flipDelay = 50'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; // Raw screen time is 4'000'000, greater than next screen time frame.displayed.push_back({ FrameType::Application, 4'000'000 }); @@ -2958,14 +2958,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) next1.timeInPresent = 50'000; next1.readyTime = 2'100'000; next1.flipDelay = 30'000; - next1.setFinalState(PresentResult::Presented); + next1.finalState = PresentResult::Presented; next1.displayed.push_back({ FrameType::Application, 3'000'000 }); FrameData next2{}; next2.presentStartTime = 3'000'000; next2.timeInPresent = 50'000; next2.readyTime = 3'100'000; - next2.setFinalState(PresentResult::Presented); + next2.finalState = PresentResult::Presented; next2.displayed.push_back({ FrameType::Application, 5'000'000 }); // Set up chain with prior app present to establish cpuStart @@ -3013,7 +3013,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 50'000; frame.readyTime = 1'100'000; frame.flipDelay = 50'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; // Raw screen time is 4'000'000, greater than next screen time frame.displayed.push_back({ FrameType::Application, 4'000'000 }); @@ -3022,14 +3022,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) next1.timeInPresent = 50'000; next1.readyTime = 2'100'000; next1.flipDelay = 30'000; - next1.setFinalState(PresentResult::Presented); + next1.finalState = PresentResult::Presented; next1.displayed.push_back({ FrameType::Application, 3'000'000 }); FrameData next2{}; next2.presentStartTime = 3'000'000; next2.timeInPresent = 50'000; next2.readyTime = 3'100'000; - next2.setFinalState(PresentResult::Presented); + next2.finalState = PresentResult::Presented; next2.displayed.push_back({ FrameType::Application, 5'000'000 }); // Set up chain with prior app present to establish cpuStart @@ -3073,11 +3073,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'000'000; frame.timeInPresent = 50'000; frame.readyTime = 1'100'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 2'000'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 2'500'000 }); FrameData priorApp{}; @@ -3109,11 +3109,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'000'000; frame.timeInPresent = 50'000; frame.readyTime = 3'000'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 2'000'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 2'500'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -3140,11 +3140,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'000'000; frame.timeInPresent = 50'000; frame.readyTime = 1'100'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 2'000'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 2'500'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -3174,11 +3174,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'000'000; frame.timeInPresent = 50'000; frame.readyTime = 1'100'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 2'000'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 2'500'000 }); FrameData priorApp{}; @@ -3214,7 +3214,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorFrame.presentStartTime = 800'000; priorFrame.timeInPresent = 200'000; priorFrame.readyTime = 1'100'000; - priorFrame.setFinalState(PresentResult::Presented); + priorFrame.finalState = PresentResult::Presented; priorFrame.displayed.push_back({ FrameType::Application, 1'200'000 }); chain.lastAppPresent = priorFrame; @@ -3223,11 +3223,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'100'000; frame.timeInPresent = 100'000; frame.readyTime = 1'200'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'300'000 }); FrameData next{}; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 1'400'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -3254,7 +3254,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.readyTime = 1'200'000; priorApp.appPropagatedPresentStartTime = 800'000; priorApp.appPropagatedTimeInPresent = 200'000; - priorApp.setFinalState(PresentResult::Presented); + priorApp.finalState = PresentResult::Presented; priorApp.displayed.push_back({ FrameType::Application, 1'300'000 }); chain.lastAppPresent = priorApp; @@ -3264,7 +3264,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'500'000; frame.timeInPresent = 100'000; frame.readyTime = 1'600'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'700'000 }); // Next displayed frame (required to process current frame's display) @@ -3272,7 +3272,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 2'000'000; next.timeInPresent = 80'000; next.readyTime = 2'100'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 2'200'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -3300,7 +3300,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 5'000'000; frame.timeInPresent = 100'000; frame.readyTime = 5'200'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 5'500'000 }); // Next displayed frame (required to process current frame's display) @@ -3308,7 +3308,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 6'000'000; next.timeInPresent = 80'000; next.readyTime = 6'100'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 6'300'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -3335,7 +3335,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorFrame.presentStartTime = 800'000; priorFrame.timeInPresent = 200'000; priorFrame.readyTime = 1'100'000; - priorFrame.setFinalState(PresentResult::Presented); + priorFrame.finalState = PresentResult::Presented; priorFrame.displayed.push_back({ FrameType::Application, 1'200'000 }); chain.lastAppPresent = priorFrame; @@ -3345,7 +3345,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'000'000; // Same as cpuStart frame.timeInPresent = 0; // Zero present duration frame.readyTime = 1'000'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'100'000 }); // Next displayed frame (required to process current frame's display) @@ -3353,7 +3353,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'500'000; next.timeInPresent = 50'000; next.readyTime = 1'600'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -3378,7 +3378,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorFrame.presentStartTime = 800'000; priorFrame.timeInPresent = 100'000; priorFrame.readyTime = 1'100'000; - priorFrame.setFinalState(PresentResult::Presented); + priorFrame.finalState = PresentResult::Presented; priorFrame.displayed.push_back({ FrameType::Application, 1'200'000 }); chain.lastAppPresent = priorFrame; @@ -3388,7 +3388,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'100'000; frame.timeInPresent = 200'000; // 20 ms frame.readyTime = 1'300'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'400'000 }); // Next displayed frame (required to process current frame's display) @@ -3396,7 +3396,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'800'000; next.timeInPresent = 50'000; next.readyTime = 1'900'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 2'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -3420,7 +3420,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorFrame.presentStartTime = 800'000; priorFrame.timeInPresent = 100'000; priorFrame.readyTime = 1'100'000; - priorFrame.setFinalState(PresentResult::Presented); + priorFrame.finalState = PresentResult::Presented; priorFrame.displayed.push_back({ FrameType::Application, 1'200'000 }); chain.lastAppPresent = priorFrame; @@ -3431,7 +3431,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 200'000; // Regular time (not used when propagated available) frame.readyTime = 1'300'000; frame.appPropagatedTimeInPresent = 150'000; // 15 ms (propagated value) - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'400'000 }); // Next displayed frame (required to process current frame's display) @@ -3439,7 +3439,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'800'000; next.timeInPresent = 50'000; next.readyTime = 1'900'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 2'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -3464,7 +3464,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorFrame.presentStartTime = 800'000; priorFrame.timeInPresent = 100'000; priorFrame.readyTime = 1'100'000; - priorFrame.setFinalState(PresentResult::Presented); + priorFrame.finalState = PresentResult::Presented; priorFrame.displayed.push_back({ FrameType::Application, 1'200'000 }); chain.lastAppPresent = priorFrame; @@ -3474,7 +3474,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'100'000; frame.timeInPresent = 0; // Zero CPU wait time frame.readyTime = 1'100'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'200'000 }); // Next displayed frame (required to process current frame's display) @@ -3482,7 +3482,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'600'000; next.timeInPresent = 50'000; next.readyTime = 1'700'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 1'800'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -3512,7 +3512,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.presentStartTime = 800'000; priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; - priorApp.setFinalState(PresentResult::Presented); + priorApp.finalState = PresentResult::Presented; priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -3524,7 +3524,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.readyTime = 1'300'000; frame.gpuStartTime = 1'050'000; frame.gpuDuration = 200'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'400'000 }); // Next displayed frame (required to process current frame's display) @@ -3532,7 +3532,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'600'000; next.timeInPresent = 50'000; next.readyTime = 1'700'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 1'800'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -3555,7 +3555,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.presentStartTime = 800'000; priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; - priorApp.setFinalState(PresentResult::Presented); + priorApp.finalState = PresentResult::Presented; priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -3569,7 +3569,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.gpuDuration = 200'000; frame.appPropagatedGPUStartTime = 1'080'000; frame.appPropagatedGPUDuration = 200'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'400'000 }); // Next displayed frame (required to process current frame's display) @@ -3577,7 +3577,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'600'000; next.timeInPresent = 50'000; next.readyTime = 1'700'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 1'800'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -3601,7 +3601,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.presentStartTime = 1'500'000; priorApp.timeInPresent = 500'000; // cpuStart = 2'000'000 priorApp.readyTime = 2'000'000; - priorApp.setFinalState(PresentResult::Presented); + priorApp.finalState = PresentResult::Presented; priorApp.displayed.push_back({ FrameType::Application, 2'100'000 }); chain.lastAppPresent = priorApp; @@ -3613,7 +3613,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.readyTime = 2'300'000; frame.gpuStartTime = 1'900'000; // Earlier than cpuStart frame.gpuDuration = 300'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 2'400'000 }); // Next displayed frame (required to process current frame's display) @@ -3621,7 +3621,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 2'600'000; next.timeInPresent = 50'000; next.readyTime = 2'700'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 2'800'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -3644,7 +3644,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.presentStartTime = 800'000; priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; - priorApp.setFinalState(PresentResult::Presented); + priorApp.finalState = PresentResult::Presented; priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -3656,7 +3656,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.readyTime = 1'200'000; frame.gpuStartTime = 1'050'000; frame.gpuDuration = 500'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'300'000 }); // Next displayed frame (required to process current frame's display) @@ -3664,7 +3664,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'500'000; next.timeInPresent = 50'000; next.readyTime = 1'600'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -3688,7 +3688,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.presentStartTime = 800'000; priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; - priorApp.setFinalState(PresentResult::Presented); + priorApp.finalState = PresentResult::Presented; priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -3700,7 +3700,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.readyTime = 1'200'000; frame.gpuStartTime = 1'050'000; frame.gpuDuration = 0; // No GPU work - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'300'000 }); // Next displayed frame (required to process current frame's display) @@ -3708,7 +3708,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'500'000; next.timeInPresent = 50'000; next.readyTime = 1'600'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -3731,7 +3731,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.presentStartTime = 800'000; priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; - priorApp.setFinalState(PresentResult::Presented); + priorApp.finalState = PresentResult::Presented; priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -3745,7 +3745,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.gpuDuration = 500'000; // Not used when propagated available frame.appPropagatedGPUStartTime = 1'050'000; frame.appPropagatedGPUDuration = 450'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'300'000 }); // Next displayed frame (required to process current frame's display) @@ -3753,7 +3753,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'500'000; next.timeInPresent = 50'000; next.readyTime = 1'600'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -3778,7 +3778,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.presentStartTime = 800'000; priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; - priorApp.setFinalState(PresentResult::Presented); + priorApp.finalState = PresentResult::Presented; priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -3790,7 +3790,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.readyTime = 1'600'000; frame.gpuStartTime = 1'000'000; frame.gpuDuration = 500'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'700'000 }); // Next displayed frame (required to process current frame's display) @@ -3798,7 +3798,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'900'000; next.timeInPresent = 50'000; next.readyTime = 2'000'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 2'100'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -3827,7 +3827,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.presentStartTime = 800'000; priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; - priorApp.setFinalState(PresentResult::Presented); + priorApp.finalState = PresentResult::Presented; priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -3839,7 +3839,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.readyTime = 1'600'000; frame.gpuStartTime = 1'000'000; frame.gpuDuration = 600'000; // Equal to total - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'700'000 }); // Next displayed frame (required to process current frame's display) @@ -3847,7 +3847,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'900'000; next.timeInPresent = 50'000; next.readyTime = 2'000'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 2'100'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -3873,7 +3873,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.presentStartTime = 800'000; priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; - priorApp.setFinalState(PresentResult::Presented); + priorApp.finalState = PresentResult::Presented; priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -3885,7 +3885,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.readyTime = 1'600'000; frame.gpuStartTime = 1'000'000; frame.gpuDuration = 700'000; // Greater than total (impossible) - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'700'000 }); // Next displayed frame (required to process current frame's display) @@ -3893,7 +3893,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'900'000; next.timeInPresent = 50'000; next.readyTime = 2'000'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 2'100'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -3919,7 +3919,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.presentStartTime = 800'000; priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; - priorApp.setFinalState(PresentResult::Presented); + priorApp.finalState = PresentResult::Presented; priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -3934,7 +3934,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.appPropagatedGPUStartTime = 1'000'000; frame.appPropagatedReadyTime = 1'550'000; frame.appPropagatedGPUDuration = 450'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'700'000 }); // Next displayed frame (required to process current frame's display) @@ -3942,7 +3942,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'900'000; next.timeInPresent = 50'000; next.readyTime = 2'000'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 2'100'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -3974,7 +3974,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.presentStartTime = 800'000; priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; - priorApp.setFinalState(PresentResult::Presented); + priorApp.finalState = PresentResult::Presented; priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -3987,7 +3987,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.gpuStartTime = 1'050'000; frame.gpuDuration = 500'000; frame.gpuVideoDuration = 200'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'300'000 }); // Next displayed frame (required to process current frame's display) @@ -3995,7 +3995,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'500'000; next.timeInPresent = 50'000; next.readyTime = 1'600'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -4019,7 +4019,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.presentStartTime = 800'000; priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; - priorApp.setFinalState(PresentResult::Presented); + priorApp.finalState = PresentResult::Presented; priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -4032,7 +4032,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.gpuStartTime = 1'050'000; frame.gpuDuration = 500'000; frame.gpuVideoDuration = 0; // No video work - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'300'000 }); // Next displayed frame (required to process current frame's display) @@ -4040,7 +4040,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'500'000; next.timeInPresent = 50'000; next.readyTime = 1'600'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -4062,7 +4062,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.presentStartTime = 800'000; priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; - priorApp.setFinalState(PresentResult::Presented); + priorApp.finalState = PresentResult::Presented; priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -4078,7 +4078,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.appPropagatedGPUStartTime = 1'050'000; frame.appPropagatedGPUDuration = 450'000; frame.appPropagatedGPUVideoDuration = 180'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'300'000 }); // Next displayed frame (required to process current frame's display) @@ -4086,7 +4086,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'500'000; next.timeInPresent = 50'000; next.readyTime = 1'600'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -4111,7 +4111,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.presentStartTime = 800'000; priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; - priorApp.setFinalState(PresentResult::Presented); + priorApp.finalState = PresentResult::Presented; priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -4124,7 +4124,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.gpuStartTime = 1'050'000; frame.gpuDuration = 500'000; frame.gpuVideoDuration = 200'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'300'000 }); // Next displayed frame (required to process current frame's display) @@ -4132,7 +4132,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'500'000; next.timeInPresent = 50'000; next.readyTime = 1'600'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -4159,7 +4159,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.presentStartTime = 800'000; priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; - priorApp.setFinalState(PresentResult::Presented); + priorApp.finalState = PresentResult::Presented; priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -4172,7 +4172,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.gpuStartTime = 1'050'000; frame.gpuDuration = 300'000; // 30 ms frame.gpuVideoDuration = 500'000; // 50 ms (larger than gpuDuration) - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'300'000 }); // Next displayed frame (required to process current frame's display) @@ -4180,7 +4180,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'500'000; next.timeInPresent = 50'000; next.readyTime = 1'600'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -4215,7 +4215,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'100'000; frame.timeInPresent = 100'000; frame.readyTime = 1'200'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'300'000 }); // Next displayed frame (required to process current frame's display) @@ -4223,7 +4223,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'500'000; next.timeInPresent = 50'000; next.readyTime = 1'600'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -4261,7 +4261,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.readyTime = 1'200'000; frame.gpuStartTime = 1'150'000; frame.gpuDuration = 200'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Repeated, 1'300'000 }); // Next displayed frame (required to process current frame's display) @@ -4269,7 +4269,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'500'000; next.timeInPresent = 50'000; next.readyTime = 1'600'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -4294,7 +4294,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.presentStartTime = 800'000; priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; - priorApp.setFinalState(PresentResult::Presented); + priorApp.finalState = PresentResult::Presented; priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -4306,7 +4306,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.readyTime = 1'200'000; frame.gpuStartTime = 1'150'000; frame.gpuDuration = 200'000; - frame.setFinalState(PresentResult::Discarded); + frame.finalState = PresentResult::Discarded; // No displayed entries auto results = ComputeMetricsForPresent(qpc, frame, nullptr, chain); @@ -4332,7 +4332,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) lastApp.presentStartTime = 800'000; lastApp.timeInPresent = 200'000; lastApp.readyTime = 1'000'000; - lastApp.setFinalState(PresentResult::Presented); + lastApp.finalState = PresentResult::Presented; lastApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = lastApp; @@ -4342,7 +4342,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'200'000; frame.timeInPresent = 100'000; frame.readyTime = 1'300'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'400'000 }); // Next displayed frame (required to process current frame's display) @@ -4350,7 +4350,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'600'000; next.timeInPresent = 50'000; next.readyTime = 1'700'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 1'800'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -4372,7 +4372,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) lastPresent.presentStartTime = 800'000; lastPresent.timeInPresent = 200'000; lastPresent.readyTime = 1'000'000; - lastPresent.setFinalState(PresentResult::Presented); + lastPresent.finalState = PresentResult::Presented; lastPresent.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastPresent = lastPresent; @@ -4383,7 +4383,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'200'000; frame.timeInPresent = 100'000; frame.readyTime = 1'300'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'400'000 }); // Next displayed frame (required to process current frame's display) @@ -4391,7 +4391,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'600'000; next.timeInPresent = 50'000; next.readyTime = 1'700'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 1'800'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -4414,7 +4414,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 5'000'000; frame.timeInPresent = 100'000; frame.readyTime = 5'200'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 5'500'000 }); // Next displayed frame (required to process current frame's display) @@ -4422,7 +4422,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 6'000'000; next.timeInPresent = 50'000; next.readyTime = 6'100'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 6'300'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -4445,7 +4445,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 100'000; frame.readyTime = 5'200'000; frame.flipDelay = 777; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 5'500'000 }); // Next displayed frame (required to process current frame's display) @@ -4453,7 +4453,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 6'000'000; next.timeInPresent = 50'000; next.readyTime = 6'100'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 6'300'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -4483,7 +4483,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.presentStartTime = 900'000'000; priorApp.timeInPresent = 100'000'000; priorApp.readyTime = 1'000'000'000; - priorApp.setFinalState(PresentResult::Presented); + priorApp.finalState = PresentResult::Presented; priorApp.displayed.push_back({ FrameType::Application, 1'100'000'000 }); chain.lastAppPresent = priorApp; @@ -4493,7 +4493,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.presentStartTime = 1'100'000'000; frame.timeInPresent = 100'000'000; frame.readyTime = 1'200'000'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'300'000'000 }); // Next displayed frame (required to process current frame's display) @@ -4501,7 +4501,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'600'000'000; next.timeInPresent = 50'000'000; next.readyTime = 1'700'000'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 1'800'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -4527,7 +4527,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.presentStartTime = 800'000; priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; - priorApp.setFinalState(PresentResult::Presented); + priorApp.finalState = PresentResult::Presented; priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -4539,7 +4539,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.readyTime = 1'200'000; frame.gpuStartTime = 1'000'001; // Only 1 tick later than cpuStart frame.gpuDuration = 200'000; - frame.setFinalState(PresentResult::Presented); + frame.finalState = PresentResult::Presented; frame.displayed.push_back({ FrameType::Application, 1'300'000 }); // Next displayed frame (required to process current frame's display) @@ -4547,7 +4547,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.presentStartTime = 1'500'000; next.timeInPresent = 50'000; next.readyTime = 1'600'000; - next.setFinalState(PresentResult::Presented); + next.finalState = PresentResult::Presented; next.displayed.push_back({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); @@ -4575,14 +4575,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frameA.gpuStartTime = 1'050'000; frameA.gpuDuration = 400'000; frameA.gpuVideoDuration = 0; // No video - frameA.setFinalState(PresentResult::Presented); + frameA.finalState = PresentResult::Presented; frameA.displayed.push_back({ FrameType::Application, 1'500'000 }); FrameData nextA{}; nextA.presentStartTime = 2'000'000; nextA.timeInPresent = 50'000; nextA.readyTime = 2'100'000; - nextA.setFinalState(PresentResult::Presented); + nextA.finalState = PresentResult::Presented; nextA.displayed.push_back({ FrameType::Application, 2'200'000 }); auto resultsA = ComputeMetricsForPresent(qpc, frameA, &nextA, chain); @@ -4597,14 +4597,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frameB.gpuStartTime = 2'150'000; frameB.gpuDuration = 400'000; frameB.gpuVideoDuration = 300'000; // 30 ms of video - frameB.setFinalState(PresentResult::Presented); + frameB.finalState = PresentResult::Presented; frameB.displayed.push_back({ FrameType::Application, 2'600'000 }); FrameData nextB{}; nextB.presentStartTime = 3'000'000; nextB.timeInPresent = 50'000; nextB.readyTime = 3'100'000; - nextB.setFinalState(PresentResult::Presented); + nextB.finalState = PresentResult::Presented; nextB.displayed.push_back({ FrameType::Application, 3'200'000 }); auto resultsB = ComputeMetricsForPresent(qpc, frameB, &nextB, chain); @@ -4847,14 +4847,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.readyTime = 1'500'000; frame1.appSimStartTime = 100; frame1.pclSimStartTime = 0; - frame1.setFinalState(PresentResult::Presented); + frame1.finalState = PresentResult::Presented; frame1.displayed.push_back({ FrameType::Application, 1'000'000 }); FrameData next1{}; next1.presentStartTime = 2'000'000; next1.timeInPresent = 400; next1.readyTime = 2'500'000; - next1.setFinalState(PresentResult::Presented); + next1.finalState = PresentResult::Presented; next1.displayed.push_back({ FrameType::Application, 2'000'000 }); auto metrics1 = ComputeMetricsForPresent(qpc, frame1, &next1, state); @@ -4882,14 +4882,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame2.readyTime = 3'500'000; frame2.appSimStartTime = 150; frame2.pclSimStartTime = 0; - frame2.setFinalState(PresentResult::Presented); + frame2.finalState = PresentResult::Presented; frame2.displayed.push_back({ FrameType::Application, 3'000'000 }); FrameData next2{}; next2.presentStartTime = 4'000'000; next2.timeInPresent = 400; next2.readyTime = 4'500'000; - next2.setFinalState(PresentResult::Presented); + next2.finalState = PresentResult::Presented; next2.displayed.push_back({ FrameType::Application, 4'000'000 }); auto metrics2 = ComputeMetricsForPresent(qpc, frame2, &next2, state); @@ -4919,14 +4919,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame3.readyTime = 5'500'000; frame3.appSimStartTime = 250; frame3.pclSimStartTime = 0; - frame3.setFinalState(PresentResult::Presented); + frame3.finalState = PresentResult::Presented; frame3.displayed.push_back({ FrameType::Application, 5'000'000 }); FrameData next3{}; next3.presentStartTime = 6'000'000; next3.timeInPresent = 400; next3.readyTime = 6'500'000; - next3.setFinalState(PresentResult::Presented); + next3.finalState = PresentResult::Presented; next3.displayed.push_back({ FrameType::Application, 6'000'000 }); auto metrics3 = ComputeMetricsForPresent(qpc, frame3, &next3, state); @@ -4978,7 +4978,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frameDropped.timeInPresent = 50'000; frameDropped.readyTime = 2'050'000; frameDropped.appSimStartTime = 150; - frameDropped.setFinalState(PresentResult::Discarded); + frameDropped.finalState = PresentResult::Discarded; // No displayed entries -> not displayed auto droppedResults = ComputeMetricsForPresent(qpc, frameDropped, nullptr, state); @@ -5007,7 +5007,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frameDisplayed.timeInPresent = 50'000; frameDisplayed.readyTime = 3'050'000; frameDisplayed.appSimStartTime = 200; - frameDisplayed.setFinalState(PresentResult::Presented); + frameDisplayed.finalState = PresentResult::Presented; frameDisplayed.displayed.push_back({ FrameType::Application, 3'500'000 }); // Dummy "next" frame – just to exercise the Case 3 path so that @@ -5017,7 +5017,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frameNext.timeInPresent = 50'000; frameNext.readyTime = 4'050'000; frameNext.appSimStartTime = 250; - frameNext.setFinalState(PresentResult::Presented); + frameNext.finalState = PresentResult::Presented; frameNext.displayed.push_back({ FrameType::Application, 4'500'000 }); auto displayedResults = ComputeMetricsForPresent(qpc, frameDisplayed, &frameNext, state); @@ -5271,14 +5271,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.readyTime = 1'500'000; frame1.appSimStartTime = 0; frame1.pclSimStartTime = 100; - frame1.setFinalState(PresentResult::Presented); + frame1.finalState = PresentResult::Presented; frame1.displayed.push_back({ FrameType::Application, 1'000'000 }); FrameData next1{}; next1.presentStartTime = 2'000'000; next1.timeInPresent = 400; next1.readyTime = 2'500'000; - next1.setFinalState(PresentResult::Presented); + next1.finalState = PresentResult::Presented; next1.displayed.push_back({ FrameType::Application, 2'000'000 }); auto metrics1 = ComputeMetricsForPresent(qpc, frame1, &next1, state); @@ -5306,14 +5306,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame2.readyTime = 3'500'000; frame2.appSimStartTime = 0; frame2.pclSimStartTime = 150; - frame2.setFinalState(PresentResult::Presented); + frame2.finalState = PresentResult::Presented; frame2.displayed.push_back({ FrameType::Application, 3'000'000 }); FrameData next2{}; next2.presentStartTime = 4'000'000; next2.timeInPresent = 400; next2.readyTime = 4'500'000; - next2.setFinalState(PresentResult::Presented); + next2.finalState = PresentResult::Presented; next2.displayed.push_back({ FrameType::Application, 4'000'000 }); auto metrics2 = ComputeMetricsForPresent(qpc, frame2, &next2, state); @@ -5343,14 +5343,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame3.readyTime = 5'500'000; frame3.appSimStartTime = 0; frame3.pclSimStartTime = 250; - frame3.setFinalState(PresentResult::Presented); + frame3.finalState = PresentResult::Presented; frame3.displayed.push_back({ FrameType::Application, 5'000'000 }); FrameData next3{}; next3.presentStartTime = 6'000'000; next3.timeInPresent = 400; next3.readyTime = 6'500'000; - next3.setFinalState(PresentResult::Presented); + next3.finalState = PresentResult::Presented; next3.displayed.push_back({ FrameType::Application, 6'000'000 }); auto metrics3 = ComputeMetricsForPresent(qpc, frame3, &next3, state); @@ -5647,7 +5647,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.presentStartTime = 800'000; priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; - priorApp.setFinalState(PresentResult::Presented); + priorApp.finalState = PresentResult::Presented; priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); state.lastAppPresent = priorApp; @@ -5715,7 +5715,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) prior.presentStartTime = 1'000'000; prior.timeInPresent = 100'000; // cpuStart1 = 1'100'000 prior.readyTime = 1'200'000; - prior.setFinalState(PresentResult::Presented); + prior.finalState = PresentResult::Presented; prior.displayed.push_back({ FrameType::Application, 1'300'000 }); state.lastAppPresent = prior; @@ -5732,14 +5732,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.readyTime = 2'100'000; frame1.appSimStartTime = 0; frame1.pclSimStartTime = 0; - frame1.setFinalState(PresentResult::Presented); + frame1.finalState = PresentResult::Presented; frame1.displayed.push_back({ FrameType::Application, 2'500'000 }); FrameData next1{}; next1.presentStartTime = 3'000'000; next1.timeInPresent = 50'000; next1.readyTime = 3'100'000; - next1.setFinalState(PresentResult::Presented); + next1.finalState = PresentResult::Presented; next1.displayed.push_back({ FrameType::Application, 3'500'000 }); auto metrics1 = ComputeMetricsForPresent(qpc, frame1, &next1, state); @@ -5766,14 +5766,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame2.readyTime = 4'200'000; frame2.appSimStartTime = 0; frame2.pclSimStartTime = 0; - frame2.setFinalState(PresentResult::Presented); + frame2.finalState = PresentResult::Presented; frame2.displayed.push_back({ FrameType::Application, 4'600'000 }); FrameData next2{}; next2.presentStartTime = 5'000'000; next2.timeInPresent = 50'000; next2.readyTime = 5'100'000; - next2.setFinalState(PresentResult::Presented); + next2.finalState = PresentResult::Presented; next2.displayed.push_back({ FrameType::Application, 5'500'000 }); auto metrics2 = ComputeMetricsForPresent(qpc, frame2, &next2, state); @@ -6633,7 +6633,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p1.readyTime = 510'000; p1.appSimStartTime = 475'000; p1.pclSimStartTime = 0; - p1.setFinalState(PresentResult::Presented); + p1.finalState = PresentResult::Presented; p1.displayed.push_back({ FrameType::Application, 1'000'000 }); // Arrival of P1 -> Case 2 (no nextDisplayed yet), becomes pending @@ -6656,7 +6656,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p2.readyTime = 610'000; p2.appSimStartTime = 575'000; p2.pclSimStartTime = 0; - p2.setFinalState(PresentResult::Presented); + p2.finalState = PresentResult::Presented; p2.displayed.push_back({ FrameType::Application, 1'100'000 }); // Arrival of P2: @@ -6696,7 +6696,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p3.readyTime = 710'000; p3.appSimStartTime = 675'000; p3.pclSimStartTime = 0; - p3.setFinalState(PresentResult::Presented); + p3.finalState = PresentResult::Presented; p3.displayed.push_back({ FrameType::Application, 1'200'000 }); // Arrival of P3: @@ -6795,7 +6795,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p1.readyTime = 510'000; p1.appSimStartTime = 475'000; // APC-provided sim start (QPC ticks) p1.pclSimStartTime = 0; - p1.setFinalState(PresentResult::Presented); + p1.finalState = PresentResult::Presented; p1.displayed.push_back({ FrameType::Application, 1'000'000 }); // screen time // P1 arrives -> Case 2 (no nextDisplayed yet), becomes pending @@ -6818,7 +6818,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p2.readyTime = 610'000; p2.appSimStartTime = 575'000; // APC timestamp, but this frame is not displayed p2.pclSimStartTime = 0; - p2.setFinalState(PresentResult::Discarded); + p2.finalState = PresentResult::Discarded; // No displayed entries -> not displayed auto p2_results = ComputeMetricsForPresent(qpc, p2, nullptr, state); @@ -6853,7 +6853,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p3.readyTime = 710'000; p3.appSimStartTime = 675'000; // another +100'000 ticks in sim space p3.pclSimStartTime = 0; - p3.setFinalState(PresentResult::Presented); + p3.finalState = PresentResult::Presented; p3.displayed.push_back({ FrameType::Application, 1'100'000 }); // next screen time // P3 arrives: @@ -7357,7 +7357,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.pclInputPingTime = 10'000; // PING0 p0.pclSimStartTime = 20'000; // SIM0 - p0.setFinalState(PresentResult::Discarded); + p0.finalState = PresentResult::Discarded; p0.displayed.clear(); // not displayed // P0 arrival -> Case 1 (not displayed), process immediately. @@ -7393,7 +7393,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p1.pclInputPingTime = 0; // no new ping p1.pclSimStartTime = 30'000; // SIM1 - p1.setFinalState(PresentResult::Discarded); + p1.finalState = PresentResult::Discarded; p1.displayed.clear(); // not displayed auto p1_metrics_list = ComputeMetricsForPresent(qpc, p1, nullptr, state); @@ -7430,7 +7430,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p2.pclInputPingTime = 0; // no new ping p2.pclSimStartTime = 40'000; // SIM2 - p2.setFinalState(PresentResult::Presented); + p2.finalState = PresentResult::Presented; p2.displayed.clear(); p2.displayed.push_back({ FrameType::Application, 50'000 }); // SCR2 @@ -7459,7 +7459,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p3.pclInputPingTime = 0; p3.pclSimStartTime = 0; // no PCL for P3 itself - p3.setFinalState(PresentResult::Presented); + p3.finalState = PresentResult::Presented; p3.displayed.clear(); p3.displayed.push_back({ FrameType::Application, 60'000 }); // some later screen time @@ -7528,7 +7528,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.swapChainAddress = SWAPCHAIN; p0.pclInputPingTime = 0; p0.pclSimStartTime = 0; - p0.setFinalState(PresentResult::Discarded); + p0.finalState = PresentResult::Discarded; auto p0_results = ComputeMetricsForPresent(qpc, p0, nullptr, state); Assert::AreEqual(size_t(1), p0_results.size(), @@ -7546,7 +7546,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p1{}; p1.processId = PROCESS_ID; p1.swapChainAddress = SWAPCHAIN; - p1.setFinalState(PresentResult::Presented); + p1.finalState = PresentResult::Presented; p1.displayed.push_back({ FrameType::Application, 100'000 }); auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, state); @@ -7563,7 +7563,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p2{}; p2.processId = PROCESS_ID; p2.swapChainAddress = SWAPCHAIN; - p2.setFinalState(PresentResult::Presented); + p2.finalState = PresentResult::Presented; p2.displayed.push_back({ FrameType::Application, 120'000 }); auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, state); @@ -7586,7 +7586,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p3{}; p3.processId = PROCESS_ID; p3.swapChainAddress = SWAPCHAIN; - p3.setFinalState(PresentResult::Presented); + p3.finalState = PresentResult::Presented; p3.displayed.push_back({ FrameType::Application, 140'000 }); auto p2_final = ComputeMetricsForPresent(qpc, p2, &p3, state); @@ -7627,7 +7627,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p0{}; p0.pclInputPingTime = 10'000; p0.pclSimStartTime = 20'000; - p0.setFinalState(PresentResult::Presented); + p0.finalState = PresentResult::Presented; p0.displayed.push_back({ FrameType::Application, 50'000 }); p0.displayed.push_back({ FrameType::Application, 60'000 }); @@ -7678,7 +7678,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p0{}; p0.pclInputPingTime = 10'000; p0.pclSimStartTime = 20'000; - p0.setFinalState(PresentResult::Presented); + p0.finalState = PresentResult::Presented; p0.displayed.push_back({ FrameType::Application, 50'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, state); @@ -7691,7 +7691,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p1{}; p1.pclInputPingTime = 30'000; p1.pclSimStartTime = 40'000; - p1.setFinalState(PresentResult::Presented); + p1.finalState = PresentResult::Presented; p1.displayed.push_back({ FrameType::Application, 70'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, state); @@ -7713,7 +7713,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) // P2: helper displayed frame to flush P1 // -------------------------------------------------------------------- FrameData p2{}; - p2.setFinalState(PresentResult::Presented); + p2.finalState = PresentResult::Presented; p2.displayed.push_back({ FrameType::Application, 90'000 }); auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, state); @@ -7751,7 +7751,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p0{}; p0.pclInputPingTime = 10'000; p0.pclSimStartTime = 20'000; - p0.setFinalState(PresentResult::Discarded); + p0.finalState = PresentResult::Discarded; auto p0_results = ComputeMetricsForPresent(qpc, p0, nullptr, state); Assert::AreEqual(size_t(1), p0_results.size(), @@ -7785,7 +7785,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p0{}; p0.pclInputPingTime = 10'000; p0.pclSimStartTime = 20'000; - p0.setFinalState(PresentResult::Discarded); + p0.finalState = PresentResult::Discarded; auto p0_results = ComputeMetricsForPresent(qpc, p0, nullptr, state); Assert::AreEqual(size_t(1), p0_results.size()); @@ -7795,7 +7795,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p1{}; p1.pclInputPingTime = 0; p1.pclSimStartTime = 30'000; - p1.setFinalState(PresentResult::Discarded); + p1.finalState = PresentResult::Discarded; auto p1_results = ComputeMetricsForPresent(qpc, p1, nullptr, state); Assert::AreEqual(size_t(1), p1_results.size(), @@ -7824,7 +7824,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p0{}; p0.pclInputPingTime = 0; p0.pclSimStartTime = 25'000; - p0.setFinalState(PresentResult::Discarded); + p0.finalState = PresentResult::Discarded; auto p0_results = ComputeMetricsForPresent(qpc, p0, nullptr, state); Assert::AreEqual(size_t(1), p0_results.size()); @@ -7854,7 +7854,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p0{}; p0.pclInputPingTime = 10'000; p0.pclSimStartTime = 20'000; - p0.setFinalState(PresentResult::Presented); + p0.finalState = PresentResult::Presented; p0.displayed.push_back({ FrameType::Application, 50'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, state); @@ -7863,7 +7863,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p1{}; p1.pclInputPingTime = 0; p1.pclSimStartTime = 35'000; - p1.setFinalState(PresentResult::Presented); + p1.finalState = PresentResult::Presented; p1.displayed.push_back({ FrameType::Application, 70'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, state); @@ -7876,7 +7876,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) Assert::AreEqual(size_t(0), p1_phase1.size()); FrameData p2{}; - p2.setFinalState(PresentResult::Presented); + p2.finalState = PresentResult::Presented; p2.displayed.push_back({ FrameType::Application, 90'000 }); auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, state); @@ -7914,7 +7914,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p0{}; p0.pclInputPingTime = 10'000; p0.pclSimStartTime = 30'000; - p0.setFinalState(PresentResult::Presented); + p0.finalState = PresentResult::Presented; p0.displayed.push_back({ FrameType::Application, 70'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, state); @@ -7923,7 +7923,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p1{}; p1.pclInputPingTime = 0; p1.pclSimStartTime = 0; - p1.setFinalState(PresentResult::Presented); + p1.finalState = PresentResult::Presented; p1.displayed.push_back({ FrameType::Application, 90'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, state); @@ -7940,7 +7940,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) Assert::AreEqual(size_t(0), p1_phase1.size()); FrameData p2{}; - p2.setFinalState(PresentResult::Presented); + p2.finalState = PresentResult::Presented; p2.displayed.push_back({ FrameType::Application, 110'000 }); auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, state); @@ -7977,13 +7977,13 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p0{}; p0.pclInputPingTime = 10'000; p0.pclSimStartTime = 20'000; - p0.setFinalState(PresentResult::Discarded); + p0.finalState = PresentResult::Discarded; ComputeMetricsForPresent(qpc, p0, nullptr, state); FrameData p1{}; p1.pclInputPingTime = 0; p1.pclSimStartTime = 30'000; - p1.setFinalState(PresentResult::Discarded); + p1.finalState = PresentResult::Discarded; ComputeMetricsForPresent(qpc, p1, nullptr, state); double accumBeforeP2 = state.accumulatedInput2FrameStartTime; @@ -7993,7 +7993,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p2{}; p2.pclInputPingTime = 100'000; p2.pclSimStartTime = 120'000; - p2.setFinalState(PresentResult::Discarded); + p2.finalState = PresentResult::Discarded; auto p2_results = ComputeMetricsForPresent(qpc, p2, nullptr, state); Assert::AreEqual(size_t(1), p2_results.size()); @@ -8026,7 +8026,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData d0{}; d0.pclInputPingTime = 10'000; d0.pclSimStartTime = 20'000; - d0.setFinalState(PresentResult::Discarded); + d0.finalState = PresentResult::Discarded; auto d0_results = ComputeMetricsForPresent(qpc, d0, nullptr, state); Assert::AreEqual(size_t(1), d0_results.size()); Assert::IsFalse(d0_results[0].metrics.msPcLatency.has_value()); @@ -8034,7 +8034,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData d1{}; d1.pclInputPingTime = 0; d1.pclSimStartTime = 30'000; - d1.setFinalState(PresentResult::Discarded); + d1.finalState = PresentResult::Discarded; auto d1_results = ComputeMetricsForPresent(qpc, d1, nullptr, state); Assert::AreEqual(size_t(1), d1_results.size()); Assert::IsFalse(d1_results[0].metrics.msPcLatency.has_value()); @@ -8046,14 +8046,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p0{}; p0.pclInputPingTime = 100'000; p0.pclSimStartTime = 120'000; - p0.setFinalState(PresentResult::Presented); + p0.finalState = PresentResult::Presented; p0.displayed.push_back({ FrameType::Application, 150'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, state); Assert::AreEqual(size_t(0), p0_phase1.size()); FrameData p1{}; - p1.setFinalState(PresentResult::Presented); + p1.finalState = PresentResult::Presented; p1.displayed.push_back({ FrameType::Application, 180'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, state); @@ -8158,7 +8158,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.gpuStartTime = 21'000; // Mark as displayed Application frame - p0.setFinalState(PresentResult::Presented); + p0.finalState = PresentResult::Presented; p0.displayed.clear(); p0.displayed.push_back({ FrameType::Application, 50'000 }); // screenTime = 50'000 @@ -8178,7 +8178,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p1.timeInPresent = 0; p1.readyTime = 0; - p1.setFinalState(PresentResult::Presented); + p1.finalState = PresentResult::Presented; p1.displayed.clear(); p1.displayed.push_back({ FrameType::Application, 60'000 }); // later display, not important @@ -8286,7 +8286,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.appSimStartTime = 0; // not needed in this test // Mark as displayed Application frame with a single screen time. - p0.setFinalState(PresentResult::Presented); + p0.finalState = PresentResult::Presented; p0.displayed.clear(); p0.displayed.push_back({ FrameType::Application, 30'000 }); // screenTime = 30'000 @@ -8306,7 +8306,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p1.timeInPresent = 0; p1.readyTime = 0; - p1.setFinalState(PresentResult::Presented); + p1.finalState = PresentResult::Presented; p1.displayed.clear(); p1.displayed.push_back({ FrameType::Application, 40'000 }); // later display @@ -8393,7 +8393,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.swapChainAddress = SWAPCHAIN; p0.appSimStartTime = 70'000; p0.gpuStartTime = 90'000; - p0.setFinalState(PresentResult::Presented); + p0.finalState = PresentResult::Presented; p0.displayed.clear(); p0.displayed.push_back({ FrameType::Application, 120'000 }); @@ -8405,7 +8405,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p1{}; p1.processId = PROCESS_ID; p1.swapChainAddress = SWAPCHAIN; - p1.setFinalState(PresentResult::Presented); + p1.finalState = PresentResult::Presented; p1.displayed.clear(); p1.displayed.push_back({ FrameType::Application, 150'000 }); @@ -8463,7 +8463,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.processId = PROCESS_ID; p0.swapChainAddress = SWAPCHAIN; p0.gpuStartTime = 80'000; - p0.setFinalState(PresentResult::Presented); + p0.finalState = PresentResult::Presented; p0.displayed.clear(); p0.displayed.push_back({ FrameType::Application, 100'000 }); @@ -8473,7 +8473,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p1{}; p1.processId = PROCESS_ID; p1.swapChainAddress = SWAPCHAIN; - p1.setFinalState(PresentResult::Presented); + p1.finalState = PresentResult::Presented; p1.displayed.push_back({ FrameType::Application, 120'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); @@ -8520,7 +8520,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.appSleepEndTime = 25'000; p0.appSimStartTime = 30'000; p0.gpuStartTime = 45'000; - p0.setFinalState(PresentResult::Discarded); + p0.finalState = PresentResult::Discarded; auto p0_results = ComputeMetricsForPresent(qpc, p0, nullptr, chain); Assert::AreEqual(size_t(1), p0_results.size(), @@ -8580,7 +8580,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.appSimStartTime = 70'000; p0.pclSimStartTime = 72'000; p0.gpuStartTime = 90'000; - p0.setFinalState(PresentResult::Presented); + p0.finalState = PresentResult::Presented; p0.displayed.clear(); p0.displayed.push_back({ FrameType::Repeated, 120'000 }); @@ -8590,7 +8590,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p1{}; p1.processId = PROCESS_ID; p1.swapChainAddress = SWAPCHAIN; - p1.setFinalState(PresentResult::Presented); + p1.finalState = PresentResult::Presented; p1.displayed.push_back({ FrameType::Application, 150'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); @@ -8626,14 +8626,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p0{}; p0.readyTime = 80'000; p0.appSleepEndTime = 50'000; - p0.setFinalState(PresentResult::Presented); + p0.finalState = PresentResult::Presented; p0.displayed.push_back({ FrameType::Application, 100'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); Assert::AreEqual(size_t(0), p0_phase1.size()); FrameData p1{}; - p1.setFinalState(PresentResult::Presented); + p1.finalState = PresentResult::Presented; p1.displayed.push_back({ FrameType::Application, 130'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); @@ -8683,14 +8683,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.appSimStartTime = 5'000; p0.readyTime = 30'000; p0.appSleepEndTime = 0; - p0.setFinalState(PresentResult::Presented); + p0.finalState = PresentResult::Presented; p0.displayed.push_back({ FrameType::Application, 60'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); Assert::AreEqual(size_t(0), p0_phase1.size()); FrameData p1{}; - p1.setFinalState(PresentResult::Presented); + p1.finalState = PresentResult::Presented; p1.displayed.push_back({ FrameType::Application, 90'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); @@ -8734,14 +8734,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p0{}; p0.appRenderSubmitStartTime = 12'000; p0.readyTime = 32'000; - p0.setFinalState(PresentResult::Presented); + p0.finalState = PresentResult::Presented; p0.displayed.push_back({ FrameType::Application, 70'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); Assert::AreEqual(size_t(0), p0_phase1.size()); FrameData p1{}; - p1.setFinalState(PresentResult::Presented); + p1.finalState = PresentResult::Presented; p1.displayed.push_back({ FrameType::Application, 90'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); @@ -8782,14 +8782,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.appRenderSubmitStartTime = 10'000; p0.readyTime = 30'000; p0.appSleepEndTime = 5'000; - p0.setFinalState(PresentResult::Presented); + p0.finalState = PresentResult::Presented; p0.displayed.push_back({ FrameType::Repeated, 60'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); Assert::AreEqual(size_t(0), p0_phase1.size()); FrameData p1{}; - p1.setFinalState(PresentResult::Presented); + p1.finalState = PresentResult::Presented; p1.displayed.push_back({ FrameType::Application, 90'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); @@ -8824,7 +8824,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.readyTime = 19'000; p0.appSleepEndTime = 4'000; p0.appSimStartTime = 2'000; - p0.setFinalState(PresentResult::Discarded); + p0.finalState = PresentResult::Discarded; auto p0_results = ComputeMetricsForPresent(qpc, p0, nullptr, chain); Assert::AreEqual(size_t(1), p0_results.size()); @@ -8861,7 +8861,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) // P0: Dropped Application frame with provider input. FrameData p0{}; p0.appInputSample = { pendingInputTime, InputDeviceType::Mouse }; - p0.setFinalState(PresentResult::Discarded); + p0.finalState = PresentResult::Discarded; auto p0_results = ComputeMetricsForPresent(qpc, p0, nullptr, chain); Assert::AreEqual(size_t(1), p0_results.size()); @@ -8870,7 +8870,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) // P1: Displayed Application frame without its own AppInputSample. FrameData p1{}; - p1.setFinalState(PresentResult::Presented); + p1.finalState = PresentResult::Presented; p1.displayed.push_back({ FrameType::Application, 70'000 }); auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, chain); @@ -8878,7 +8878,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) // P2: Simple next displayed frame to flush P1. FrameData p2{}; - p2.setFinalState(PresentResult::Presented); + p2.finalState = PresentResult::Presented; p2.displayed.push_back({ FrameType::Application, 90'000 }); auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, chain); @@ -8923,7 +8923,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p0{}; p0.appInputSample = { pendingInputTime, InputDeviceType::Keyboard }; - p0.setFinalState(PresentResult::Discarded); + p0.finalState = PresentResult::Discarded; auto p0_results = ComputeMetricsForPresent(qpc, p0, nullptr, chain); Assert::AreEqual(size_t(1), p0_results.size()); @@ -8931,14 +8931,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p1{}; p1.appInputSample = { directInputTime, InputDeviceType::Mouse }; - p1.setFinalState(PresentResult::Presented); + p1.finalState = PresentResult::Presented; p1.displayed.push_back({ FrameType::Application, 60'000 }); auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, chain); Assert::AreEqual(size_t(0), p1_phase1.size()); FrameData p2{}; - p2.setFinalState(PresentResult::Presented); + p2.finalState = PresentResult::Presented; p2.displayed.push_back({ FrameType::Application, 80'000 }); auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, chain); @@ -8980,14 +8980,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p0{}; p0.appInputSample = { ignoredInputTime, InputDeviceType::Mouse }; - p0.setFinalState(PresentResult::Presented); + p0.finalState = PresentResult::Presented; p0.displayed.push_back({ FrameType::Repeated, 50'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); Assert::AreEqual(size_t(0), p0_phase1.size()); FrameData p1{}; - p1.setFinalState(PresentResult::Presented); + p1.finalState = PresentResult::Presented; p1.displayed.push_back({ FrameType::Application, 80'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); @@ -8999,7 +8999,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) Assert::AreEqual(size_t(0), p1_phase1.size()); FrameData p2{}; - p2.setFinalState(PresentResult::Presented); + p2.finalState = PresentResult::Presented; p2.displayed.push_back({ FrameType::Application, 100'000 }); auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, chain); @@ -9012,4 +9012,4 @@ TEST_CLASS(ComputeMetricsForPresentTests) Assert::AreEqual(size_t(0), p2_phase1.size()); } }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonAPI2Tests/SwapChainTests.cpp b/IntelPresentMon/UnitTests/SwapChainTests.cpp similarity index 99% rename from IntelPresentMon/PresentMonAPI2Tests/SwapChainTests.cpp rename to IntelPresentMon/UnitTests/SwapChainTests.cpp index f9149ccb..088a4b56 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/SwapChainTests.cpp +++ b/IntelPresentMon/UnitTests/SwapChainTests.cpp @@ -7,7 +7,7 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework; -namespace SwapChainTests +namespace MetricsCoreTests { TEST_CLASS(SwapChainStateTests) { @@ -176,7 +176,7 @@ namespace SwapChainTests // Store in core state swapChain.lastPresent = present; - + // Verify access Assert::IsTrue(swapChain.lastPresent.has_value()); Assert::AreEqual(uint32_t(7777), swapChain.lastPresent.value().appFrameId); @@ -196,7 +196,7 @@ namespace SwapChainTests swapChain.lastPresent = presentOne; swapChain.lastSimStartTime = 1000; Assert::AreEqual(true, swapChain.lastPresent.has_value()); - + // Frame 2: Next frame received pmon::util::metrics::FrameData presentTwo; @@ -208,9 +208,8 @@ namespace SwapChainTests swapChain.lastDisplayedSimStartTime = 2000; swapChain.lastDisplayedScreenTime = 2016; // +16ms latency swapChain.lastDisplayedAppScreenTime = 2016; - Assert::AreEqual(uint64_t(2016), swapChain.lastDisplayedScreenTime); - + // Frame 3 pmon::util::metrics::FrameData presentThree; swapChain.lastPresent = presentThree; diff --git a/IntelPresentMon/UnitTests/UnitTests.vcxproj b/IntelPresentMon/UnitTests/UnitTests.vcxproj index c77292a0..2540bc7e 100644 --- a/IntelPresentMon/UnitTests/UnitTests.vcxproj +++ b/IntelPresentMon/UnitTests/UnitTests.vcxproj @@ -102,6 +102,7 @@ + diff --git a/IntelPresentMon/UnitTests/UnitTests.vcxproj.filters b/IntelPresentMon/UnitTests/UnitTests.vcxproj.filters index ec12170e..5dda8b0d 100644 --- a/IntelPresentMon/UnitTests/UnitTests.vcxproj.filters +++ b/IntelPresentMon/UnitTests/UnitTests.vcxproj.filters @@ -4,6 +4,7 @@ + \ No newline at end of file From 51c5ea6de3d3813620d1b018db638e8fa955a948 Mon Sep 17 00:00:00 2001 From: Chili Date: Sun, 4 Jan 2026 17:12:55 +0900 Subject: [PATCH 099/205] metric usage verbose logging --- .../CommonUtilities/log/EntryBuilder.h | 32 ++++++++++++++++++- IntelPresentMon/CommonUtilities/log/Verbose.h | 1 + .../KernelProcess/KernelProcess.args.json | 2 +- .../KernelProcess/kact/Introspect.h | 26 ++------------- .../KernelProcess/kact/PushSpecification.h | 25 ++------------- .../InterimBroadcasterTests.cpp | 2 +- .../ActionExecutionContext.cpp | 16 ++++++++-- .../ActionExecutionContext.h | 11 +++++-- .../PresentMonService/acts/ReportMetricUse.h | 11 +++++-- 9 files changed, 69 insertions(+), 57 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/log/EntryBuilder.h b/IntelPresentMon/CommonUtilities/log/EntryBuilder.h index a523db53..b4c179be 100644 --- a/IntelPresentMon/CommonUtilities/log/EntryBuilder.h +++ b/IntelPresentMon/CommonUtilities/log/EntryBuilder.h @@ -1,8 +1,16 @@ -#pragma once +#pragma once #include "Entry.h" #include #include #include +#include +#if __has_include() +#include +#include +#define PMLOG_HAS_CEREAL_JSON 1 +#else +#define PMLOG_HAS_CEREAL_JSON 0 +#endif #include "TimePoint.h" #include "PanicLogger.h" @@ -53,6 +61,28 @@ namespace pmon::util::log catch (...) { pmlog_panic_("Failed to format watch in EntryBuilder"); } return *this; } + template + EntryBuilder& serialize(const char* symbol, const T& value) noexcept + { +#if PMLOG_HAS_CEREAL_JSON + try { + std::ostringstream os; + auto opts = cereal::JSONOutputArchive::Options::Default(); + cereal::JSONOutputArchive ar(os, opts); + ar(cereal::make_nvp(symbol, value)); + return watch(symbol, os.str()); + } + catch (const std::exception& e) { + return watch(symbol, std::string("JSON serialization failed: ") + e.what()); + } + catch (...) { + return watch(symbol, std::string("JSON serialization failed")); + } +#else + (void)value; + return watch(symbol, std::string("JSON serialization unavailable (cereal not built)")); +#endif + } template EntryBuilder& raise() { diff --git a/IntelPresentMon/CommonUtilities/log/Verbose.h b/IntelPresentMon/CommonUtilities/log/Verbose.h index 7d8273cb..b92240d0 100644 --- a/IntelPresentMon/CommonUtilities/log/Verbose.h +++ b/IntelPresentMon/CommonUtilities/log/Verbose.h @@ -15,6 +15,7 @@ namespace pmon::util::log etwq, kact, ipc_sto, + met_use, Count }; diff --git a/IntelPresentMon/KernelProcess/KernelProcess.args.json b/IntelPresentMon/KernelProcess/KernelProcess.args.json index e496679e..43b7afbc 100644 --- a/IntelPresentMon/KernelProcess/KernelProcess.args.json +++ b/IntelPresentMon/KernelProcess/KernelProcess.args.json @@ -34,7 +34,7 @@ }, { "Id": "49b6fc1f-b969-4280-b0d8-17a213e80f16", - "Command": "--log-verbose-modules v8async core_metric kact" + "Command": "--log-verbose-modules met_use" }, { "Id": "bab48d6d-3a48-4b3b-9ed9-903e381824c6", diff --git a/IntelPresentMon/KernelProcess/kact/Introspect.h b/IntelPresentMon/KernelProcess/kact/Introspect.h index 4930e76e..73c60ed9 100644 --- a/IntelPresentMon/KernelProcess/kact/Introspect.h +++ b/IntelPresentMon/KernelProcess/kact/Introspect.h @@ -10,9 +10,7 @@ // cereal JSON dump + NVP macro #include -#include #include -#include #define ACT_NAME Introspect #define ACT_EXEC_CTX KernelExecutionContext @@ -176,28 +174,8 @@ namespace ACT_NS }); } - // --- dump response as JSON to log (best-effort; never fail the action due to logging) --- - std::string responseJson; - try - { - std::ostringstream os; - - // Pretty JSON by default; use NoIndent() if you want compact output. - auto opts = cereal::JSONOutputArchive::Options::Default(); - cereal::JSONOutputArchive ar(os, opts); - - // Use macro-driven naming for the root object too - auto& introspect = res; - ar(CEREAL_NVP(introspect)); - - responseJson = os.str(); - } - catch (const std::exception& e) - { - responseJson = std::string("Introspect JSON serialization failed: ") + e.what(); - } - - pmlog_verb(v::kact)("Introspect action").pmwatch(responseJson); + pmlog_verb(v::kact)("Introspect action") + .serialize("introspect", res); return res; } diff --git a/IntelPresentMon/KernelProcess/kact/PushSpecification.h b/IntelPresentMon/KernelProcess/kact/PushSpecification.h index aef681e8..2477814f 100644 --- a/IntelPresentMon/KernelProcess/kact/PushSpecification.h +++ b/IntelPresentMon/KernelProcess/kact/PushSpecification.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "../../Interprocess/source/act/ActionHelper.h" #include "KernelExecutionContext.h" #include "../MakeOverlaySpec.h" @@ -11,11 +11,9 @@ // cereal JSON dump + NVP macro #include -#include #include #include #include -#include #define ACT_NAME PushSpecification #define ACT_EXEC_CTX KernelExecutionContext @@ -299,27 +297,10 @@ namespace ACT_NS (*ctx.ppKernel)->PushSpec(MakeOverlaySpec(in)); } - // --- dump params as JSON to log (best-effort; never fail the action due to logging) --- // (No useful response fields; logging the request is typically what you want here.) - std::string paramsJson; - try - { - std::ostringstream os; - auto opts = cereal::JSONOutputArchive::Options::Default(); - cereal::JSONOutputArchive ar(os, opts); - - auto& pushSpecification = in; - ar(CEREAL_NVP(pushSpecification)); - - paramsJson = os.str(); - } - catch (const std::exception& e) - { - paramsJson = std::string("PushSpecification JSON serialization failed: ") + e.what(); - } - using v = pmon::util::log::V; - pmlog_verb(v::kact)("PushSpecification action").pmwatch(paramsJson); + pmlog_verb(v::kact)("PushSpecification action") + .serialize("pushSpecification", in); return {}; } diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 44887ca2..5dc336b9 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -74,7 +74,7 @@ namespace InterimBroadcasterTests .ctrlPipe = R"(\\.\pipe\pm-intbroad-test-ctrl)", .shmNamePrefix = "pm_intbroad_test", .logLevel = "verbose", - .logVerboseModules = "ipc_sto", + .logVerboseModules = "ipc_sto met_use", .logFolder = logFolder_, .sampleClientMode = "NONE", }; diff --git a/IntelPresentMon/PresentMonService/ActionExecutionContext.cpp b/IntelPresentMon/PresentMonService/ActionExecutionContext.cpp index 26b3b3af..3531cf49 100644 --- a/IntelPresentMon/PresentMonService/ActionExecutionContext.cpp +++ b/IntelPresentMon/PresentMonService/ActionExecutionContext.cpp @@ -1,7 +1,8 @@ -#include "ActionExecutionContext.h" +#include "ActionExecutionContext.h" #include #include #include "../Interprocess/source/act/ActionHelper.h" +#include namespace pmon::svc::acts { @@ -20,6 +21,9 @@ namespace pmon::svc::acts UpdateTelemetryPeriod(); stx.requestedEtwFlushPeriodMs.reset(); UpdateEtwFlushPeriod(); + pmlog_verb(pmon::util::log::V::met_use)("Session closing, removing metric usage") + .pmwatch(stx.remotePid) + .serialize("sessionMetricUsage", stx.metricUsage); stx.metricUsage.clear(); UpdateMetricUsage(); } @@ -49,13 +53,21 @@ namespace pmon::svc::acts } void ActionExecutionContext::UpdateMetricUsage() const { + std::unordered_set aggregateMetricUsage; std::unordered_set deviceMetricUsage; auto&& allUsageSets = util::rng::MemberSlice(*pSessionMap, &SessionContextType::metricUsage); for (auto&& clientUsageSet : allUsageSets) { for (auto&& usage : clientUsageSet) { + aggregateMetricUsage.insert(usage); deviceMetricUsage.insert(usage.deviceId); } } + if (!hasLastAggregateMetricUsage || aggregateMetricUsage != lastAggregateMetricUsage) { + pmlog_verb(pmon::util::log::V::met_use)("Aggregate metric usage updated") + .serialize("aggregateMetricUsage", aggregateMetricUsage); + lastAggregateMetricUsage = aggregateMetricUsage; + hasLastAggregateMetricUsage = true; + } pPmon->SetDeviceMetricUsage(std::move(deviceMetricUsage)); } -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonService/ActionExecutionContext.h b/IntelPresentMon/PresentMonService/ActionExecutionContext.h index 0637068c..245d90d8 100644 --- a/IntelPresentMon/PresentMonService/ActionExecutionContext.h +++ b/IntelPresentMon/PresentMonService/ActionExecutionContext.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "../Interprocess/source/act/SymmetricActionConnector.h" #include "../Interprocess/source/ShmNamer.h" #include "../CommonUtilities/Hash.h" @@ -13,6 +13,7 @@ #include #include #include +#include #include "PresentMon.h" #include "Service.h" @@ -21,7 +22,7 @@ namespace pmon::svc::acts { struct ActionExecutionContext; - + // TODO: move this struct into its own header struct MetricUse { PM_METRIC metricId; @@ -31,7 +32,9 @@ namespace pmon::svc::acts template void serialize(A& ar) { - ar(metricId, deviceId, arrayIdx); + ar(CEREAL_NVP(metricId), + CEREAL_NVP(deviceId), + CEREAL_NVP(arrayIdx)); } bool operator==(const MetricUse& rhs) const @@ -91,6 +94,8 @@ namespace pmon::svc::acts PresentMon* pPmon = nullptr; const std::unordered_map* pSessionMap = nullptr; std::optional responseWriteTimeoutMs; + mutable std::unordered_set lastAggregateMetricUsage; + mutable bool hasLastAggregateMetricUsage = false; // functions void Dispose(SessionContextType& stx); diff --git a/IntelPresentMon/PresentMonService/acts/ReportMetricUse.h b/IntelPresentMon/PresentMonService/acts/ReportMetricUse.h index b7a3608b..6d42ff1a 100644 --- a/IntelPresentMon/PresentMonService/acts/ReportMetricUse.h +++ b/IntelPresentMon/PresentMonService/acts/ReportMetricUse.h @@ -1,6 +1,7 @@ -#pragma once +#pragma once #include "../../Interprocess/source/act/ActionHelper.h" #include +#include #define ACT_NAME ReportMetricUse #define ACT_EXEC_CTX ActionExecutionContext @@ -19,7 +20,7 @@ namespace pmon::svc::acts { std::unordered_set metricUsage; template void serialize(A& ar) { - ar(metricUsage); + ar(CEREAL_NVP(metricUsage)); } }; struct Response @@ -30,6 +31,10 @@ namespace pmon::svc::acts friend class ACT_TYPE; static Response Execute_(const ACT_EXEC_CTX& ctx, SessionContext& stx, Params&& in) { + pmlog_verb(pmon::util::log::V::met_use)("ReportMetricUse action payload") + .pmwatch(stx.remotePid) + .serialize("reportMetricUse", in); + stx.metricUsage = std::move(in.metricUsage); ctx.UpdateMetricUsage(); return {}; @@ -46,4 +51,4 @@ ACTION_TRAITS_DEF(); #undef ACT_NAME #undef ACT_EXEC_CTX #undef ACT_NS -#undef ACT_TYPE \ No newline at end of file +#undef ACT_TYPE From a32b816d4e3ef5224ca0e05427304193a60ea045 Mon Sep 17 00:00:00 2001 From: Chili Date: Sun, 4 Jan 2026 18:15:58 +0900 Subject: [PATCH 100/205] frame lifecycle logging --- .../Interprocess/source/Interprocess.cpp | 57 +++++++++++++++---- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/Interprocess.cpp b/IntelPresentMon/Interprocess/source/Interprocess.cpp index efb3fb4b..00c44134 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.cpp +++ b/IntelPresentMon/Interprocess/source/Interprocess.cpp @@ -140,22 +140,46 @@ namespace pmon::ipc { // resolve out existing or new weak ptr, try and lock auto& pWeak = frameShmWeaks_[pid]; - auto pFrameData = frameShmWeaks_[pid].lock(); + auto pFrameData = pWeak.lock(); if (!pFrameData) { // if weak ptr was new (or expired), lock will not work and we need to construct // make a frame data store as shared ptr - pFrameData = std::make_shared>( - namer_.MakeFrameName(pid), - DataStoreSizingInfo{ - .ringSamples = frameRingSamples_, - .backpressured = backpressured, - }, - static_cast(Permissions_{})); + const auto segmentName = namer_.MakeFrameName(pid); + const DataStoreSizingInfo sizing{ + .ringSamples = frameRingSamples_, + .backpressured = backpressured, + }; + pFrameData = std::shared_ptr>( + new OwnedDataSegment( + segmentName, + sizing, + static_cast(Permissions_{})), + [pid, segmentName](OwnedDataSegment* pSegment) { + pmlog_dbg("Frame data segment destroyed") + .pmwatch(pid) + .pmwatch(segmentName); + delete pSegment; + }); // store a weak reference pWeak = pFrameData; + pmlog_dbg("Frame data segment created") + .pmwatch(pid) + .pmwatch(segmentName) + .pmwatch(backpressured); } // remove stale elements to keep map lean - std::erase_if(frameShmWeaks_, [](auto&&kv){return kv.second.expired();}); + for (auto it = frameShmWeaks_.begin(); it != frameShmWeaks_.end(); ) { + if (it->second.expired()) { + const auto segmentName = namer_.MakeFrameName(it->first); + pmlog_dbg("Frame data segment released") + .pmwatch(it->first) + .pmwatch(segmentName); + it = frameShmWeaks_.erase(it); + } + else { + ++it; + } + } return pFrameData; } @@ -167,6 +191,10 @@ namespace pmon::ipc return pSegment; } // if weak ptr has expired, garbage collect from the map + const auto segmentName = namer_.MakeFrameName(pid); + pmlog_dbg("Frame data segment released") + .pmwatch(pid) + .pmwatch(segmentName); frameShmWeaks_.erase(i); } return {}; @@ -320,10 +348,19 @@ namespace pmon::ipc std::forward_as_tuple(pid), std::forward_as_tuple(segName) ); + pmlog_dbg("Frame data segment opened") + .pmwatch(pid) + .pmwatch(segName); } void CloseFrameDataStore(uint32_t pid) override { - frameShms_.erase(pid); + if (auto it = frameShms_.find(pid); it != frameShms_.end()) { + const auto segName = namer_.MakeFrameName(pid); + pmlog_dbg("Frame data segment closed") + .pmwatch(pid) + .pmwatch(segName); + frameShms_.erase(it); + } } // data store access const FrameDataStore& GetFrameDataStore(uint32_t pid) const override From 515ad6920a42865d0b76602c8075ab7f3d346ec4 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Mon, 5 Jan 2026 08:21:23 -0800 Subject: [PATCH 101/205] Initial cleanup for updating metrics returns. --- .../CommonUtilities/mc/MetricsCalculator.cpp | 386 ++++++++---------- 1 file changed, 159 insertions(+), 227 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index f0d51199..8aea10df 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -6,170 +6,165 @@ #include "../IntelPresentMon/PresentMonUtils/StreamFormat.h" #include "../Math.h" +// Layout: internal helpers -> entry points -> metric assembly -> exported helpers + namespace pmon::util::metrics { + // ============================================================================ + // 1) Internal helpers (file-local) + // ============================================================================ namespace { // Helper dedicated to computing msUntilDisplayed, matching legacy ReportMetricsHelper behavior. - void ComputeMsUntilDisplayed( + + // ---- Display metrics ---- + double ComputeMsUntilDisplayed( const QpcConverter& qpc, const FrameData& present, bool isDisplayed, - uint64_t screenTime, - FrameMetrics& out) + uint64_t screenTime) { - if (isDisplayed) { - out.msUntilDisplayed = qpc.DeltaUnsignedMilliSeconds( - present.presentStartTime, - screenTime); - } - else { - out.msUntilDisplayed = 0.0; - } + return isDisplayed + ? qpc.DeltaUnsignedMilliSeconds(present.presentStartTime, screenTime) + : 0.0; } + // Helper dedicated to computing msBetweenDisplayChange, matching legacy ReportMetricsHelper behavior. - void ComputeMsBetweenDisplayChange( + double ComputeMsBetweenDisplayChange( const QpcConverter& qpc, const SwapChainCoreState& chain, bool isDisplayed, - uint64_t screenTime, - FrameMetrics& out) + uint64_t screenTime) { - if (isDisplayed) { - out.msBetweenDisplayChange = qpc.DeltaUnsignedMilliSeconds( - chain.lastDisplayedScreenTime, - screenTime); - } - else { - out.msBetweenDisplayChange = 0.0; - } + return isDisplayed + ? qpc.DeltaUnsignedMilliSeconds(chain.lastDisplayedScreenTime, screenTime) + : 0.0; } + // Helper dedicated to computing msDisplayedTime, matching legacy ReportMetricsHelper behavior. - void ComputeMsDisplayedTime( + double ComputeMsDisplayedTime( const QpcConverter& qpc, bool isDisplayed, uint64_t screenTime, - uint64_t nextScreenTime, - FrameMetrics& out) + uint64_t nextScreenTime) { - if (isDisplayed) { - out.msDisplayedTime = qpc.DeltaUnsignedMilliSeconds( - screenTime, - nextScreenTime); - } - else { - out.msDisplayedTime = 0.0; - } + return isDisplayed + ? qpc.DeltaUnsignedMilliSeconds(screenTime, nextScreenTime) + : 0.0; } + - void ComputeMsFlipDelay( + std::optional ComputeMsFlipDelay( const QpcConverter& qpc, const FrameData& present, - bool isDisplayed, - FrameMetrics& out) + bool isDisplayed) { if (isDisplayed && present.flipDelay != 0) { - out.msFlipDelay = qpc.DurationMilliSeconds(present.flipDelay); - } - else { - out.msFlipDelay = std::nullopt; + return qpc.DurationMilliSeconds(present.flipDelay); } + return std::nullopt; } - void ComputeMsDisplayLatency( + + double ComputeMsDisplayLatency( const QpcConverter& qpc, const SwapChainCoreState& swapChain, const FrameData& present, bool isDisplayed, - uint64_t screenTime, - FrameMetrics& out) + uint64_t screenTime) { const auto cpuStart = CalculateCPUStart(swapChain, present); - if (isDisplayed && cpuStart != 0) { - out.msDisplayLatency = qpc.DeltaUnsignedMilliSeconds(cpuStart, screenTime); - } else { - out.msDisplayLatency = 0.0; - } + return (isDisplayed && cpuStart != 0) + ? qpc.DeltaUnsignedMilliSeconds(cpuStart, screenTime) + : 0.0; } - void ComputeMsReadyTimeToDisplayLatency( + + std::optional ComputeMsReadyTimeToDisplayLatency( const QpcConverter& qpc, const FrameData& present, bool isDisplayed, - uint64_t screenTime, - FrameMetrics& out) + uint64_t screenTime) { if (isDisplayed && present.readyTime != 0) { - out.msReadyTimeToDisplayLatency = qpc.DeltaUnsignedMilliSeconds(present.readyTime, screenTime); - } - else { - out.msReadyTimeToDisplayLatency = std::nullopt; + return qpc.DeltaUnsignedMilliSeconds(present.readyTime, screenTime); } + return std::nullopt; } - void ComputeMsCpuBusy( + + // ---- CPU/GPU metrics ---- + double ComputeMsCpuBusy( const QpcConverter& qpc, const SwapChainCoreState& swapChain, const FrameData& present, - bool isAppPresent, - FrameMetrics& out) + bool isAppPresent) { - //out.msCPUBusy = std::nullopt; - out.msCPUBusy = 0.0; - if (isAppPresent) { - auto cpuStart = CalculateCPUStart(swapChain, present); - if (cpuStart != 0) { - if (present.appPropagatedPresentStartTime != 0) { - out.msCPUBusy = qpc.DeltaUnsignedMilliSeconds(cpuStart, present.appPropagatedPresentStartTime); - } else if (present.presentStartTime != 0) { - out.msCPUBusy = qpc.DeltaUnsignedMilliSeconds(cpuStart, present.presentStartTime); - } - } + if (!isAppPresent) { + return 0.0; + } + + const auto cpuStart = CalculateCPUStart(swapChain, present); + if (cpuStart == 0) { + return 0.0; + } + + if (present.appPropagatedPresentStartTime != 0) { + return qpc.DeltaUnsignedMilliSeconds(cpuStart, present.appPropagatedPresentStartTime); } + if (present.presentStartTime != 0) { + return qpc.DeltaUnsignedMilliSeconds(cpuStart, present.presentStartTime); + } + return 0.0; } - void ComputeMsCpuWait( + + double ComputeMsCpuWait( const QpcConverter& qpc, const FrameData& present, - bool isAppPresent, - FrameMetrics& out) + bool isAppPresent) { - //out.msCPUWait = std::nullopt; - out.msCPUWait = 0.0; - if (isAppPresent) { - if (present.appPropagatedTimeInPresent != 0) { - out.msCPUWait = qpc.DurationMilliSeconds(present.appPropagatedTimeInPresent); - } - else if (present.timeInPresent != 0) { - out.msCPUWait = qpc.DurationMilliSeconds(present.timeInPresent); - } + if (!isAppPresent) { + return 0.0; + } + + if (present.appPropagatedTimeInPresent != 0) { + return qpc.DurationMilliSeconds(present.appPropagatedTimeInPresent); } + if (present.timeInPresent != 0) { + return qpc.DurationMilliSeconds(present.timeInPresent); + } + return 0.0; } - void ComputeMsGpuLatency( + + double ComputeMsGpuLatency( const QpcConverter& qpc, const SwapChainCoreState& swapChain, const FrameData& present, - bool isAppPresent, - FrameMetrics& out) + bool isAppPresent) { - //out.msGPULatency = std::nullopt; - out.msGPULatency = 0.0; - if (isAppPresent) { - auto cpuStart = CalculateCPUStart(swapChain, present); - if (cpuStart != 0) { - if (present.appPropagatedGPUStartTime != 0) { - out.msGPULatency = qpc.DeltaUnsignedMilliSeconds(cpuStart, present.appPropagatedGPUStartTime); - } - else if (present.gpuStartTime != 0) { - out.msGPULatency = qpc.DeltaUnsignedMilliSeconds(cpuStart, present.gpuStartTime); - } - } + if (!isAppPresent) { + return 0.0; + } + + const auto cpuStart = CalculateCPUStart(swapChain, present); + if (cpuStart == 0) { + return 0.0; + } + + if (present.appPropagatedGPUStartTime != 0) { + return qpc.DeltaUnsignedMilliSeconds(cpuStart, present.appPropagatedGPUStartTime); } + if (present.gpuStartTime != 0) { + return qpc.DeltaUnsignedMilliSeconds(cpuStart, present.gpuStartTime); + } + return 0.0; } + double ComputeMsGpuBusy( const QpcConverter& qpc, const FrameData& present, @@ -188,24 +183,25 @@ namespace pmon::util::metrics return msGPUBusy; } - void ComputeMsVideoBusy( + double ComputeMsVideoBusy( const QpcConverter& qpc, const FrameData& present, - bool isAppPresent, - FrameMetrics& out) + bool isAppPresent) { - //out.msVideoBusy = std::nullopt; - out.msVideoBusy = 0.0; - if (isAppPresent) { - if (present.appPropagatedGPUVideoDuration != 0) { - out.msVideoBusy = qpc.DurationMilliSeconds(present.appPropagatedGPUVideoDuration); - } - else if (present.gpuVideoDuration != 0) { - out.msVideoBusy = qpc.DurationMilliSeconds(present.gpuVideoDuration); - } + if (!isAppPresent) { + return 0.0; + } + + if (present.appPropagatedGPUVideoDuration != 0) { + return qpc.DurationMilliSeconds(present.appPropagatedGPUVideoDuration); + } + if (present.gpuVideoDuration != 0) { + return qpc.DurationMilliSeconds(present.gpuVideoDuration); } + return 0.0; } + double ComputeMsGpuDuration ( const QpcConverter& qpc, const FrameData& present, @@ -224,27 +220,28 @@ namespace pmon::util::metrics return msGPUDuration; } - void ComputeMsGpuWait( + double ComputeMsGpuWait( const QpcConverter& qpc, const FrameData& present, - bool isAppPresent, - FrameMetrics& out) + bool isAppPresent) { - out.msGPUWait = std::max(0.0, ComputeMsGpuDuration(qpc, present, isAppPresent) - ComputeMsGpuBusy(qpc, present, isAppPresent)); + return std::max(0.0, + ComputeMsGpuDuration(qpc, present, isAppPresent) - + ComputeMsGpuBusy(qpc, present, isAppPresent)); } - void ComputeAnimationError( + + // ---- Animation metrics ---- + std::optional ComputeAnimationError( const QpcConverter& qpc, const SwapChainCoreState& chain, const FrameData& present, bool isDisplayed, bool isAppFrame, - uint64_t screenTime, - FrameMetrics& out) + uint64_t screenTime) { if (!isDisplayed || !isAppFrame) { - out.msAnimationError = std::nullopt; - return; + return std::nullopt; } uint64_t currentSimStart = CalculateAnimationErrorSimStartTime(chain, present, chain.animationErrorSource); @@ -253,32 +250,29 @@ namespace pmon::util::metrics chain.lastDisplayedSimStartTime == 0 || currentSimStart <= chain.lastDisplayedSimStartTime || chain.lastDisplayedAppScreenTime == 0) { - out.msAnimationError = std::nullopt; - return; + return std::nullopt; } double simElapsed = qpc.DeltaUnsignedMilliSeconds(chain.lastDisplayedSimStartTime, currentSimStart); double displayElapsed = qpc.DeltaUnsignedMilliSeconds(chain.lastDisplayedAppScreenTime, screenTime); - if(simElapsed == 0.0 || displayElapsed == 0.0) { - out.msAnimationError = std::nullopt; - return; + if (simElapsed == 0.0 || displayElapsed == 0.0) { + return std::nullopt; } - out.msAnimationError = simElapsed - displayElapsed; + return simElapsed - displayElapsed; } - void ComputeAnimationTime( + + std::optional ComputeAnimationTime( const QpcConverter& qpc, const SwapChainCoreState& chain, const FrameData& present, bool isDisplayed, - bool isAppFrame, - FrameMetrics& out) + bool isAppFrame) { if (!isDisplayed || !isAppFrame) { - out.msAnimationTime = std::nullopt; - return; + return std::nullopt; } bool isFirstProviderSimTime = @@ -287,19 +281,19 @@ namespace pmon::util::metrics if (isFirstProviderSimTime) { // Seed only: no animation time yet. UpdateAfterPresent will flip us // into AppProvider/PCL and latch firstAppSimStartTime. - out.msAnimationTime = std::nullopt; - return; + return std::nullopt; } uint64_t currentSimStart = CalculateAnimationErrorSimStartTime(chain, present, chain.animationErrorSource); if (currentSimStart == 0) { - out.msAnimationTime = std::nullopt; - return; + return std::nullopt; } - out.msAnimationTime = CalculateAnimationTime(qpc, chain.firstAppSimStartTime, currentSimStart); + return CalculateAnimationTime(qpc, chain.firstAppSimStartTime, currentSimStart); } + + // ---- NV collapsed/runt correction ---- void AdjustScreenTimeForCollapsedPresentNV( FrameData& present, FrameData* nextDisplayedPresent, @@ -338,6 +332,7 @@ namespace pmon::util::metrics } } + // ---- Input latency metrics ---- std::optional ComputeClickToPhotonLatency( const QpcConverter& qpc, const SwapChainCoreState& chain, @@ -468,6 +463,7 @@ namespace pmon::util::metrics return qpc.DeltaUnsignedMilliSeconds(inputTime, screenTime); } + // ---- Instrumented metrics ---- std::optional ComputeInstrumentedLatency( const QpcConverter& qpc, const FrameData& present, @@ -553,6 +549,7 @@ namespace pmon::util::metrics return qpc.DeltaUnsignedMilliSeconds(instrumentedStartTime, present.gpuStartTime); } + // ---- Simulation metrics ---- std::optional ComputeMsBetweenSimulationStarts( const QpcConverter& qpc, const SwapChainCoreState& chain, @@ -583,6 +580,7 @@ namespace pmon::util::metrics } } + // ---- Swap-chain state application ---- void ApplyStateDeltas( SwapChainCoreState& chainState, const ComputedMetrics::StateDeltas& d) @@ -642,6 +640,10 @@ namespace pmon::util::metrics } } + + // ============================================================================ + // 2) Public entry points + // ============================================================================ DisplayIndexing DisplayIndexing::Calculate( const FrameData& present, const FrameData* nextDisplayed) @@ -837,7 +839,9 @@ namespace pmon::util::metrics return results; } - + // ============================================================================ + // 3) Metric assembly helpers (ComputeFrameMetrics) + // ============================================================================ void CalculateBasePresentMetrics( const QpcConverter& qpc, const FrameData& present, @@ -877,57 +881,15 @@ namespace pmon::util::metrics bool isDisplayed, uint64_t screenTime, uint64_t nextScreenTime, - FrameMetrics& metrics) { - - // msUntilDisplayed depends only on whether this display instance is displayed and its screen time - ComputeMsUntilDisplayed( - qpc, - present, - isDisplayed, - screenTime, - metrics); - - // msBetweenDisplayChange depends on previous displayed screen time and the current screen time - ComputeMsBetweenDisplayChange( - qpc, - swapChain, - isDisplayed, - screenTime, - metrics); - - // msDisplayedTime depends on the current screen time and the next screen time - ComputeMsDisplayedTime( - qpc, - isDisplayed, - screenTime, - nextScreenTime, - metrics); - - // msFlipDelay depends if the current present has a flip delay - ComputeMsFlipDelay( - qpc, - present, - isDisplayed, - metrics); - - // msDisplayLatency is the cpu start time to the current screen time - ComputeMsDisplayLatency( - qpc, - swapChain, - present, - isDisplayed, - screenTime, - metrics); - - // msReadyTimeToDisplayLatency is ready time to the current screen time - ComputeMsReadyTimeToDisplayLatency( - qpc, - present, - isDisplayed, - screenTime, - metrics); + FrameMetrics& metrics) + { + metrics.msUntilDisplayed = ComputeMsUntilDisplayed(qpc, present, isDisplayed, screenTime); + metrics.msBetweenDisplayChange = ComputeMsBetweenDisplayChange(qpc, swapChain, isDisplayed, screenTime); + metrics.msDisplayedTime = ComputeMsDisplayedTime(qpc, isDisplayed, screenTime, nextScreenTime); + metrics.msFlipDelay = ComputeMsFlipDelay(qpc, present, isDisplayed); + metrics.msDisplayLatency = ComputeMsDisplayLatency(qpc, swapChain, present, isDisplayed, screenTime); + metrics.msReadyTimeToDisplayLatency = ComputeMsReadyTimeToDisplayLatency(qpc, present, isDisplayed, screenTime); - // screenTimeQpc is simply the current screen time metrics.screenTimeQpc = screenTime; } @@ -938,45 +900,15 @@ namespace pmon::util::metrics bool isAppFrame, FrameMetrics& metrics) { - ComputeMsCpuBusy( - qpc, - chainState, - present, - isAppFrame, - metrics); - - ComputeMsCpuWait( - qpc, - present, - isAppFrame, - metrics); - - ComputeMsGpuLatency( - qpc, - chainState, - present, - isAppFrame, - metrics); - - metrics.msGPUBusy = ComputeMsGpuBusy( - qpc, - present, - isAppFrame); - - ComputeMsVideoBusy( - qpc, - present, - isAppFrame, - metrics); + metrics.msCPUBusy = ComputeMsCpuBusy(qpc, chainState, present, isAppFrame); + metrics.msCPUWait = ComputeMsCpuWait(qpc, present, isAppFrame); + metrics.msGPULatency = ComputeMsGpuLatency(qpc, chainState, present, isAppFrame); - ComputeMsGpuWait( - qpc, - present, - isAppFrame, - metrics); + metrics.msGPUBusy = ComputeMsGpuBusy(qpc, present, isAppFrame); + metrics.msVideoBusy = ComputeMsVideoBusy(qpc, present, isAppFrame); + metrics.msGPUWait = ComputeMsGpuWait(qpc, present, isAppFrame); } - void CalculateAnimationMetrics( const QpcConverter& qpc, const SwapChainCoreState& swapChain, @@ -986,22 +918,20 @@ namespace pmon::util::metrics uint64_t screenTime, FrameMetrics& metrics) { - ComputeAnimationError( + metrics.msAnimationError = ComputeAnimationError( qpc, swapChain, present, isDisplayed, isAppFrame, - screenTime, - metrics); + screenTime); - ComputeAnimationTime( + metrics.msAnimationTime = ComputeAnimationTime( qpc, swapChain, present, isDisplayed, - isAppFrame, - metrics); + isAppFrame); } void CalculateInputLatencyMetrics( @@ -1242,7 +1172,9 @@ namespace pmon::util::metrics return result; } - // Helper: Calculate CPU start time + // ============================================================================ + // 4) Exported helper definitions (declared in MetricsCalculator.h) + // ============================================================================ uint64_t CalculateCPUStart( const SwapChainCoreState& chainState, const FrameData& present) From 6698ef6ce54853420379b6ff2b25b199efec8eee Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Mon, 5 Jan 2026 13:01:02 -0800 Subject: [PATCH 102/205] More cleanup of arguments on various function calls. --- .../CommonUtilities/mc/MetricsCalculator.cpp | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 8aea10df..694f8267 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -505,11 +505,10 @@ namespace pmon::util::metrics return qpc.DeltaUnsignedMilliSeconds(present.appRenderSubmitStartTime, screenTime); } - std::optional ComputeInstrumentedSleep(const QpcConverter& qpc, + std::optional ComputeInstrumentedSleep( + const QpcConverter& qpc, const FrameData& present, - bool isDisplayed, - bool isAppFrame, - uint64_t screenTime) + bool isAppFrame) { if (!isAppFrame) { return std::nullopt; @@ -526,7 +525,6 @@ namespace pmon::util::metrics std::optional ComputeInstrumentedGpuLatency( const QpcConverter& qpc, const FrameData& present, - bool isDisplayed, bool isAppFrame) { if (!isAppFrame) { @@ -943,13 +941,14 @@ namespace pmon::util::metrics FrameMetrics& metrics, ComputedMetrics::StateDeltas& stateDeltas) { + const uint64_t screenTime = metrics.screenTimeQpc; metrics.msClickToPhotonLatency = ComputeClickToPhotonLatency( qpc, swapChain, present, isDisplayed, isAppFrame, - metrics.screenTimeQpc, + screenTime, stateDeltas); metrics.msAllInputPhotonLatency = ComputeAllInputToPhotonLatency( @@ -958,7 +957,7 @@ namespace pmon::util::metrics present, isDisplayed, isAppFrame, - metrics.screenTimeQpc, + screenTime, stateDeltas); metrics.msInstrumentedInputTime = ComputeInstrumentedInputToPhotonLatency( @@ -967,7 +966,7 @@ namespace pmon::util::metrics present, isDisplayed, isAppFrame, - metrics.screenTimeQpc, + screenTime, stateDeltas); } @@ -1077,14 +1076,11 @@ namespace pmon::util::metrics metrics.msInstrumentedSleep = ComputeInstrumentedSleep( qpc, present, - isDisplayed, - isAppFrame, - screenTime); + isAppFrame); metrics.msInstrumentedGpuLatency = ComputeInstrumentedGpuLatency( qpc, present, - isDisplayed, isAppFrame); metrics.msBetweenSimStarts = ComputeMsBetweenSimulationStarts( From 41d8774a41a7d8601ff5a96162eb8c6316e50d8f Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 6 Jan 2026 07:16:02 +0900 Subject: [PATCH 103/205] log caps --- .../Interprocess/source/Interprocess.cpp | 10 +++++++++ .../source/MetricCapabilities.cpp | 21 ++++++++++++++++++- .../Interprocess/source/MetricCapabilities.h | 6 ++++-- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/Interprocess.cpp b/IntelPresentMon/Interprocess/source/Interprocess.cpp index 00c44134..2c535e89 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.cpp +++ b/IntelPresentMon/Interprocess/source/Interprocess.cpp @@ -66,6 +66,10 @@ namespace pmon::ipc { auto lck = LockIntrospectionMutexExclusive_(); const auto deviceId = nextDeviceIndex_++; + pmlog_dbg("GPU metric capabilities") + .pmwatch(deviceId) + .pmwatch(deviceName) + .pmwatch(caps.ToString(26)); intro::PopulateGpuDevice( shm_.get_segment_manager(), *pRoot_, deviceId, vendor, deviceName, caps @@ -105,6 +109,12 @@ namespace pmon::ipc const MetricCapabilities& caps) override { auto lck = LockIntrospectionMutexExclusive_(); + constexpr size_t kWatchIndent = 5; + constexpr size_t kCapsTextIndent = kWatchIndent + (sizeof("capsText => ") - 1); + const auto capsText = caps.ToString(kCapsTextIndent); + pmlog_dbg("CPU metric capabilities") + .pmwatch(deviceName) + .pmwatch(capsText); intro::PopulateCpu( shm_.get_segment_manager(), *pRoot_, vendor, std::move(deviceName), caps ); diff --git a/IntelPresentMon/Interprocess/source/MetricCapabilities.cpp b/IntelPresentMon/Interprocess/source/MetricCapabilities.cpp index e1a2e0ab..bafac4c8 100644 --- a/IntelPresentMon/Interprocess/source/MetricCapabilities.cpp +++ b/IntelPresentMon/Interprocess/source/MetricCapabilities.cpp @@ -1,4 +1,6 @@ -#include "MetricCapabilities.h" +#include "MetricCapabilities.h" +#include +#include namespace pmon::ipc { @@ -37,4 +39,21 @@ namespace pmon::ipc } return it->second; } + + std::string MetricCapabilities::ToString(size_t indentSpaces) const + { + std::ostringstream oss; + const std::string indent(indentSpaces, ' '); + bool first = true; + for (const auto& kv : caps_) { + if (!first) { + oss << "\r\n"; + oss << indent; + } + first = false; + oss << "metricId=" << static_cast>(kv.first) + << " arraySize=" << kv.second; + } + return oss.str(); + } } diff --git a/IntelPresentMon/Interprocess/source/MetricCapabilities.h b/IntelPresentMon/Interprocess/source/MetricCapabilities.h index dfc410cf..1be3e8e1 100644 --- a/IntelPresentMon/Interprocess/source/MetricCapabilities.h +++ b/IntelPresentMon/Interprocess/source/MetricCapabilities.h @@ -1,6 +1,7 @@ -#pragma once +#pragma once #include "../../PresentMonAPI2/PresentMonAPI.h" #include +#include namespace pmon::ipc { @@ -13,6 +14,7 @@ namespace pmon::ipc void Set(PM_METRIC metricId, size_t arraySize); void Merge(const MetricCapabilities& capsToMerge); size_t Check(PM_METRIC metricId) const noexcept; + std::string ToString(size_t indentSpaces = 0) const; auto begin() const noexcept { return caps_.begin(); @@ -24,4 +26,4 @@ namespace pmon::ipc private: std::unordered_map caps_; }; -} \ No newline at end of file +} From ee39f13456cfbfc46c47c7f69a9f8816e071d004 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Mon, 5 Jan 2026 15:10:11 -0800 Subject: [PATCH 104/205] Breakout of metrics calculator functions --- .../CommonUtilities/CommonUtilities.vcxproj | 6 + .../CommonUtilities.vcxproj.filters | 18 + .../CommonUtilities/mc/MetricsCalculator.cpp | 873 +----------------- .../mc/MetricsCalculatorAnimation.cpp | 134 +++ .../mc/MetricsCalculatorCpuGpu.cpp | 164 ++++ .../mc/MetricsCalculatorDisplay.cpp | 207 +++++ .../mc/MetricsCalculatorInput.cpp | 186 ++++ .../mc/MetricsCalculatorInstrumented.cpp | 250 +++++ .../mc/MetricsCalculatorInternal.h | 76 ++ 9 files changed, 1044 insertions(+), 870 deletions(-) create mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsCalculatorAnimation.cpp create mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsCalculatorCpuGpu.cpp create mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsCalculatorDisplay.cpp create mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsCalculatorInput.cpp create mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsCalculatorInstrumented.cpp create mode 100644 IntelPresentMon/CommonUtilities/mc/MetricsCalculatorInternal.h diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj index 10b2ecfb..98c48b80 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj @@ -69,6 +69,7 @@ + @@ -155,6 +156,11 @@ + + + + + diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters index 17d10f4a..6e60966d 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters @@ -306,6 +306,9 @@ Header Files + + Source Files + @@ -494,6 +497,21 @@ Source Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 694f8267..1901bd05 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -1,6 +1,7 @@ -// Copyright (C) 2025 Intel Corporation +// Copyright (C) 2025 Intel Corporation // SPDX-License-Identifier: MIT #include "MetricsCalculator.h" +#include "MetricsCalculatorInternal.h" #include "../PresentData/PresentMonTraceConsumer.hpp" #include "../IntelPresentMon/PresentMonUtils/StreamFormat.h" @@ -15,569 +16,6 @@ namespace pmon::util::metrics // ============================================================================ namespace { - // Helper dedicated to computing msUntilDisplayed, matching legacy ReportMetricsHelper behavior. - - // ---- Display metrics ---- - double ComputeMsUntilDisplayed( - const QpcConverter& qpc, - const FrameData& present, - bool isDisplayed, - uint64_t screenTime) - { - return isDisplayed - ? qpc.DeltaUnsignedMilliSeconds(present.presentStartTime, screenTime) - : 0.0; - } - - - // Helper dedicated to computing msBetweenDisplayChange, matching legacy ReportMetricsHelper behavior. - double ComputeMsBetweenDisplayChange( - const QpcConverter& qpc, - const SwapChainCoreState& chain, - bool isDisplayed, - uint64_t screenTime) - { - return isDisplayed - ? qpc.DeltaUnsignedMilliSeconds(chain.lastDisplayedScreenTime, screenTime) - : 0.0; - } - - - // Helper dedicated to computing msDisplayedTime, matching legacy ReportMetricsHelper behavior. - double ComputeMsDisplayedTime( - const QpcConverter& qpc, - bool isDisplayed, - uint64_t screenTime, - uint64_t nextScreenTime) - { - return isDisplayed - ? qpc.DeltaUnsignedMilliSeconds(screenTime, nextScreenTime) - : 0.0; - } - - - std::optional ComputeMsFlipDelay( - const QpcConverter& qpc, - const FrameData& present, - bool isDisplayed) - { - if (isDisplayed && present.flipDelay != 0) { - return qpc.DurationMilliSeconds(present.flipDelay); - } - return std::nullopt; - } - - - double ComputeMsDisplayLatency( - const QpcConverter& qpc, - const SwapChainCoreState& swapChain, - const FrameData& present, - bool isDisplayed, - uint64_t screenTime) - { - const auto cpuStart = CalculateCPUStart(swapChain, present); - return (isDisplayed && cpuStart != 0) - ? qpc.DeltaUnsignedMilliSeconds(cpuStart, screenTime) - : 0.0; - } - - - std::optional ComputeMsReadyTimeToDisplayLatency( - const QpcConverter& qpc, - const FrameData& present, - bool isDisplayed, - uint64_t screenTime) - { - if (isDisplayed && present.readyTime != 0) { - return qpc.DeltaUnsignedMilliSeconds(present.readyTime, screenTime); - } - return std::nullopt; - } - - - // ---- CPU/GPU metrics ---- - double ComputeMsCpuBusy( - const QpcConverter& qpc, - const SwapChainCoreState& swapChain, - const FrameData& present, - bool isAppPresent) - { - if (!isAppPresent) { - return 0.0; - } - - const auto cpuStart = CalculateCPUStart(swapChain, present); - if (cpuStart == 0) { - return 0.0; - } - - if (present.appPropagatedPresentStartTime != 0) { - return qpc.DeltaUnsignedMilliSeconds(cpuStart, present.appPropagatedPresentStartTime); - } - if (present.presentStartTime != 0) { - return qpc.DeltaUnsignedMilliSeconds(cpuStart, present.presentStartTime); - } - return 0.0; - } - - - double ComputeMsCpuWait( - const QpcConverter& qpc, - const FrameData& present, - bool isAppPresent) - { - if (!isAppPresent) { - return 0.0; - } - - if (present.appPropagatedTimeInPresent != 0) { - return qpc.DurationMilliSeconds(present.appPropagatedTimeInPresent); - } - if (present.timeInPresent != 0) { - return qpc.DurationMilliSeconds(present.timeInPresent); - } - return 0.0; - } - - - double ComputeMsGpuLatency( - const QpcConverter& qpc, - const SwapChainCoreState& swapChain, - const FrameData& present, - bool isAppPresent) - { - if (!isAppPresent) { - return 0.0; - } - - const auto cpuStart = CalculateCPUStart(swapChain, present); - if (cpuStart == 0) { - return 0.0; - } - - if (present.appPropagatedGPUStartTime != 0) { - return qpc.DeltaUnsignedMilliSeconds(cpuStart, present.appPropagatedGPUStartTime); - } - if (present.gpuStartTime != 0) { - return qpc.DeltaUnsignedMilliSeconds(cpuStart, present.gpuStartTime); - } - return 0.0; - } - - - double ComputeMsGpuBusy( - const QpcConverter& qpc, - const FrameData& present, - bool isAppPresent) - { - //out.msGPUBusy = std::nullopt; - double msGPUBusy = 0.0; - if (isAppPresent) { - if (present.appPropagatedGPUDuration != 0) { - msGPUBusy = qpc.DurationMilliSeconds(present.appPropagatedGPUDuration); - } - else if (present.gpuDuration != 0) { - msGPUBusy = qpc.DurationMilliSeconds(present.gpuDuration); - } - } - return msGPUBusy; - } - - double ComputeMsVideoBusy( - const QpcConverter& qpc, - const FrameData& present, - bool isAppPresent) - { - if (!isAppPresent) { - return 0.0; - } - - if (present.appPropagatedGPUVideoDuration != 0) { - return qpc.DurationMilliSeconds(present.appPropagatedGPUVideoDuration); - } - if (present.gpuVideoDuration != 0) { - return qpc.DurationMilliSeconds(present.gpuVideoDuration); - } - return 0.0; - } - - - double ComputeMsGpuDuration ( - const QpcConverter& qpc, - const FrameData& present, - bool isAppPresent) - { - //msGPUDuration = std::nullopt; - double msGPUDuration = 0.0; - if (isAppPresent) { - if (present.appPropagatedGPUStartTime != 0 || present.appPropagatedReadyTime != 0) { - msGPUDuration = qpc.DeltaUnsignedMilliSeconds(present.appPropagatedGPUStartTime, present.appPropagatedReadyTime); - } - else if (present.gpuStartTime != 0 || present.readyTime != 0) { - msGPUDuration = qpc.DeltaUnsignedMilliSeconds(present.gpuStartTime, present.readyTime); - } - } - return msGPUDuration; - } - - double ComputeMsGpuWait( - const QpcConverter& qpc, - const FrameData& present, - bool isAppPresent) - { - return std::max(0.0, - ComputeMsGpuDuration(qpc, present, isAppPresent) - - ComputeMsGpuBusy(qpc, present, isAppPresent)); - } - - - // ---- Animation metrics ---- - std::optional ComputeAnimationError( - const QpcConverter& qpc, - const SwapChainCoreState& chain, - const FrameData& present, - bool isDisplayed, - bool isAppFrame, - uint64_t screenTime) - { - if (!isDisplayed || !isAppFrame) { - return std::nullopt; - } - - uint64_t currentSimStart = CalculateAnimationErrorSimStartTime(chain, present, chain.animationErrorSource); - - if (currentSimStart == 0 || - chain.lastDisplayedSimStartTime == 0 || - currentSimStart <= chain.lastDisplayedSimStartTime || - chain.lastDisplayedAppScreenTime == 0) { - return std::nullopt; - } - - double simElapsed = qpc.DeltaUnsignedMilliSeconds(chain.lastDisplayedSimStartTime, currentSimStart); - double displayElapsed = qpc.DeltaUnsignedMilliSeconds(chain.lastDisplayedAppScreenTime, screenTime); - - if (simElapsed == 0.0 || displayElapsed == 0.0) { - return std::nullopt; - } - - return simElapsed - displayElapsed; - } - - - std::optional ComputeAnimationTime( - const QpcConverter& qpc, - const SwapChainCoreState& chain, - const FrameData& present, - bool isDisplayed, - bool isAppFrame) - { - if (!isDisplayed || !isAppFrame) { - return std::nullopt; - } - - bool isFirstProviderSimTime = - chain.animationErrorSource == AnimationErrorSource::CpuStart && - (present.appSimStartTime != 0 || present.pclSimStartTime != 0); - if (isFirstProviderSimTime) { - // Seed only: no animation time yet. UpdateAfterPresent will flip us - // into AppProvider/PCL and latch firstAppSimStartTime. - return std::nullopt; - } - - uint64_t currentSimStart = CalculateAnimationErrorSimStartTime(chain, present, chain.animationErrorSource); - if (currentSimStart == 0) { - return std::nullopt; - } - - return CalculateAnimationTime(qpc, chain.firstAppSimStartTime, currentSimStart); - } - - - // ---- NV collapsed/runt correction ---- - void AdjustScreenTimeForCollapsedPresentNV( - FrameData& present, - FrameData* nextDisplayedPresent, - const uint64_t& lastDisplayedFlipDelay, - const uint64_t& lastDisplayedScreenTime, - uint64_t& screenTime, - uint64_t& nextScreenTime, - MetricsVersion version) - { - if (version == MetricsVersion::V1) { - // NV1 collapsed/runt correction: legacy V1 adjusts the *current* present using the - // previous displayed state when the last displayed screen time (adjusted by flip delay) - // is greater than this present's screen time. - if (lastDisplayedFlipDelay > 0 && - lastDisplayedScreenTime > screenTime && - !present.displayed.empty()) { - - const uint64_t diff = lastDisplayedScreenTime - screenTime; - present.flipDelay += diff; - present.displayed[0].second = lastDisplayedScreenTime; - screenTime = present.displayed[0].second; - } - return; - } - - // nextDisplayedPresent should always be non-null for NV GPU. - if (present.flipDelay && screenTime > nextScreenTime && nextDisplayedPresent) { - // If screenTime that is adjusted by flipDelay is larger than nextScreenTime, - // it implies this present is a collapsed present, or a runt frame. - // So we adjust the screenTime and flipDelay of nextDisplayedPresent, - // effectively making nextScreenTime equals to screenTime. - - nextDisplayedPresent->flipDelay += (screenTime - nextScreenTime); - nextScreenTime = screenTime; - nextDisplayedPresent->displayed[0].second = nextScreenTime; - } - } - - // ---- Input latency metrics ---- - std::optional ComputeClickToPhotonLatency( - const QpcConverter& qpc, - const SwapChainCoreState& chain, - const FrameData& present, - bool isDisplayed, - bool isAppFrame, - uint64_t screenTime, - ComputedMetrics::StateDeltas& stateDeltas) - { - // Only app frames participate in click-to-photon. - if (!isAppFrame) { - return std::nullopt; - } - - uint64_t inputTime = 0; - - // Case 1: This frame *has* a click. - if (present.mouseClickTime != 0) { - inputTime = present.mouseClickTime; - - if (!isDisplayed) { - // Not displayed: stash the click for a future displayed frame. - stateDeltas.lastReceivedNotDisplayedMouseClickTime = inputTime; - return std::nullopt; - } else { - stateDeltas.shouldResetInputTimes = true; - } - } - // Case 2: No click on this frame, but frame is displayed: see if we can - // reuse a pending click from a previously dropped frame. - else if (isDisplayed && chain.lastReceivedNotDisplayedMouseClickTime != 0) { - inputTime = chain.lastReceivedNotDisplayedMouseClickTime; - stateDeltas.shouldResetInputTimes = true; - } - - // If we still have no inputTime, nothing to compute. - if (inputTime == 0) { - return std::nullopt; - } - - return qpc.DeltaUnsignedMilliSeconds(inputTime, screenTime); - } - - - std::optional ComputeAllInputToPhotonLatency( - const QpcConverter& qpc, - const SwapChainCoreState& chain, - const FrameData& present, - bool isDisplayed, - bool isAppFrame, - uint64_t screenTime, - ComputedMetrics::StateDeltas& stateDeltas) - { - // Only app frames participate in click-to-photon. - if (!isAppFrame) { - return std::nullopt; - } - - uint64_t inputTime = 0; - - // Case 1: This frame *has* a click. - if (present.inputTime != 0) { - inputTime = present.inputTime; - - if (!isDisplayed) { - // Not displayed: stash the click for a future displayed frame. - stateDeltas.lastReceivedNotDisplayedAllInputTime = inputTime; - return std::nullopt; - } else { - stateDeltas.shouldResetInputTimes = true; - } - } - // Case 2: No click on this frame, but frame is displayed: see if we can - // reuse a pending click from a previously dropped frame. - else if (isDisplayed && chain.lastReceivedNotDisplayedAllInputTime != 0) { - inputTime = chain.lastReceivedNotDisplayedAllInputTime; - stateDeltas.shouldResetInputTimes = true; - } - - // If we still have no inputTime, nothing to compute. - if (inputTime == 0) { - return std::nullopt; - } - - return qpc.DeltaUnsignedMilliSeconds(inputTime, screenTime); - } - - std::optional ComputeInstrumentedInputToPhotonLatency( - const QpcConverter& qpc, - const SwapChainCoreState& chain, - const FrameData& present, - bool isDisplayed, - bool isAppFrame, - uint64_t screenTime, - ComputedMetrics::StateDeltas& stateDeltas) - { - // Only app frames participate in click-to-photon. - if (!isAppFrame) { - return std::nullopt; - } - - uint64_t inputTime = 0; - - // Case 1: This frame *has* a click. - if (present.appInputSample.first != 0) { - inputTime = present.appInputSample.first; - - if (!isDisplayed) { - // Not displayed: stash the click for a future displayed frame. - stateDeltas.lastReceivedNotDisplayedAppProviderInputTime = inputTime; - return std::nullopt; - } else { - stateDeltas.shouldResetInputTimes = true; - } - } - // Case 2: No click on this frame, but frame is displayed: see if we can - // reuse a pending click from a previously dropped frame. - else if (isDisplayed && chain.lastReceivedNotDisplayedAppProviderInputTime != 0) { - inputTime = chain.lastReceivedNotDisplayedAppProviderInputTime; - stateDeltas.shouldResetInputTimes = true; - } - - // If we still have no inputTime, nothing to compute. - if (inputTime == 0) { - return std::nullopt; - } - - return qpc.DeltaUnsignedMilliSeconds(inputTime, screenTime); - } - - // ---- Instrumented metrics ---- - std::optional ComputeInstrumentedLatency( - const QpcConverter& qpc, - const FrameData& present, - bool isDisplayed, - bool isAppFrame, - uint64_t screenTime) - { - if (!isDisplayed || !isAppFrame) { - return std::nullopt; - } - - auto instrumentedStartTime = present.appSleepEndTime != 0 ? - present.appSleepEndTime : present.appSimStartTime; - - if (instrumentedStartTime == 0) { - // No instrumented start time: nothing to compute. - return std::nullopt; - } - - return qpc.DeltaUnsignedMilliSeconds(instrumentedStartTime, screenTime); - } - - std::optional ComputeInstrumentedRenderLatency( - const QpcConverter& qpc, - const FrameData& present, - bool isDisplayed, - bool isAppFrame, - uint64_t screenTime) - { - if (!isDisplayed || !isAppFrame) { - return std::nullopt; - } - - if (present.appRenderSubmitStartTime == 0) { - // No app provided render submit start time: nothing to compute. - return std::nullopt; - } - - return qpc.DeltaUnsignedMilliSeconds(present.appRenderSubmitStartTime, screenTime); - } - - std::optional ComputeInstrumentedSleep( - const QpcConverter& qpc, - const FrameData& present, - bool isAppFrame) - { - if (!isAppFrame) { - return std::nullopt; - } - - if (present.appSleepStartTime == 0 || present.appSleepEndTime == 0) { - // No app provided sleep times: nothing to compute. - return std::nullopt; - } - - return qpc.DeltaUnsignedMilliSeconds(present.appSleepStartTime, present.appSleepEndTime); - } - - std::optional ComputeInstrumentedGpuLatency( - const QpcConverter& qpc, - const FrameData& present, - bool isAppFrame) - { - if (!isAppFrame) { - return std::nullopt; - } - - auto instrumentedStartTime = present.appSleepEndTime != 0 ? - present.appSleepEndTime : present.appSimStartTime; - - if (instrumentedStartTime == 0) { - // No provider sleep end or sim start time: nothing to compute. - return std::nullopt; - } - - if (present.gpuStartTime == 0) { - // No GPU start time: nothing to compute. - return std::nullopt; - } - - return qpc.DeltaUnsignedMilliSeconds(instrumentedStartTime, present.gpuStartTime); - } - - // ---- Simulation metrics ---- - std::optional ComputeMsBetweenSimulationStarts( - const QpcConverter& qpc, - const SwapChainCoreState& chain, - const FrameData& present, - bool isAppFrame) - { - if (!isAppFrame) { - return std::nullopt; - } - - // The current sim start time is only dependent on the current frame's simulation start times. - // Preference is PCL, then App. - uint64_t currentSimStartTime = 0; - if (present.pclSimStartTime != 0) { - currentSimStartTime = present.pclSimStartTime; - } - else if (present.appSimStartTime != 0) { - currentSimStartTime = present.appSimStartTime; - } - if (chain.lastSimStartTime != 0 && currentSimStartTime != 0 && - currentSimStartTime > chain.lastSimStartTime) { - return qpc.DeltaUnsignedMilliSeconds( - chain.lastSimStartTime, - currentSimStartTime); - } - else { - return std::nullopt; - } - } - // ---- Swap-chain state application ---- void ApplyStateDeltas( SwapChainCoreState& chainState, @@ -639,63 +77,8 @@ namespace pmon::util::metrics } - // ============================================================================ // 2) Public entry points // ============================================================================ - DisplayIndexing DisplayIndexing::Calculate( - const FrameData& present, - const FrameData* nextDisplayed) - { - DisplayIndexing result{}; - - // Get display count - auto displayCount = present.displayed.size(); // ConsoleAdapter/PresentSnapshot method - - // Check if displayed - bool displayed = present.finalState == PresentResult::Presented && displayCount > 0; - - // hasNextDisplayed - result.hasNextDisplayed = (nextDisplayed != nullptr); - - // Figure out range to process based on three cases: - // Case 1: Not displayed → empty range [0, 0) - // Case 2: Displayed, no next → process [0..N-2], postpone N-1 → range [0, N-1) - // Case 3: Displayed, with next → process postponed [N-1] → range [N-1, N) - - if (!displayed || displayCount == 0) { - // Case 1: Not displayed - result.startIndex = 0; - result.endIndex = 0; // Empty range - } - else if (nextDisplayed == nullptr) { - // Case 2: Postpone last display - result.startIndex = 0; - result.endIndex = displayCount - 1; // One past [N-2] = [N-1] (excludes last!) - } - else { - // Case 3: Process postponed last display - result.startIndex = displayCount - 1; - result.endIndex = displayCount; // One past [N-1] = [N] - } - - // appIndex - find first NotSet or Application frame - // Search from startIndex through ALL displays (not just the processing range) - result.appIndex = std::numeric_limits::max(); - if (displayCount > 0) { - for (size_t i = result.startIndex; i < displayCount; ++i) { - auto frameType = present.displayed[i].first; - if (frameType == FrameType::NotSet || frameType == FrameType::Application) { - result.appIndex = i; - break; - } - } - } - else { - result.appIndex = 0; - } - return result; - } - std::vector ComputeMetricsForPresent( const QpcConverter& qpc, FrameData& present, @@ -840,256 +223,6 @@ namespace pmon::util::metrics // ============================================================================ // 3) Metric assembly helpers (ComputeFrameMetrics) // ============================================================================ - void CalculateBasePresentMetrics( - const QpcConverter& qpc, - const FrameData& present, - const SwapChainCoreState& swapChain, - FrameMetrics& out) - { - out.timeInSeconds = present.presentStartTime; - - // Calculate the delta from the previous present (if one exists) - // to the current present - if (swapChain.lastPresent.has_value()) { - out.msBetweenPresents = qpc.DeltaUnsignedMilliSeconds( - swapChain.lastPresent->presentStartTime, - present.presentStartTime); - } else { - out.msBetweenPresents = 0.0; - } - - out.msInPresentApi = qpc.DurationMilliSeconds(present.timeInPresent); - out.msUntilRenderStart = qpc.DeltaSignedMilliSeconds( - present.presentStartTime, - present.gpuStartTime); - out.msUntilRenderComplete = qpc.DeltaSignedMilliSeconds( - present.presentStartTime, - present.readyTime); - out.msGpuDuration = qpc.DurationMilliSeconds(present.gpuDuration); - out.msVideoDuration = qpc.DurationMilliSeconds(present.gpuVideoDuration); - out.msSinceInput = (present.inputTime == 0) - ? 0.0 - : qpc.DurationMilliSeconds(present.presentStartTime - present.inputTime); - } - - void CalculateDisplayMetrics( - const QpcConverter& qpc, - const FrameData& present, - const SwapChainCoreState& swapChain, - bool isDisplayed, - uint64_t screenTime, - uint64_t nextScreenTime, - FrameMetrics& metrics) - { - metrics.msUntilDisplayed = ComputeMsUntilDisplayed(qpc, present, isDisplayed, screenTime); - metrics.msBetweenDisplayChange = ComputeMsBetweenDisplayChange(qpc, swapChain, isDisplayed, screenTime); - metrics.msDisplayedTime = ComputeMsDisplayedTime(qpc, isDisplayed, screenTime, nextScreenTime); - metrics.msFlipDelay = ComputeMsFlipDelay(qpc, present, isDisplayed); - metrics.msDisplayLatency = ComputeMsDisplayLatency(qpc, swapChain, present, isDisplayed, screenTime); - metrics.msReadyTimeToDisplayLatency = ComputeMsReadyTimeToDisplayLatency(qpc, present, isDisplayed, screenTime); - - metrics.screenTimeQpc = screenTime; - } - - void CalculateCpuGpuMetrics( - const QpcConverter& qpc, - const SwapChainCoreState& chainState, - const FrameData& present, - bool isAppFrame, - FrameMetrics& metrics) - { - metrics.msCPUBusy = ComputeMsCpuBusy(qpc, chainState, present, isAppFrame); - metrics.msCPUWait = ComputeMsCpuWait(qpc, present, isAppFrame); - metrics.msGPULatency = ComputeMsGpuLatency(qpc, chainState, present, isAppFrame); - - metrics.msGPUBusy = ComputeMsGpuBusy(qpc, present, isAppFrame); - metrics.msVideoBusy = ComputeMsVideoBusy(qpc, present, isAppFrame); - metrics.msGPUWait = ComputeMsGpuWait(qpc, present, isAppFrame); - } - - void CalculateAnimationMetrics( - const QpcConverter& qpc, - const SwapChainCoreState& swapChain, - const FrameData& present, - bool isDisplayed, - bool isAppFrame, - uint64_t screenTime, - FrameMetrics& metrics) - { - metrics.msAnimationError = ComputeAnimationError( - qpc, - swapChain, - present, - isDisplayed, - isAppFrame, - screenTime); - - metrics.msAnimationTime = ComputeAnimationTime( - qpc, - swapChain, - present, - isDisplayed, - isAppFrame); - } - - void CalculateInputLatencyMetrics( - const QpcConverter& qpc, - const SwapChainCoreState& swapChain, - const FrameData& present, - bool isDisplayed, - bool isAppFrame, - FrameMetrics& metrics, - ComputedMetrics::StateDeltas& stateDeltas) - { - const uint64_t screenTime = metrics.screenTimeQpc; - metrics.msClickToPhotonLatency = ComputeClickToPhotonLatency( - qpc, - swapChain, - present, - isDisplayed, - isAppFrame, - screenTime, - stateDeltas); - - metrics.msAllInputPhotonLatency = ComputeAllInputToPhotonLatency( - qpc, - swapChain, - present, - isDisplayed, - isAppFrame, - screenTime, - stateDeltas); - - metrics.msInstrumentedInputTime = ComputeInstrumentedInputToPhotonLatency( - qpc, - swapChain, - present, - isDisplayed, - isAppFrame, - screenTime, - stateDeltas); - } - - std::optional CalculatePcLatency( - const QpcConverter& qpc, - const SwapChainCoreState& chain, - const FrameData& present, - bool isDisplayed, - uint64_t screenTime, - ComputedMetrics::StateDeltas& stateDeltas) - { - if (!isDisplayed) { - if (present.pclSimStartTime != 0) { - if (present.pclInputPingTime != 0) { - // This frame was dropped but we have valid pc latency input and simulation start - // times. Calculate the initial input to sim start time - stateDeltas.newAccumulatedInput2FrameStart = qpc.DeltaUnsignedMilliSeconds( - present.pclInputPingTime, - present.pclSimStartTime); - } - else if (chain.accumulatedInput2FrameStartTime != 0.f) { - // This frame was also dropped and there is no pc latency input time. However, since we have - // accumulated time this means we have a pending input that has had multiple dropped frames - // and has not yet hit the screen. Calculate the time between the last not displayed sim start and - // this sim start and add it to our accumulated total - stateDeltas.newAccumulatedInput2FrameStart = chain.accumulatedInput2FrameStartTime + - qpc.DeltaUnsignedMilliSeconds( - chain.lastReceivedNotDisplayedPclSimStart, - present.pclSimStartTime); - } - stateDeltas.newLastReceivedPclSimStart = present.pclSimStartTime; - } - return std::nullopt; - } - - // Check to see if we have a valid PC Latency sim start time - if (present.pclSimStartTime != 0) { - if (present.pclInputPingTime != 0) { - // Both the pclSimStartTime and pclInputPingTime are valid, use them to update - // the Input to Frame Start EMA. Store in state deltas for later application. - - stateDeltas.newInput2FrameStartEma = pmon::util::CalculateEma( - chain.Input2FrameStartTimeEma, - qpc.DeltaUnsignedMilliSeconds(present.pclInputPingTime, present.pclSimStartTime), - 0.1); - - // Defensively clear the tracking variables for when we have a dropped frame with a pc latency input - stateDeltas.newAccumulatedInput2FrameStart = 0.0; - stateDeltas.newLastReceivedPclSimStart = 0; - } - else { - if (chain.accumulatedInput2FrameStartTime != 0.0) { - // This frame was displayed but we don't have a pc latency input time. However, there is accumulated time - // so there is a pending input that will now hit the screen. Add in the time from the last not - // displayed pc simulation start to this frame's pc simulation start. - stateDeltas.newAccumulatedInput2FrameStart = chain.accumulatedInput2FrameStartTime + - qpc.DeltaUnsignedMilliSeconds( - chain.lastReceivedNotDisplayedPclSimStart, - present.pclSimStartTime); - - stateDeltas.newInput2FrameStartEma = pmon::util::CalculateEma( - chain.Input2FrameStartTimeEma, - *stateDeltas.newAccumulatedInput2FrameStart, - 0.1); - - // Reset the tracking variables for when we have a dropped frame with a pc latency input - stateDeltas.newAccumulatedInput2FrameStart = 0.0; - stateDeltas.newLastReceivedPclSimStart = 0; - } - } - } - - auto simStartTime = present.pclSimStartTime != 0 ? present.pclSimStartTime : chain.lastSimStartTime; - double input2FrameStartEma = stateDeltas.newInput2FrameStartEma.has_value() ? - stateDeltas.newInput2FrameStartEma.value() : chain.Input2FrameStartTimeEma; - if (input2FrameStartEma != 0.0 && simStartTime != 0) { - return input2FrameStartEma + qpc.DeltaSignedMilliSeconds(simStartTime, screenTime); - } - else { - return std::nullopt; - } - } - - void CalculateInstrumentedMetrics( - const QpcConverter& qpc, - const SwapChainCoreState& chain, - const FrameData& present, - bool isDisplayed, - bool isAppFrame, - uint64_t screenTime, - FrameMetrics& metrics) { - - metrics.msInstrumentedLatency = ComputeInstrumentedLatency( - qpc, - present, - isDisplayed, - isAppFrame, - screenTime); - - metrics.msInstrumentedRenderLatency = ComputeInstrumentedRenderLatency( - qpc, - present, - isDisplayed, - isAppFrame, - screenTime); - - metrics.msInstrumentedSleep = ComputeInstrumentedSleep( - qpc, - present, - isAppFrame); - - metrics.msInstrumentedGpuLatency = ComputeInstrumentedGpuLatency( - qpc, - present, - isAppFrame); - - metrics.msBetweenSimStarts = ComputeMsBetweenSimulationStarts( - qpc, - chain, - present, - isAppFrame); - } - ComputedMetrics ComputeFrameMetrics( const QpcConverter& qpc, const FrameData& present, @@ -1226,4 +359,4 @@ namespace pmon::util::metrics } return animationTime; } -} \ No newline at end of file +} diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorAnimation.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorAnimation.cpp new file mode 100644 index 00000000..bf828064 --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorAnimation.cpp @@ -0,0 +1,134 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#include "MetricsCalculator.h" +#include "MetricsCalculatorInternal.h" + +#include "../PresentData/PresentMonTraceConsumer.hpp" +#include "../IntelPresentMon/PresentMonUtils/StreamFormat.h" +#include "../Math.h" + +namespace pmon::util::metrics +{ + namespace + { + // ---- Animation metrics ---- + std::optional ComputeAnimationError( + const QpcConverter& qpc, + const SwapChainCoreState& chain, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + uint64_t screenTime) + { + if (!isDisplayed || !isAppFrame) { + return std::nullopt; + } + + uint64_t currentSimStart = CalculateAnimationErrorSimStartTime(chain, present, chain.animationErrorSource); + + if (currentSimStart == 0 || + chain.lastDisplayedSimStartTime == 0 || + currentSimStart <= chain.lastDisplayedSimStartTime || + chain.lastDisplayedAppScreenTime == 0) { + return std::nullopt; + } + + double simElapsed = qpc.DeltaUnsignedMilliSeconds(chain.lastDisplayedSimStartTime, currentSimStart); + double displayElapsed = qpc.DeltaUnsignedMilliSeconds(chain.lastDisplayedAppScreenTime, screenTime); + + if (simElapsed == 0.0 || displayElapsed == 0.0) { + return std::nullopt; + } + + return simElapsed - displayElapsed; + } + + + std::optional ComputeAnimationTime( + const QpcConverter& qpc, + const SwapChainCoreState& chain, + const FrameData& present, + bool isDisplayed, + bool isAppFrame) + { + if (!isDisplayed || !isAppFrame) { + return std::nullopt; + } + + bool isFirstProviderSimTime = + chain.animationErrorSource == AnimationErrorSource::CpuStart && + (present.appSimStartTime != 0 || present.pclSimStartTime != 0); + if (isFirstProviderSimTime) { + // Seed only: no animation time yet. UpdateAfterPresent will flip us + // into AppProvider/PCL and latch firstAppSimStartTime. + return std::nullopt; + } + + uint64_t currentSimStart = CalculateAnimationErrorSimStartTime(chain, present, chain.animationErrorSource); + if (currentSimStart == 0) { + return std::nullopt; + } + + return CalculateAnimationTime(qpc, chain.firstAppSimStartTime, currentSimStart); + } + + + } + + void CalculateBasePresentMetrics( + const QpcConverter& qpc, + const FrameData& present, + const SwapChainCoreState& swapChain, + FrameMetrics& out) + { + out.timeInSeconds = present.presentStartTime; + + // Calculate the delta from the previous present (if one exists) + // to the current present + if (swapChain.lastPresent.has_value()) { + out.msBetweenPresents = qpc.DeltaUnsignedMilliSeconds( + swapChain.lastPresent->presentStartTime, + present.presentStartTime); + } else { + out.msBetweenPresents = 0.0; + } + + out.msInPresentApi = qpc.DurationMilliSeconds(present.timeInPresent); + out.msUntilRenderStart = qpc.DeltaSignedMilliSeconds( + present.presentStartTime, + present.gpuStartTime); + out.msUntilRenderComplete = qpc.DeltaSignedMilliSeconds( + present.presentStartTime, + present.readyTime); + out.msGpuDuration = qpc.DurationMilliSeconds(present.gpuDuration); + out.msVideoDuration = qpc.DurationMilliSeconds(present.gpuVideoDuration); + out.msSinceInput = (present.inputTime == 0) + ? 0.0 + : qpc.DurationMilliSeconds(present.presentStartTime - present.inputTime); + } + + void CalculateAnimationMetrics( + const QpcConverter& qpc, + const SwapChainCoreState& swapChain, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + uint64_t screenTime, + FrameMetrics& metrics) + { + metrics.msAnimationError = ComputeAnimationError( + qpc, + swapChain, + present, + isDisplayed, + isAppFrame, + screenTime); + + metrics.msAnimationTime = ComputeAnimationTime( + qpc, + swapChain, + present, + isDisplayed, + isAppFrame); + } +} diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorCpuGpu.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorCpuGpu.cpp new file mode 100644 index 00000000..559886a1 --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorCpuGpu.cpp @@ -0,0 +1,164 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#include "MetricsCalculator.h" +#include "MetricsCalculatorInternal.h" + +#include "../PresentData/PresentMonTraceConsumer.hpp" +#include "../IntelPresentMon/PresentMonUtils/StreamFormat.h" +#include "../Math.h" + +namespace pmon::util::metrics +{ + namespace + { + // ---- CPU/GPU metrics ---- + double ComputeMsCpuBusy( + const QpcConverter& qpc, + const SwapChainCoreState& swapChain, + const FrameData& present, + bool isAppPresent) + { + if (!isAppPresent) { + return 0.0; + } + + const auto cpuStart = CalculateCPUStart(swapChain, present); + if (cpuStart == 0) { + return 0.0; + } + + if (present.appPropagatedPresentStartTime != 0) { + return qpc.DeltaUnsignedMilliSeconds(cpuStart, present.appPropagatedPresentStartTime); + } + if (present.presentStartTime != 0) { + return qpc.DeltaUnsignedMilliSeconds(cpuStart, present.presentStartTime); + } + return 0.0; + } + + + double ComputeMsCpuWait( + const QpcConverter& qpc, + const FrameData& present, + bool isAppPresent) + { + if (!isAppPresent) { + return 0.0; + } + + if (present.appPropagatedTimeInPresent != 0) { + return qpc.DurationMilliSeconds(present.appPropagatedTimeInPresent); + } + if (present.timeInPresent != 0) { + return qpc.DurationMilliSeconds(present.timeInPresent); + } + return 0.0; + } + + + double ComputeMsGpuLatency( + const QpcConverter& qpc, + const SwapChainCoreState& swapChain, + const FrameData& present, + bool isAppPresent) + { + if (!isAppPresent) { + return 0.0; + } + + const auto cpuStart = CalculateCPUStart(swapChain, present); + if (cpuStart == 0) { + return 0.0; + } + + if (present.appPropagatedGPUStartTime != 0) { + return qpc.DeltaUnsignedMilliSeconds(cpuStart, present.appPropagatedGPUStartTime); + } + if (present.gpuStartTime != 0) { + return qpc.DeltaUnsignedMilliSeconds(cpuStart, present.gpuStartTime); + } + return 0.0; + } + + + double ComputeMsGpuBusy( + const QpcConverter& qpc, + const FrameData& present, + bool isAppPresent) + { + //out.msGPUBusy = std::nullopt; + double msGPUBusy = 0.0; + if (isAppPresent) { + if (present.appPropagatedGPUDuration != 0) { + msGPUBusy = qpc.DurationMilliSeconds(present.appPropagatedGPUDuration); + } + else if (present.gpuDuration != 0) { + msGPUBusy = qpc.DurationMilliSeconds(present.gpuDuration); + } + } + return msGPUBusy; + } + + double ComputeMsVideoBusy( + const QpcConverter& qpc, + const FrameData& present, + bool isAppPresent) + { + if (!isAppPresent) { + return 0.0; + } + + if (present.appPropagatedGPUVideoDuration != 0) { + return qpc.DurationMilliSeconds(present.appPropagatedGPUVideoDuration); + } + if (present.gpuVideoDuration != 0) { + return qpc.DurationMilliSeconds(present.gpuVideoDuration); + } + return 0.0; + } + + double ComputeMsGpuDuration( + const QpcConverter& qpc, + const FrameData& present, + bool isAppPresent) + { + //msGPUDuration = std::nullopt; + double msGPUDuration = 0.0; + if (isAppPresent) { + if (present.appPropagatedGPUStartTime != 0 || present.appPropagatedReadyTime != 0) { + msGPUDuration = qpc.DeltaUnsignedMilliSeconds(present.appPropagatedGPUStartTime, present.appPropagatedReadyTime); + } + else if (present.gpuStartTime != 0 || present.readyTime != 0) { + msGPUDuration = qpc.DeltaUnsignedMilliSeconds(present.gpuStartTime, present.readyTime); + } + } + return msGPUDuration; + } + + double ComputeMsGpuWait( + const QpcConverter& qpc, + const FrameData& present, + bool isAppPresent) + { + return std::max(0.0, + ComputeMsGpuDuration(qpc, present, isAppPresent) - + ComputeMsGpuBusy(qpc, present, isAppPresent)); + } + } + + void CalculateCpuGpuMetrics( + const QpcConverter& qpc, + const SwapChainCoreState& chainState, + const FrameData& present, + bool isAppFrame, + FrameMetrics& metrics) + { + metrics.msCPUBusy = ComputeMsCpuBusy(qpc, chainState, present, isAppFrame); + metrics.msCPUWait = ComputeMsCpuWait(qpc, present, isAppFrame); + metrics.msGPULatency = ComputeMsGpuLatency(qpc, chainState, present, isAppFrame); + + metrics.msGPUBusy = ComputeMsGpuBusy(qpc, present, isAppFrame); + metrics.msVideoBusy = ComputeMsVideoBusy(qpc, present, isAppFrame); + metrics.msGPUWait = ComputeMsGpuWait(qpc, present, isAppFrame); + } +} \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorDisplay.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorDisplay.cpp new file mode 100644 index 00000000..42e95983 --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorDisplay.cpp @@ -0,0 +1,207 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#include "MetricsCalculator.h" +#include "MetricsCalculatorInternal.h" + +#include "../PresentData/PresentMonTraceConsumer.hpp" +#include "../IntelPresentMon/PresentMonUtils/StreamFormat.h" +#include "../Math.h" + +namespace pmon::util::metrics +{ + namespace + { + // ---- Display metrics ---- + double ComputeMsUntilDisplayed( + const QpcConverter& qpc, + const FrameData& present, + bool isDisplayed, + uint64_t screenTime) + { + return isDisplayed + ? qpc.DeltaUnsignedMilliSeconds(present.presentStartTime, screenTime) + : 0.0; + } + + + // Helper dedicated to computing msBetweenDisplayChange, matching legacy ReportMetricsHelper behavior. + double ComputeMsBetweenDisplayChange( + const QpcConverter& qpc, + const SwapChainCoreState& chain, + bool isDisplayed, + uint64_t screenTime) + { + return isDisplayed + ? qpc.DeltaUnsignedMilliSeconds(chain.lastDisplayedScreenTime, screenTime) + : 0.0; + } + + + // Helper dedicated to computing msDisplayedTime, matching legacy ReportMetricsHelper behavior. + double ComputeMsDisplayedTime( + const QpcConverter& qpc, + bool isDisplayed, + uint64_t screenTime, + uint64_t nextScreenTime) + { + return isDisplayed + ? qpc.DeltaUnsignedMilliSeconds(screenTime, nextScreenTime) + : 0.0; + } + + + std::optional ComputeMsFlipDelay( + const QpcConverter& qpc, + const FrameData& present, + bool isDisplayed) + { + if (isDisplayed && present.flipDelay != 0) { + return qpc.DurationMilliSeconds(present.flipDelay); + } + return std::nullopt; + } + + + double ComputeMsDisplayLatency( + const QpcConverter& qpc, + const SwapChainCoreState& swapChain, + const FrameData& present, + bool isDisplayed, + uint64_t screenTime) + { + const auto cpuStart = CalculateCPUStart(swapChain, present); + return (isDisplayed && cpuStart != 0) + ? qpc.DeltaUnsignedMilliSeconds(cpuStart, screenTime) + : 0.0; + } + + + std::optional ComputeMsReadyTimeToDisplayLatency( + const QpcConverter& qpc, + const FrameData& present, + bool isDisplayed, + uint64_t screenTime) + { + if (isDisplayed && present.readyTime != 0) { + return qpc.DeltaUnsignedMilliSeconds(present.readyTime, screenTime); + } + return std::nullopt; + } + + + } + + // ---- NV collapsed/runt correction ---- + void AdjustScreenTimeForCollapsedPresentNV( + FrameData& present, + FrameData* nextDisplayedPresent, + const uint64_t& lastDisplayedFlipDelay, + const uint64_t& lastDisplayedScreenTime, + uint64_t& screenTime, + uint64_t& nextScreenTime, + MetricsVersion version) + { + if (version == MetricsVersion::V1) { + // NV1 collapsed/runt correction: legacy V1 adjusts the *current* present using the + // previous displayed state when the last displayed screen time (adjusted by flip delay) + // is greater than this present's screen time. + if (lastDisplayedFlipDelay > 0 && + lastDisplayedScreenTime > screenTime && + !present.displayed.empty()) { + + const uint64_t diff = lastDisplayedScreenTime - screenTime; + present.flipDelay += diff; + present.displayed[0].second = lastDisplayedScreenTime; + screenTime = present.displayed[0].second; + } + return; + } + + // nextDisplayedPresent should always be non-null for NV GPU. + if (present.flipDelay && screenTime > nextScreenTime && nextDisplayedPresent) { + // If screenTime that is adjusted by flipDelay is larger than nextScreenTime, + // it implies this present is a collapsed present, or a runt frame. + // So we adjust the screenTime and flipDelay of nextDisplayedPresent, + // effectively making nextScreenTime equals to screenTime. + + nextDisplayedPresent->flipDelay += (screenTime - nextScreenTime); + nextScreenTime = screenTime; + nextDisplayedPresent->displayed[0].second = nextScreenTime; + } + } + + + DisplayIndexing DisplayIndexing::Calculate( + const FrameData& present, + const FrameData* nextDisplayed) + { + DisplayIndexing result{}; + + // Get display count + auto displayCount = present.displayed.size(); // ConsoleAdapter/PresentSnapshot method + + // Check if displayed + bool displayed = present.finalState == PresentResult::Presented && displayCount > 0; + + // hasNextDisplayed + result.hasNextDisplayed = (nextDisplayed != nullptr); + + // Figure out range to process based on three cases: + // Case 1: Not displayed → empty range [0, 0) + // Case 2: Displayed, no next → process [0..N-2], postpone N-1 → range [0, N-1) + // Case 3: Displayed, with next → process postponed [N-1] → range [N-1, N) + + if (!displayed || displayCount == 0) { + // Case 1: Not displayed + result.startIndex = 0; + result.endIndex = 0; // Empty range + } + else if (nextDisplayed == nullptr) { + // Case 2: Postpone last display + result.startIndex = 0; + result.endIndex = displayCount - 1; // One past [N-2] = [N-1] (excludes last!) + } + else { + // Case 3: Process postponed last display + result.startIndex = displayCount - 1; + result.endIndex = displayCount; // One past [N-1] = [N] + } + + // appIndex - find first NotSet or Application frame + // Search from startIndex through ALL displays (not just the processing range) + result.appIndex = std::numeric_limits::max(); + if (displayCount > 0) { + for (size_t i = result.startIndex; i < displayCount; ++i) { + auto frameType = present.displayed[i].first; + if (frameType == FrameType::NotSet || frameType == FrameType::Application) { + result.appIndex = i; + break; + } + } + } + else { + result.appIndex = 0; + } + return result; + } + + + void CalculateDisplayMetrics( + const QpcConverter& qpc, + const FrameData& present, + const SwapChainCoreState& swapChain, + bool isDisplayed, + uint64_t screenTime, + uint64_t nextScreenTime, + FrameMetrics& metrics) + { + metrics.msUntilDisplayed = ComputeMsUntilDisplayed(qpc, present, isDisplayed, screenTime); + metrics.msBetweenDisplayChange = ComputeMsBetweenDisplayChange(qpc, swapChain, isDisplayed, screenTime); + metrics.msDisplayedTime = ComputeMsDisplayedTime(qpc, isDisplayed, screenTime, nextScreenTime); + metrics.msFlipDelay = ComputeMsFlipDelay(qpc, present, isDisplayed); + metrics.msDisplayLatency = ComputeMsDisplayLatency(qpc, swapChain, present, isDisplayed, screenTime); + metrics.msReadyTimeToDisplayLatency = ComputeMsReadyTimeToDisplayLatency(qpc, present, isDisplayed, screenTime); + + metrics.screenTimeQpc = screenTime; + } +} diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorInput.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorInput.cpp new file mode 100644 index 00000000..237a8dd9 --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorInput.cpp @@ -0,0 +1,186 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#include "MetricsCalculator.h" +#include "MetricsCalculatorInternal.h" + +#include "../PresentData/PresentMonTraceConsumer.hpp" +#include "../IntelPresentMon/PresentMonUtils/StreamFormat.h" +#include "../Math.h" + +namespace pmon::util::metrics +{ + namespace + { + // ---- Input latency metrics ---- + std::optional ComputeClickToPhotonLatency( + const QpcConverter& qpc, + const SwapChainCoreState& chain, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + uint64_t screenTime, + ComputedMetrics::StateDeltas& stateDeltas) + { + // Only app frames participate in click-to-photon. + if (!isAppFrame) { + return std::nullopt; + } + + uint64_t inputTime = 0; + + // Case 1: This frame *has* a click. + if (present.mouseClickTime != 0) { + inputTime = present.mouseClickTime; + + if (!isDisplayed) { + // Not displayed: stash the click for a future displayed frame. + stateDeltas.lastReceivedNotDisplayedMouseClickTime = inputTime; + return std::nullopt; + } + else { + stateDeltas.shouldResetInputTimes = true; + } + } + // Case 2: No click on this frame, but frame is displayed: see if we can + // reuse a pending click from a previously dropped frame. + else if (isDisplayed && chain.lastReceivedNotDisplayedMouseClickTime != 0) { + inputTime = chain.lastReceivedNotDisplayedMouseClickTime; + stateDeltas.shouldResetInputTimes = true; + } + + // If we still have no inputTime, nothing to compute. + if (inputTime == 0) { + return std::nullopt; + } + + return qpc.DeltaUnsignedMilliSeconds(inputTime, screenTime); + } + + + std::optional ComputeAllInputToPhotonLatency( + const QpcConverter& qpc, + const SwapChainCoreState& chain, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + uint64_t screenTime, + ComputedMetrics::StateDeltas& stateDeltas) + { + // Only app frames participate in click-to-photon. + if (!isAppFrame) { + return std::nullopt; + } + + uint64_t inputTime = 0; + + // Case 1: This frame *has* a click. + if (present.inputTime != 0) { + inputTime = present.inputTime; + + if (!isDisplayed) { + // Not displayed: stash the click for a future displayed frame. + stateDeltas.lastReceivedNotDisplayedAllInputTime = inputTime; + return std::nullopt; + } + else { + stateDeltas.shouldResetInputTimes = true; + } + } + // Case 2: No click on this frame, but frame is displayed: see if we can + // reuse a pending click from a previously dropped frame. + else if (isDisplayed && chain.lastReceivedNotDisplayedAllInputTime != 0) { + inputTime = chain.lastReceivedNotDisplayedAllInputTime; + stateDeltas.shouldResetInputTimes = true; + } + + // If we still have no inputTime, nothing to compute. + if (inputTime == 0) { + return std::nullopt; + } + + return qpc.DeltaUnsignedMilliSeconds(inputTime, screenTime); + } + + std::optional ComputeInstrumentedInputToPhotonLatency( + const QpcConverter& qpc, + const SwapChainCoreState& chain, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + uint64_t screenTime, + ComputedMetrics::StateDeltas& stateDeltas) + { + // Only app frames participate in click-to-photon. + if (!isAppFrame) { + return std::nullopt; + } + + uint64_t inputTime = 0; + + // Case 1: This frame *has* a click. + if (present.appInputSample.first != 0) { + inputTime = present.appInputSample.first; + + if (!isDisplayed) { + // Not displayed: stash the click for a future displayed frame. + stateDeltas.lastReceivedNotDisplayedAppProviderInputTime = inputTime; + return std::nullopt; + } + else { + stateDeltas.shouldResetInputTimes = true; + } + } + // Case 2: No click on this frame, but frame is displayed: see if we can + // reuse a pending click from a previously dropped frame. + else if (isDisplayed && chain.lastReceivedNotDisplayedAppProviderInputTime != 0) { + inputTime = chain.lastReceivedNotDisplayedAppProviderInputTime; + stateDeltas.shouldResetInputTimes = true; + } + + // If we still have no inputTime, nothing to compute. + if (inputTime == 0) { + return std::nullopt; + } + + return qpc.DeltaUnsignedMilliSeconds(inputTime, screenTime); + } + } + + void CalculateInputLatencyMetrics( + const QpcConverter& qpc, + const SwapChainCoreState& swapChain, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + FrameMetrics& metrics, + ComputedMetrics::StateDeltas& stateDeltas) + { + const uint64_t screenTime = metrics.screenTimeQpc; + metrics.msClickToPhotonLatency = ComputeClickToPhotonLatency( + qpc, + swapChain, + present, + isDisplayed, + isAppFrame, + screenTime, + stateDeltas); + + metrics.msAllInputPhotonLatency = ComputeAllInputToPhotonLatency( + qpc, + swapChain, + present, + isDisplayed, + isAppFrame, + screenTime, + stateDeltas); + + metrics.msInstrumentedInputTime = ComputeInstrumentedInputToPhotonLatency( + qpc, + swapChain, + present, + isDisplayed, + isAppFrame, + screenTime, + stateDeltas); + } +} diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorInstrumented.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorInstrumented.cpp new file mode 100644 index 00000000..4833cab8 --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorInstrumented.cpp @@ -0,0 +1,250 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#include "MetricsCalculator.h" +#include "MetricsCalculatorInternal.h" + +#include "../PresentData/PresentMonTraceConsumer.hpp" +#include "../IntelPresentMon/PresentMonUtils/StreamFormat.h" +#include "../Math.h" + +namespace pmon::util::metrics +{ + namespace + { + // ---- Instrumented metrics ---- + std::optional ComputeInstrumentedLatency( + const QpcConverter& qpc, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + uint64_t screenTime) + { + if (!isDisplayed || !isAppFrame) { + return std::nullopt; + } + + auto instrumentedStartTime = present.appSleepEndTime != 0 ? + present.appSleepEndTime : present.appSimStartTime; + + if (instrumentedStartTime == 0) { + // No instrumented start time: nothing to compute. + return std::nullopt; + } + + return qpc.DeltaUnsignedMilliSeconds(instrumentedStartTime, screenTime); + } + + std::optional ComputeInstrumentedRenderLatency( + const QpcConverter& qpc, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + uint64_t screenTime) + { + if (!isDisplayed || !isAppFrame) { + return std::nullopt; + } + + if (present.appRenderSubmitStartTime == 0) { + // No app provided render submit start time: nothing to compute. + return std::nullopt; + } + + return qpc.DeltaUnsignedMilliSeconds(present.appRenderSubmitStartTime, screenTime); + } + + std::optional ComputeInstrumentedSleep( + const QpcConverter& qpc, + const FrameData& present, + bool isAppFrame) + { + if (!isAppFrame) { + return std::nullopt; + } + + if (present.appSleepStartTime == 0 || present.appSleepEndTime == 0) { + // No app provided sleep times: nothing to compute. + return std::nullopt; + } + + return qpc.DeltaUnsignedMilliSeconds(present.appSleepStartTime, present.appSleepEndTime); + } + + std::optional ComputeInstrumentedGpuLatency( + const QpcConverter& qpc, + const FrameData& present, + bool isAppFrame) + { + if (!isAppFrame) { + return std::nullopt; + } + + auto instrumentedStartTime = present.appSleepEndTime != 0 ? + present.appSleepEndTime : present.appSimStartTime; + + if (instrumentedStartTime == 0) { + // No provider sleep end or sim start time: nothing to compute. + return std::nullopt; + } + + if (present.gpuStartTime == 0) { + // No GPU start time: nothing to compute. + return std::nullopt; + } + + return qpc.DeltaUnsignedMilliSeconds(instrumentedStartTime, present.gpuStartTime); + } + + // ---- Simulation metrics ---- + std::optional ComputeMsBetweenSimulationStarts( + const QpcConverter& qpc, + const SwapChainCoreState& chain, + const FrameData& present, + bool isAppFrame) + { + if (!isAppFrame) { + return std::nullopt; + } + + // The current sim start time is only dependent on the current frame's simulation start times. + // Preference is PCL, then App. + uint64_t currentSimStartTime = 0; + if (present.pclSimStartTime != 0) { + currentSimStartTime = present.pclSimStartTime; + } + else if (present.appSimStartTime != 0) { + currentSimStartTime = present.appSimStartTime; + } + if (chain.lastSimStartTime != 0 && currentSimStartTime != 0 && + currentSimStartTime > chain.lastSimStartTime) { + return qpc.DeltaUnsignedMilliSeconds( + chain.lastSimStartTime, + currentSimStartTime); + } + else { + return std::nullopt; + } + } + + } + + std::optional CalculatePcLatency( + const QpcConverter& qpc, + const SwapChainCoreState& chain, + const FrameData& present, + bool isDisplayed, + uint64_t screenTime, + ComputedMetrics::StateDeltas& stateDeltas) + { + if (!isDisplayed) { + if (present.pclSimStartTime != 0) { + if (present.pclInputPingTime != 0) { + // This frame was dropped but we have valid pc latency input and simulation start + // times. Calculate the initial input to sim start time + stateDeltas.newAccumulatedInput2FrameStart = qpc.DeltaUnsignedMilliSeconds( + present.pclInputPingTime, + present.pclSimStartTime); + } + else if (chain.accumulatedInput2FrameStartTime != 0.f) { + // This frame was also dropped and there is no pc latency input time. However, since we have + // accumulated time this means we have a pending input that has had multiple dropped frames + // and has not yet hit the screen. Calculate the time between the last not displayed sim start and + // this sim start and add it to our accumulated total + stateDeltas.newAccumulatedInput2FrameStart = chain.accumulatedInput2FrameStartTime + + qpc.DeltaUnsignedMilliSeconds( + chain.lastReceivedNotDisplayedPclSimStart, + present.pclSimStartTime); + } + stateDeltas.newLastReceivedPclSimStart = present.pclSimStartTime; + } + return std::nullopt; + } + + // Check to see if we have a valid PC Latency sim start time + if (present.pclSimStartTime != 0) { + if (present.pclInputPingTime != 0) { + // Both the pclSimStartTime and pclInputPingTime are valid, use them to update + // the Input to Frame Start EMA. Store in state deltas for later application. + + stateDeltas.newInput2FrameStartEma = pmon::util::CalculateEma( + chain.Input2FrameStartTimeEma, + qpc.DeltaUnsignedMilliSeconds(present.pclInputPingTime, present.pclSimStartTime), + 0.1); + + // Defensively clear the tracking variables for when we have a dropped frame with a pc latency input + stateDeltas.newAccumulatedInput2FrameStart = 0.0; + stateDeltas.newLastReceivedPclSimStart = 0; + } + else { + if (chain.accumulatedInput2FrameStartTime != 0.0) { + // This frame was displayed but we don't have a pc latency input time. However, there is accumulated time + // so there is a pending input that will now hit the screen. Add in the time from the last not + // displayed pc simulation start to this frame's pc simulation start. + stateDeltas.newAccumulatedInput2FrameStart = chain.accumulatedInput2FrameStartTime + + qpc.DeltaUnsignedMilliSeconds( + chain.lastReceivedNotDisplayedPclSimStart, + present.pclSimStartTime); + + stateDeltas.newInput2FrameStartEma = pmon::util::CalculateEma( + chain.Input2FrameStartTimeEma, + *stateDeltas.newAccumulatedInput2FrameStart, + 0.1); + + // Reset the tracking variables for when we have a dropped frame with a pc latency input + stateDeltas.newAccumulatedInput2FrameStart = 0.0; + stateDeltas.newLastReceivedPclSimStart = 0; + } + } + } + + auto simStartTime = present.pclSimStartTime != 0 ? present.pclSimStartTime : chain.lastSimStartTime; + double input2FrameStartEma = stateDeltas.newInput2FrameStartEma.has_value() ? + stateDeltas.newInput2FrameStartEma.value() : chain.Input2FrameStartTimeEma; + if (input2FrameStartEma != 0.0 && simStartTime != 0) { + return input2FrameStartEma + qpc.DeltaSignedMilliSeconds(simStartTime, screenTime); + } + else { + return std::nullopt; + } + } + + void CalculateInstrumentedMetrics( + const QpcConverter& qpc, + const SwapChainCoreState& chain, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + uint64_t screenTime, + FrameMetrics& metrics) { + + metrics.msInstrumentedLatency = ComputeInstrumentedLatency( + qpc, + present, + isDisplayed, + isAppFrame, + screenTime); + + metrics.msInstrumentedRenderLatency = ComputeInstrumentedRenderLatency( + qpc, + present, + isDisplayed, + isAppFrame, + screenTime); + + metrics.msInstrumentedSleep = ComputeInstrumentedSleep( + qpc, + present, + isAppFrame); + + metrics.msInstrumentedGpuLatency = ComputeInstrumentedGpuLatency( + qpc, + present, + isAppFrame); + + metrics.msBetweenSimStarts = ComputeMsBetweenSimulationStarts( + qpc, + chain, + present, + isAppFrame); + } +} diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorInternal.h b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorInternal.h new file mode 100644 index 00000000..26bc910a --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorInternal.h @@ -0,0 +1,76 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#pragma once + +#include "MetricsCalculator.h" + +namespace pmon::util::metrics +{ + // Category calculators implemented across MetricsCalculator_*.cpp translation units. + void CalculateBasePresentMetrics( + const QpcConverter& qpc, + const FrameData& present, + const SwapChainCoreState& swapChain, + FrameMetrics& out); + + void CalculateDisplayMetrics( + const QpcConverter& qpc, + const FrameData& present, + const SwapChainCoreState& swapChain, + bool isDisplayed, + uint64_t screenTime, + uint64_t nextScreenTime, + FrameMetrics& metrics); + + void CalculateCpuGpuMetrics( + const QpcConverter& qpc, + const SwapChainCoreState& chainState, + const FrameData& present, + bool isAppFrame, + FrameMetrics& metrics); + + void CalculateAnimationMetrics( + const QpcConverter& qpc, + const SwapChainCoreState& swapChain, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + uint64_t screenTime, + FrameMetrics& metrics); + + void CalculateInputLatencyMetrics( + const QpcConverter& qpc, + const SwapChainCoreState& swapChain, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + FrameMetrics& metrics, + ComputedMetrics::StateDeltas& stateDeltas); + + std::optional CalculatePcLatency( + const QpcConverter& qpc, + const SwapChainCoreState& chain, + const FrameData& present, + bool isDisplayed, + uint64_t screenTime, + ComputedMetrics::StateDeltas& stateDeltas); + + void CalculateInstrumentedMetrics( + const QpcConverter& qpc, + const SwapChainCoreState& chain, + const FrameData& present, + bool isDisplayed, + bool isAppFrame, + uint64_t screenTime, + FrameMetrics& metrics); + + // NVIDIA collapsed/runt correction helper used by ComputeMetricsForPresent. + void AdjustScreenTimeForCollapsedPresentNV( + FrameData& present, + FrameData* nextDisplayedPresent, + const uint64_t& lastDisplayedFlipDelay, + const uint64_t& lastDisplayedScreenTime, + uint64_t& screenTime, + uint64_t& nextScreenTime, + MetricsVersion version); +} \ No newline at end of file From f58fbe1c36480e200990b034d279ac1a762a819a Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 13 Jan 2026 17:34:52 +0900 Subject: [PATCH 105/205] add FixedVector container --- .../CommonUtilities/CommonUtilities.vcxproj | 5 +- .../CommonUtilities.vcxproj.filters | 7 +- .../CommonUtilities/cnr/FixedVector.h | 343 ++++++++++++++++++ 3 files changed, 351 insertions(+), 4 deletions(-) create mode 100644 IntelPresentMon/CommonUtilities/cnr/FixedVector.h diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj index 98c48b80..834f28ea 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj @@ -1,4 +1,4 @@ - + @@ -20,6 +20,7 @@ + @@ -395,4 +396,4 @@ - \ No newline at end of file + diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters index 6e60966d..fde2d727 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters @@ -1,4 +1,4 @@ - + @@ -18,6 +18,9 @@ Header Files + + Header Files + Header Files @@ -516,4 +519,4 @@ - \ No newline at end of file + diff --git a/IntelPresentMon/CommonUtilities/cnr/FixedVector.h b/IntelPresentMon/CommonUtilities/cnr/FixedVector.h new file mode 100644 index 00000000..7f8b849f --- /dev/null +++ b/IntelPresentMon/CommonUtilities/cnr/FixedVector.h @@ -0,0 +1,343 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace pmon::util::cnr +{ + template + class FixedVector + { + public: + using value_type = T; + using size_type = size_t; + using difference_type = std::ptrdiff_t; + using reference = T&; + using const_reference = const T&; + using pointer = T*; + using const_pointer = const T*; + using iterator = T*; + using const_iterator = const T*; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + FixedVector() noexcept = default; + + explicit FixedVector(size_type count) + { + Resize(count); + } + + FixedVector(size_type count, const T& value) + { + Resize(count, value); + } + + FixedVector(std::initializer_list init) + { + Assign(init); + } + + template, int> = 0> + FixedVector(It first, It last) + { + Assign(first, last); + } + + template + FixedVector(std::from_range_t, R&& range) + { + AssignRange_(std::forward(range)); + } + + FixedVector(const FixedVector& other) + { + CopyFrom_(other); + } + + FixedVector(FixedVector&& other) noexcept(std::is_nothrow_move_constructible_v) + { + MoveFrom_(std::move(other)); + } + + FixedVector& operator=(const FixedVector& other) + { + if (this != &other) { + Clear(); + CopyFrom_(other); + } + return *this; + } + + FixedVector& operator=(FixedVector&& other) noexcept(std::is_nothrow_move_constructible_v) + { + if (this != &other) { + Clear(); + MoveFrom_(std::move(other)); + } + return *this; + } + + FixedVector& operator=(std::initializer_list init) + { + Assign(init); + return *this; + } + + ~FixedVector() + { + Clear(); + } + + iterator begin() noexcept { return Data(); } + const_iterator begin() const noexcept { return Data(); } + const_iterator CBegin() const noexcept { return Data(); } + iterator end() noexcept { return Data() + size_; } + const_iterator end() const noexcept { return Data() + size_; } + const_iterator CEnd() const noexcept { return Data() + size_; } + + reverse_iterator RBegin() noexcept { return reverse_iterator(end()); } + const_reverse_iterator RBegin() const noexcept { return const_reverse_iterator(end()); } + const_reverse_iterator CRBegin() const noexcept { return const_reverse_iterator(CEnd()); } + reverse_iterator REnd() noexcept { return reverse_iterator(begin()); } + const_reverse_iterator REnd() const noexcept { return const_reverse_iterator(begin()); } + const_reverse_iterator CREnd() const noexcept { return const_reverse_iterator(CBegin()); } + + size_type Size() const noexcept { return size_; } + constexpr size_type Capacity() const noexcept { return MaxCapacity; } + constexpr size_type MaxSize() const noexcept { return MaxCapacity; } + bool Empty() const noexcept { return size_ == 0; } + + void Reserve(size_type count) + { + CheckCapacity_(count); + } + + void ShrinkToFit() noexcept {} + + reference operator[](size_type index) noexcept { return *Ptr_(index); } + const_reference operator[](size_type index) const noexcept { return *Ptr_(index); } + + reference At(size_type index) + { + if (index >= size_) { + throw std::out_of_range("FixedVector::At"); + } + return (*this)[index]; + } + + const_reference At(size_type index) const + { + if (index >= size_) { + throw std::out_of_range("FixedVector::At"); + } + return (*this)[index]; + } + + reference Front() noexcept + { + assert(size_ > 0); + return (*this)[0]; + } + + const_reference Front() const noexcept + { + assert(size_ > 0); + return (*this)[0]; + } + + reference Back() noexcept + { + assert(size_ > 0); + return (*this)[size_ - 1]; + } + + const_reference Back() const noexcept + { + assert(size_ > 0); + return (*this)[size_ - 1]; + } + + pointer Data() noexcept { return DataPtr_(); } + const_pointer Data() const noexcept { return DataPtr_(); } + + void Clear() noexcept + { + DestroyRange_(0, size_); + size_ = 0; + } + + void Resize(size_type count) + { + if (count < size_) { + DestroyRange_(count, size_); + size_ = count; + } + else if (count > size_) { + CheckCapacity_(count); + for (size_type i = size_; i < count; ++i) { + std::construct_at(Ptr_(i)); + ++size_; + } + } + } + + void Resize(size_type count, const T& value) + { + if (count < size_) { + DestroyRange_(count, size_); + size_ = count; + } + else if (count > size_) { + CheckCapacity_(count); + for (size_type i = size_; i < count; ++i) { + std::construct_at(Ptr_(i), value); + ++size_; + } + } + } + + void PushBack(const T& value) + { + EmplaceBack(value); + } + + void PushBack(T&& value) + { + EmplaceBack(std::move(value)); + } + + template + reference EmplaceBack(Args&&... args) + { + CheckCapacity_(size_ + 1); + pointer ptr = Ptr_(size_); + std::construct_at(ptr, std::forward(args)...); + ++size_; + return *ptr; + } + + void PopBack() noexcept + { + assert(size_ > 0); + if (size_ == 0) { + return; + } + DestroyRange_(size_ - 1, size_); + --size_; + } + + void Assign(size_type count, const T& value) + { + Clear(); + Resize(count, value); + } + + void Assign(std::initializer_list init) + { + Clear(); + CheckCapacity_(init.size()); + for (const auto& value : init) { + EmplaceBack(value); + } + } + + template, int> = 0> + void Assign(It first, It last) + { + Clear(); + for (; first != last; ++first) { + EmplaceBack(*first); + } + } + + template + void Assign(std::from_range_t, R&& range) + { + AssignRange_(std::forward(range)); + } + + private: + using Storage = std::aligned_storage_t; + + template + void AssignRange_(R&& range) + { + Clear(); + if constexpr (std::ranges::sized_range) { + CheckCapacity_(static_cast(std::ranges::size(range))); + } + for (auto&& value : range) { + EmplaceBack(std::forward(value)); + } + } + + pointer DataPtr_() noexcept + { + if (size_ == 0) { + return reinterpret_cast(storage_.data()); + } + return Ptr_(0); + } + + const_pointer DataPtr_() const noexcept + { + if (size_ == 0) { + return reinterpret_cast(storage_.data()); + } + return Ptr_(0); + } + + pointer Ptr_(size_type index) noexcept + { + return std::launder(reinterpret_cast(&storage_[index])); + } + + const_pointer Ptr_(size_type index) const noexcept + { + return std::launder(reinterpret_cast(&storage_[index])); + } + + void DestroyRange_(size_type first, size_type last) noexcept + { + for (size_type i = first; i < last; ++i) { + std::destroy_at(Ptr_(i)); + } + } + + void CheckCapacity_(size_type count) const + { + if (count > MaxCapacity) { + throw std::length_error("FixedVector capacity exceeded"); + } + } + + void CopyFrom_(const FixedVector& other) + { + for (size_type i = 0; i < other.size_; ++i) { + std::construct_at(Ptr_(i), other[i]); + ++size_; + } + } + + void MoveFrom_(FixedVector&& other) + { + for (size_type i = 0; i < other.size_; ++i) { + std::construct_at(Ptr_(i), std::move(other[i])); + ++size_; + } + other.Clear(); + } + + std::array storage_{}; + size_type size_ = 0; + }; +} From d50e836e47aa816475e8c8924a32d199cf5a43c6 Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 13 Jan 2026 18:06:38 +0900 Subject: [PATCH 106/205] test fixed vector --- .../CommonUtilities/cnr/FixedVector.h | 10 +- .../UnitTests/FixedVectorTests.cpp | 312 ++++++++++++++++++ IntelPresentMon/UnitTests/UnitTests.vcxproj | 5 +- .../UnitTests/UnitTests.vcxproj.filters | 3 +- 4 files changed, 323 insertions(+), 7 deletions(-) create mode 100644 IntelPresentMon/UnitTests/FixedVectorTests.cpp diff --git a/IntelPresentMon/CommonUtilities/cnr/FixedVector.h b/IntelPresentMon/CommonUtilities/cnr/FixedVector.h index 7f8b849f..7a01dc33 100644 --- a/IntelPresentMon/CommonUtilities/cnr/FixedVector.h +++ b/IntelPresentMon/CommonUtilities/cnr/FixedVector.h @@ -266,7 +266,7 @@ namespace pmon::util::cnr } private: - using Storage = std::aligned_storage_t; + using StorageByte = std::byte; template void AssignRange_(R&& range) @@ -298,12 +298,14 @@ namespace pmon::util::cnr pointer Ptr_(size_type index) noexcept { - return std::launder(reinterpret_cast(&storage_[index])); + auto* raw = storage_.data() + (index * sizeof(T)); + return std::launder(reinterpret_cast(raw)); } const_pointer Ptr_(size_type index) const noexcept { - return std::launder(reinterpret_cast(&storage_[index])); + auto* raw = storage_.data() + (index * sizeof(T)); + return std::launder(reinterpret_cast(raw)); } void DestroyRange_(size_type first, size_type last) noexcept @@ -337,7 +339,7 @@ namespace pmon::util::cnr other.Clear(); } - std::array storage_{}; + alignas(T) std::array storage_{}; size_type size_ = 0; }; } diff --git a/IntelPresentMon/UnitTests/FixedVectorTests.cpp b/IntelPresentMon/UnitTests/FixedVectorTests.cpp new file mode 100644 index 00000000..558073fd --- /dev/null +++ b/IntelPresentMon/UnitTests/FixedVectorTests.cpp @@ -0,0 +1,312 @@ +// Copyright (C) 2026 Intel Corporation +// SPDX-License-Identifier: MIT + +#include + +#include + +#include +#include +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace UtilityTests +{ + struct ValueType + { + int value = 0; + ValueType() : value(7) {} + explicit ValueType(int v) : value(v) {} + bool operator==(const ValueType& other) const { return value == other.value; } + }; + + struct CountingType + { + static int Alive; + static int Constructions; + static int Destructions; + + int value = 0; + CountingType() : value(0) { ++Alive; ++Constructions; } + explicit CountingType(int v) : value(v) { ++Alive; ++Constructions; } + CountingType(const CountingType& other) : value(other.value) { ++Alive; ++Constructions; } + CountingType(CountingType&& other) noexcept : value(other.value) { other.value = -1; ++Alive; ++Constructions; } + CountingType& operator=(const CountingType& other) { value = other.value; return *this; } + CountingType& operator=(CountingType&& other) noexcept { value = other.value; other.value = -1; return *this; } + ~CountingType() { --Alive; ++Destructions; } + + static void Reset() + { + Alive = 0; + Constructions = 0; + Destructions = 0; + } + }; + + int CountingType::Alive = 0; + int CountingType::Constructions = 0; + int CountingType::Destructions = 0; + + struct CounterRange + { + struct Iterator + { + using iterator_concept = std::input_iterator_tag; + using iterator_category = std::input_iterator_tag; + using value_type = int; + using difference_type = std::ptrdiff_t; + + int value = 0; + int finish = 0; + + int operator*() const { return value; } + Iterator& operator++() { ++value; return *this; } + void operator++(int) { ++value; } + bool operator==(const Iterator& other) const { return value == other.value; } + bool operator!=(const Iterator& other) const { return value != other.value; } + }; + + int start = 0; + int finish = 0; + + Iterator begin() const { return Iterator{ start, finish }; } + Iterator end() const { return Iterator{ finish, finish }; } + }; + + TEST_CLASS(TestFixedVector) + { + public: + TEST_METHOD(DefaultState) + { + pmon::util::cnr::FixedVector vec; + Assert::IsTrue(vec.Empty()); + Assert::AreEqual(0u, vec.Size()); + Assert::AreEqual(4u, vec.Capacity()); + Assert::AreEqual(4u, vec.MaxSize()); + Assert::IsTrue(vec.begin() == vec.end()); + Assert::IsTrue(vec.CBegin() == vec.CEnd()); + Assert::IsTrue(vec.Data() != nullptr); + } + + TEST_METHOD(CountConstructors) + { + pmon::util::cnr::FixedVector vecDefault(3); + Assert::AreEqual(3u, vecDefault.Size()); + Assert::AreEqual(7, vecDefault[0].value); + Assert::AreEqual(7, vecDefault[1].value); + Assert::AreEqual(7, vecDefault[2].value); + + pmon::util::cnr::FixedVector vecFill(2, ValueType{ 9 }); + Assert::AreEqual(2u, vecFill.Size()); + Assert::AreEqual(9, vecFill[0].value); + Assert::AreEqual(9, vecFill[1].value); + } + + TEST_METHOD(InitializerListAndIteratorConstructors) + { + pmon::util::cnr::FixedVector initVec{ 1, 2, 3 }; + Assert::AreEqual(3u, initVec.Size()); + Assert::AreEqual(1, initVec[0]); + Assert::AreEqual(2, initVec[1]); + Assert::AreEqual(3, initVec[2]); + + std::array source{ 4, 5, 6 }; + pmon::util::cnr::FixedVector iterVec(source.begin(), source.end()); + Assert::AreEqual(3u, iterVec.Size()); + Assert::AreEqual(4, iterVec[0]); + Assert::AreEqual(5, iterVec[1]); + Assert::AreEqual(6, iterVec[2]); + } + + TEST_METHOD(FromRangeConstructorAndAssign) + { + std::vector source{ 7, 8, 9 }; + pmon::util::cnr::FixedVector rangeVec(std::from_range, source); + Assert::AreEqual(3u, rangeVec.Size()); + Assert::AreEqual(7, rangeVec[0]); + Assert::AreEqual(8, rangeVec[1]); + Assert::AreEqual(9, rangeVec[2]); + + pmon::util::cnr::FixedVector assigned; + assigned.Assign(std::from_range, source); + Assert::AreEqual(3u, assigned.Size()); + Assert::AreEqual(7, assigned[0]); + Assert::AreEqual(8, assigned[1]); + Assert::AreEqual(9, assigned[2]); + } + + TEST_METHOD(CopyAndMove) + { + pmon::util::cnr::FixedVector src{ 1, 2, 3 }; + pmon::util::cnr::FixedVector copy(src); + Assert::AreEqual(3u, copy.Size()); + Assert::AreEqual(1, copy[0]); + Assert::AreEqual(2, copy[1]); + Assert::AreEqual(3, copy[2]); + + pmon::util::cnr::FixedVector assigned; + assigned = src; + Assert::AreEqual(3u, assigned.Size()); + Assert::AreEqual(1, assigned[0]); + Assert::AreEqual(2, assigned[1]); + Assert::AreEqual(3, assigned[2]); + + pmon::util::cnr::FixedVector moved(std::move(src)); + Assert::AreEqual(3u, moved.Size()); + Assert::AreEqual(1, moved[0]); + Assert::AreEqual(2, moved[1]); + Assert::AreEqual(3, moved[2]); + Assert::IsTrue(src.Empty()); + + pmon::util::cnr::FixedVector moveAssign; + moveAssign = std::move(moved); + Assert::AreEqual(3u, moveAssign.Size()); + Assert::AreEqual(1, moveAssign[0]); + Assert::AreEqual(2, moveAssign[1]); + Assert::AreEqual(3, moveAssign[2]); + Assert::IsTrue(moved.Empty()); + } + + TEST_METHOD(PushPopEmplace) + { + pmon::util::cnr::FixedVector vec; + ValueType first{ 1 }; + vec.PushBack(first); + vec.PushBack(ValueType{ 2 }); + auto& ref = vec.EmplaceBack(3); + + Assert::AreEqual(3u, vec.Size()); + Assert::AreEqual(1, vec[0].value); + Assert::AreEqual(2, vec[1].value); + Assert::AreEqual(3, vec[2].value); + Assert::IsTrue(&ref == &vec.Back()); + + vec.PopBack(); + Assert::AreEqual(2u, vec.Size()); + Assert::AreEqual(2, vec.Back().value); + } + + TEST_METHOD(ResizeClearAndLifetime) + { + CountingType::Reset(); + { + pmon::util::cnr::FixedVector vec; + vec.Resize(3); + Assert::AreEqual(3, CountingType::Alive); + vec.Resize(1); + Assert::AreEqual(1, CountingType::Alive); + vec.Clear(); + Assert::AreEqual(0, CountingType::Alive); + } + Assert::AreEqual(0, CountingType::Alive); + } + + TEST_METHOD(AssignOverloads) + { + pmon::util::cnr::FixedVector vec; + vec.Assign(3, 42); + Assert::AreEqual(3u, vec.Size()); + Assert::AreEqual(42, vec[0]); + Assert::AreEqual(42, vec[2]); + + vec.Assign({ 1, 2 }); + Assert::AreEqual(2u, vec.Size()); + Assert::AreEqual(1, vec[0]); + Assert::AreEqual(2, vec[1]); + + std::array source{ 9, 8, 7 }; + vec.Assign(source.begin(), source.end()); + Assert::AreEqual(3u, vec.Size()); + Assert::AreEqual(9, vec[0]); + Assert::AreEqual(8, vec[1]); + Assert::AreEqual(7, vec[2]); + } + + TEST_METHOD(ElementAccessAndIterators) + { + pmon::util::cnr::FixedVector vec{ 1, 2, 3 }; + const auto& cvec = vec; + + Assert::AreEqual(1, vec.Front()); + Assert::AreEqual(3, vec.Back()); + Assert::AreEqual(2, vec[1]); + Assert::AreEqual(2, cvec[1]); + Assert::AreEqual(1, cvec.At(0)); + Assert::AreEqual(3, cvec.At(2)); + + Assert::IsTrue(vec.Data() == &vec[0]); + Assert::IsTrue(vec.Data() + 2 == &vec[2]); + + int sum = 0; + for (int value : vec) { + sum += value; + } + Assert::AreEqual(6, sum); + + std::vector reversed; + for (auto it = vec.RBegin(); it != vec.REnd(); ++it) { + reversed.push_back(*it); + } + Assert::AreEqual(3u, reversed.size()); + Assert::AreEqual(3, reversed[0]); + Assert::AreEqual(2, reversed[1]); + Assert::AreEqual(1, reversed[2]); + } + + TEST_METHOD(ExceptionsAndCapacity) + { + pmon::util::cnr::FixedVector vec; + vec.PushBack(1); + vec.PushBack(2); + vec.PushBack(3); + + Assert::ExpectException([&]() { + vec.At(3); + }); + + Assert::ExpectException([&]() { + vec.Reserve(4); + }); + + Assert::ExpectException([&]() { + vec.Resize(4); + }); + + Assert::ExpectException([&]() { + vec.PushBack(4); + }); + + pmon::util::cnr::FixedVector fromInit; + Assert::ExpectException([&]() { + fromInit.Assign({ 1, 2, 3, 4 }); + }); + + std::vector sizedRange{ 1, 2, 3, 4 }; + pmon::util::cnr::FixedVector fromSized; + Assert::ExpectException([&]() { + fromSized.Assign(std::from_range, sizedRange); + }); + + CounterRange unsized{ 0, 5 }; + pmon::util::cnr::FixedVector fromUnsized; + Assert::ExpectException([&]() { + fromUnsized.Assign(std::from_range, unsized); + }); + Assert::AreEqual(3u, fromUnsized.Size()); + Assert::AreEqual(0, fromUnsized[0]); + Assert::AreEqual(1, fromUnsized[1]); + Assert::AreEqual(2, fromUnsized[2]); + } + + TEST_METHOD(ReserveAndShrinkNoOp) + { + pmon::util::cnr::FixedVector vec{ 1, 2 }; + vec.Reserve(4); + vec.ShrinkToFit(); + Assert::AreEqual(2u, vec.Size()); + Assert::AreEqual(2, vec.Back()); + } + }; +} diff --git a/IntelPresentMon/UnitTests/UnitTests.vcxproj b/IntelPresentMon/UnitTests/UnitTests.vcxproj index 2540bc7e..d9894308 100644 --- a/IntelPresentMon/UnitTests/UnitTests.vcxproj +++ b/IntelPresentMon/UnitTests/UnitTests.vcxproj @@ -1,4 +1,4 @@ - + @@ -101,6 +101,7 @@ + @@ -117,4 +118,4 @@ - \ No newline at end of file + diff --git a/IntelPresentMon/UnitTests/UnitTests.vcxproj.filters b/IntelPresentMon/UnitTests/UnitTests.vcxproj.filters index 5dda8b0d..2988db50 100644 --- a/IntelPresentMon/UnitTests/UnitTests.vcxproj.filters +++ b/IntelPresentMon/UnitTests/UnitTests.vcxproj.filters @@ -3,8 +3,9 @@ + - \ No newline at end of file + From 16de1556694bbb19b8417b31650a107452a9283c Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 13 Jan 2026 19:06:57 +0900 Subject: [PATCH 107/205] switch FrameData over to FixedVector --- .../CommonUtilities/cnr/FixedVector.h | 37 + .../CommonUtilities/mc/MetricsCalculator.cpp | 6 +- .../mc/MetricsCalculatorDisplay.cpp | 6 +- .../CommonUtilities/mc/MetricsTypes.cpp | 15 +- .../CommonUtilities/mc/MetricsTypes.h | 12 +- .../CommonUtilities/mc/SwapChainState.cpp | 4 +- .../CommonUtilities/mc/UnifiedSwapChain.cpp | 14 +- IntelPresentMon/UnitTests/MetricsCore.cpp | 788 +++++++++--------- 8 files changed, 461 insertions(+), 421 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/cnr/FixedVector.h b/IntelPresentMon/CommonUtilities/cnr/FixedVector.h index 7a01dc33..a6fabead 100644 --- a/IntelPresentMon/CommonUtilities/cnr/FixedVector.h +++ b/IntelPresentMon/CommonUtilities/cnr/FixedVector.h @@ -235,6 +235,43 @@ namespace pmon::util::cnr --size_; } + iterator Erase(const_iterator pos) + { + return Erase(pos, pos + 1); + } + + iterator Erase(const_iterator first, const_iterator last) + { + assert(first >= CBegin()); + assert(last <= CEnd()); + assert(first <= last); + + const size_type start = static_cast(first - CBegin()); + const size_type end = static_cast(last - CBegin()); + if (start == end) { + return begin() + start; + } + + const size_type count = end - start; + const size_type tail = size_ - end; + + if constexpr (std::is_move_assignable_v) { + for (size_type i = 0; i < tail; ++i) { + (*this)[start + i] = std::move((*this)[end + i]); + } + } + else { + for (size_type i = 0; i < tail; ++i) { + std::destroy_at(Ptr_(start + i)); + std::construct_at(Ptr_(start + i), std::move((*this)[end + i])); + } + } + + DestroyRange_(size_ - count, size_); + size_ -= count; + return begin() + start; + } + void Assign(size_type count, const T& value) { Clear(); diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 1901bd05..998e64fe 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Intel Corporation +// Copyright (C) 2025 Intel Corporation // SPDX-License-Identifier: MIT #include "MetricsCalculator.h" #include "MetricsCalculatorInternal.h" @@ -88,7 +88,7 @@ namespace pmon::util::metrics { std::vector results; - const auto displayCount = present.displayed.size(); + const auto displayCount = present.displayed.Size(); const bool isDisplayed = present.finalState == PresentResult::Presented && displayCount > 0; // Case 1: not displayed, return single not-displayed metrics @@ -181,7 +181,7 @@ namespace pmon::util::metrics // Next display instance of the same present nextScreenTime = present.displayed[displayIndex + 1].second; } - else if (nextDisplayed != nullptr && !nextDisplayed->displayed.empty()) { + else if (nextDisplayed != nullptr && !nextDisplayed->displayed.Empty()) { // First display of the *next* presented frame nextScreenTime = nextDisplayed->displayed[0].second; } diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorDisplay.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorDisplay.cpp index 42e95983..d67f9c5e 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorDisplay.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorDisplay.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Intel Corporation +// Copyright (C) 2025 Intel Corporation // SPDX-License-Identifier: MIT #include "MetricsCalculator.h" #include "MetricsCalculatorInternal.h" @@ -107,7 +107,7 @@ namespace pmon::util::metrics // is greater than this present's screen time. if (lastDisplayedFlipDelay > 0 && lastDisplayedScreenTime > screenTime && - !present.displayed.empty()) { + !present.displayed.Empty()) { const uint64_t diff = lastDisplayedScreenTime - screenTime; present.flipDelay += diff; @@ -138,7 +138,7 @@ namespace pmon::util::metrics DisplayIndexing result{}; // Get display count - auto displayCount = present.displayed.size(); // ConsoleAdapter/PresentSnapshot method + auto displayCount = present.displayed.Size(); // ConsoleAdapter/PresentSnapshot method // Check if displayed bool displayed = present.finalState == PresentResult::Presented && displayCount > 0; diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp index 6e6d68b1..1bb1264f 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Intel Corporation +// Copyright (C) 2025 Intel Corporation // SPDX-License-Identifier: MIT #include "MetricsTypes.h" @@ -46,12 +46,11 @@ namespace pmon::util::metrics { frame.flipToken = p.FlipToken; // Normalize parallel arrays to vector - frame.displayed.reserve(p.DisplayedCount); + frame.displayed.Reserve(p.DisplayedCount); for (size_t i = 0; i < p.DisplayedCount; ++i) { - frame.displayed.push_back({ + frame.displayed.EmplaceBack( p.Displayed_FrameType[i], - p.Displayed_ScreenTime[i] - }); + p.Displayed_ScreenTime[i]); } frame.swapChainAddress = p.SwapChainAddress; @@ -106,7 +105,7 @@ namespace pmon::util::metrics { frame.flipDelay = p->FlipDelay; frame.flipToken = p->FlipToken; - frame.displayed = p->Displayed; + frame.displayed.Assign(p->Displayed.begin(), p->Displayed.end()); frame.swapChainAddress = p->SwapChainAddress; frame.syncInterval = p->SyncInterval; @@ -160,7 +159,7 @@ namespace pmon::util::metrics { frame.flipDelay = p.FlipDelay; frame.flipToken = p.FlipToken; - frame.displayed = p.Displayed; + frame.displayed.Assign(p.Displayed.begin(), p.Displayed.end()); frame.swapChainAddress = p.SwapChainAddress; frame.syncInterval = p.SyncInterval; @@ -176,4 +175,4 @@ namespace pmon::util::metrics { return frame; } -} \ No newline at end of file +} diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h index 26f94bcb..b4687583 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h @@ -1,11 +1,12 @@ -// Copyright (C) 2025 Intel Corporation +// Copyright (C) 2025 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #include -#include #include #include +#include "../cnr/FixedVector.h" + // Forward declarations for external types enum class Runtime; enum class PresentMode; @@ -30,6 +31,9 @@ namespace pmon::util::metrics { PCLatency, }; + using DisplayedEntry = std::pair; + using DisplayedVector = pmon::util::cnr::FixedVector; + // Immutable snapshot - safe for both ownership models struct FrameData { Runtime runtime = {}; @@ -66,7 +70,7 @@ namespace pmon::util::metrics { uint64_t inputTime = 0; // All input devices uint64_t mouseClickTime = 0; // Mouse click specific - std::vector> displayed; + DisplayedVector displayed; // PC Latency data uint64_t pclSimStartTime = 0; @@ -148,4 +152,4 @@ namespace pmon::util::metrics { // Frame Classification FrameType frameType = {}; }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/CommonUtilities/mc/SwapChainState.cpp b/IntelPresentMon/CommonUtilities/mc/SwapChainState.cpp index c1430903..964fd282 100644 --- a/IntelPresentMon/CommonUtilities/mc/SwapChainState.cpp +++ b/IntelPresentMon/CommonUtilities/mc/SwapChainState.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Intel Corporation +// Copyright (C) 2025 Intel Corporation // SPDX-License-Identifier: MIT #include "SwapChainState.h" #include "../PresentData/PresentMonTraceConsumer.hpp" @@ -8,7 +8,7 @@ namespace pmon::util::metrics { void SwapChainCoreState::UpdateAfterPresent(const FrameData& present) { const auto finalState = present.finalState; - const size_t displayCnt = present.displayed.size(); + const size_t displayCnt = present.displayed.Size(); if (finalState == PresentResult::Presented) { if (displayCnt > 0) { diff --git a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp index f5d08adb..bfcedf82 100644 --- a/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp +++ b/IntelPresentMon/CommonUtilities/mc/UnifiedSwapChain.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Intel Corporation +// Copyright (C) 2025 Intel Corporation // SPDX-License-Identifier: MIT #include "MetricsTypes.h" #include "UnifiedSwapChain.h" @@ -22,10 +22,10 @@ namespace pmon::util::metrics void UnifiedSwapChain::SanitizeDisplayedRepeatedPresents(FrameData& present) { - // Port of OutputThread.cpp::ReportMetrics() �Remove Repeated flips� pre-pass, - // but applied to FrameData (so we don�t mutate PresentEvent). + // Port of OutputThread.cpp::ReportMetrics() "Remove Repeated flips" pre-pass, + // but applied to FrameData (so we do not mutate PresentEvent). auto& d = present.displayed; - for (size_t i = 0; i + 1 < d.size(); ) { + for (size_t i = 0; i + 1 < d.Size(); ) { const auto a = d[i].first; const auto b = d[i + 1].first; @@ -33,10 +33,10 @@ namespace pmon::util::metrics const bool rep_then_app = (a == FrameType::Repeated && b == FrameType::Application); if (app_then_rep) { - d.erase(d.begin() + i + 1); + d.Erase(d.begin() + i + 1); } else if (rep_then_app) { - d.erase(d.begin() + i); + d.Erase(d.begin() + i); } else { ++i; @@ -75,7 +75,7 @@ namespace pmon::util::metrics const bool isDisplayed = (present.finalState == PresentResult::Presented) && - (!present.displayed.empty()); + (!present.displayed.Empty()); if (isDisplayed) { // 1) Finalize previously waiting displayed (if any), pointing at swapchain-owned next displayed. diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index b993745e..55945a65 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Intel Corporation +// Copyright (C) 2025 Intel Corporation // SPDX-License-Identifier: MIT #include #include @@ -180,7 +180,7 @@ namespace MetricsCoreTests auto frame = FrameData::CopyFrameData(nsmEvent); - Assert::AreEqual(size_t(2), frame.displayed.size()); + Assert::AreEqual(size_t(2), frame.displayed.Size()); Assert::IsTrue(frame.displayed[0].first == FrameType::Application); Assert::AreEqual(9000ull, frame.displayed[0].second); Assert::IsTrue(frame.displayed[1].first == FrameType::Repeated); @@ -194,7 +194,7 @@ namespace MetricsCoreTests auto frame = FrameData::CopyFrameData(nsmEvent); - Assert::AreEqual(size_t(0), frame.displayed.size()); + Assert::AreEqual(size_t(0), frame.displayed.Size()); } TEST_METHOD(FromCircularBuffer_CopiesMetadata) @@ -463,7 +463,7 @@ namespace MetricsCoreTests { FrameData present{}; // No displayed frames - present.displayed.clear(); + present.displayed.Clear(); auto result = DisplayIndexing::Calculate(present, nullptr); @@ -476,7 +476,7 @@ namespace MetricsCoreTests TEST_METHOD(Calculate_SingleDisplay_NoNext_Postponed) { FrameData present{}; - present.displayed.push_back({ FrameType::Application, 1000 }); + present.displayed.PushBack({ FrameType::Application, 1000 }); present.finalState = PresentResult::Presented; auto result = DisplayIndexing::Calculate(present, nullptr); @@ -491,9 +491,9 @@ namespace MetricsCoreTests TEST_METHOD(Calculate_MultipleDisplays_NoNext_PostponeLast) { FrameData present{}; - present.displayed.push_back({ FrameType::Application, 1000 }); - present.displayed.push_back({ FrameType::Repeated, 2000 }); - present.displayed.push_back({ FrameType::Repeated, 3000 }); + present.displayed.PushBack({ FrameType::Application, 1000 }); + present.displayed.PushBack({ FrameType::Repeated, 2000 }); + present.displayed.PushBack({ FrameType::Repeated, 3000 }); present.finalState = PresentResult::Presented; auto result = DisplayIndexing::Calculate(present, nullptr); @@ -508,13 +508,13 @@ namespace MetricsCoreTests TEST_METHOD(Calculate_MultipleDisplays_WithNext_ProcessPostponed) { FrameData present{}; - present.displayed.push_back({ FrameType::Application, 1000 }); - present.displayed.push_back({ FrameType::Repeated, 2000 }); - present.displayed.push_back({ FrameType::Repeated, 3000 }); + present.displayed.PushBack({ FrameType::Application, 1000 }); + present.displayed.PushBack({ FrameType::Repeated, 2000 }); + present.displayed.PushBack({ FrameType::Repeated, 3000 }); present.finalState = PresentResult::Presented; FrameData next{}; - next.displayed.push_back({ FrameType::Application, 4000 }); + next.displayed.PushBack({ FrameType::Application, 4000 }); auto result = DisplayIndexing::Calculate(present, &next); @@ -528,8 +528,8 @@ namespace MetricsCoreTests TEST_METHOD(Calculate_NotDisplayed_ReturnsEmptyRange) { FrameData present{}; - present.displayed.push_back({ FrameType::Application, 1000 }); - present.displayed.push_back({ FrameType::Repeated, 2000 }); + present.displayed.PushBack({ FrameType::Application, 1000 }); + present.displayed.PushBack({ FrameType::Repeated, 2000 }); // Don't set finalState = Presented, so displayed = false auto result = DisplayIndexing::Calculate(present, nullptr); @@ -544,9 +544,9 @@ namespace MetricsCoreTests TEST_METHOD(Calculate_FindsAppFrameIndex_Displayed) { FrameData present{}; - present.displayed.push_back({ FrameType::Repeated, 1000 }); - present.displayed.push_back({ FrameType::Application, 2000 }); - present.displayed.push_back({ FrameType::Repeated, 3000 }); + present.displayed.PushBack({ FrameType::Repeated, 1000 }); + present.displayed.PushBack({ FrameType::Application, 2000 }); + present.displayed.PushBack({ FrameType::Repeated, 3000 }); present.finalState = PresentResult::Presented; auto result = DisplayIndexing::Calculate(present, nullptr); @@ -560,9 +560,9 @@ namespace MetricsCoreTests TEST_METHOD(Calculate_FindsAppFrameIndex_NotDisplayed) { FrameData present{}; - present.displayed.push_back({ FrameType::Repeated, 1000 }); - present.displayed.push_back({ FrameType::Application, 2000 }); - present.displayed.push_back({ FrameType::Repeated, 3000 }); + present.displayed.PushBack({ FrameType::Repeated, 1000 }); + present.displayed.PushBack({ FrameType::Application, 2000 }); + present.displayed.PushBack({ FrameType::Repeated, 3000 }); // Not displayed auto result = DisplayIndexing::Calculate(present, nullptr); @@ -575,9 +575,9 @@ namespace MetricsCoreTests TEST_METHOD(Calculate_AllRepeatedFrames_AppIndexInvalid) { FrameData present{}; - present.displayed.push_back({ FrameType::Repeated, 1000 }); - present.displayed.push_back({ FrameType::Repeated, 2000 }); - present.displayed.push_back({ FrameType::Repeated, 3000 }); + present.displayed.PushBack({ FrameType::Repeated, 1000 }); + present.displayed.PushBack({ FrameType::Repeated, 2000 }); + present.displayed.PushBack({ FrameType::Repeated, 3000 }); present.finalState = PresentResult::Presented; auto result = DisplayIndexing::Calculate(present, nullptr); @@ -591,9 +591,9 @@ namespace MetricsCoreTests TEST_METHOD(Calculate_MultipleAppFrames_FindsFirst) { FrameData present{}; - present.displayed.push_back({ FrameType::Application, 1000 }); - present.displayed.push_back({ FrameType::Application, 2000 }); - present.displayed.push_back({ FrameType::Repeated, 3000 }); + present.displayed.PushBack({ FrameType::Application, 1000 }); + present.displayed.PushBack({ FrameType::Application, 2000 }); + present.displayed.PushBack({ FrameType::Repeated, 3000 }); present.finalState = PresentResult::Presented; auto result = DisplayIndexing::Calculate(present, nullptr); @@ -608,7 +608,7 @@ namespace MetricsCoreTests { // Verify template works with FrameData FrameData present{}; - present.displayed.push_back({ FrameType::Application, 1000 }); + present.displayed.PushBack({ FrameType::Application, 1000 }); present.finalState = PresentResult::Presented; auto result = DisplayIndexing::Calculate(present, nullptr); @@ -841,7 +841,7 @@ namespace MetricsCoreTests uint64_t presentStartTime, uint64_t timeInPresent, uint64_t readyTime, - const std::vector>& displayed, + const DisplayedVector& displayed, uint64_t appSimStartTime = 0, uint64_t pclSimStartTime = 0, uint64_t flipDelay = 0) @@ -973,7 +973,7 @@ TEST_CLASS(UnifiedSwapChainTests) Assert::AreEqual(size_t(1), out.size()); Assert::IsNotNull(out[0].presentPtr); - Assert::AreEqual(size_t(1), out[0].presentPtr->displayed.size()); + Assert::AreEqual(size_t(1), out[0].presentPtr->displayed.Size()); Assert::AreEqual((int)FrameType::Application, (int)out[0].presentPtr->displayed[0].first); } @@ -991,7 +991,7 @@ TEST_CLASS(UnifiedSwapChainTests) Assert::AreEqual(size_t(1), out.size()); Assert::IsNotNull(out[0].presentPtr); - Assert::AreEqual(size_t(1), out[0].presentPtr->displayed.size()); + Assert::AreEqual(size_t(1), out[0].presentPtr->displayed.Size()); Assert::AreEqual((int)FrameType::Application, (int)out[0].presentPtr->displayed[0].first); } @@ -1835,7 +1835,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) /*presentStartTime*/ 1'016'000, // slightly later than first /*timeInPresent*/ 300'000, /*readyTime*/ 1'616'000, - std::vector>{ + DisplayedVector{ { FrameType::Application, 2'000'000 } // one displayed instance }); second.gpuStartTime = 1'200'000; @@ -1846,7 +1846,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) /*presentStartTime*/ 2'100'000, /*timeInPresent*/ 100'000, /*readyTime*/ 2'200'000, - std::vector>{ + DisplayedVector{ { FrameType::Application, 2'300'000 } }); @@ -1939,11 +1939,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.readyTime = 2'050'000; frame.finalState = PresentResult::Presented; // Single displayed; will be postponed unless nextDisplayed provided - frame.displayed.push_back({ FrameType::Application, 2'500'000 }); + frame.displayed.PushBack({ FrameType::Application, 2'500'000 }); FrameData next{}; // provide nextDisplayed to process postponed next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 3'000'000 }); + next.displayed.PushBack({ FrameType::Application, 3'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -1962,11 +1962,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.readyTime = 5'030'000; frame.finalState = PresentResult::Presented; // Displayed generated frame (e.g., Repeated/Composed/Desktop depending on enum) - frame.displayed.push_back({ FrameType::Intel_XEFG, 5'100'000 }); + frame.displayed.PushBack({ FrameType::Intel_XEFG, 5'100'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 6'000'000 }); + next.displayed.PushBack({ FrameType::Application, 6'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -2005,11 +2005,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 20'000; frame.readyTime = 2'050'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 2'500'000 }); + frame.displayed.PushBack({ FrameType::Application, 2'500'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'800'000 }); + next.displayed.PushBack({ FrameType::Application, 2'800'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -2029,13 +2029,13 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 30'000; frame.readyTime = 3'050'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 3'100'000 }); - frame.displayed.push_back({ FrameType::Repeated, 3'400'000 }); - frame.displayed.push_back({ FrameType::Repeated, 3'700'000 }); + frame.displayed.PushBack({ FrameType::Application, 3'100'000 }); + frame.displayed.PushBack({ FrameType::Repeated, 3'400'000 }); + frame.displayed.PushBack({ FrameType::Repeated, 3'700'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 4'000'000 }); + next.displayed.PushBack({ FrameType::Application, 4'000'000 }); auto results1 = ComputeMetricsForPresent(qpc, frame, nullptr, chain); Assert::AreEqual(size_t(2), results1.size()); @@ -2067,11 +2067,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 50'000; frame.readyTime = 5'100'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 5'500'000 }); + frame.displayed.PushBack({ FrameType::Application, 5'500'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 6'000'000 }); + next.displayed.PushBack({ FrameType::Application, 6'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -2091,11 +2091,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 50'000; frame.readyTime = 5'100'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 5'500'000 }); + frame.displayed.PushBack({ FrameType::Application, 5'500'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 6'000'000 }); + next.displayed.PushBack({ FrameType::Application, 6'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -2135,13 +2135,13 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 50'000; frame.readyTime = 5'100'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 5'500'000 }); - frame.displayed.push_back({ FrameType::Repeated, 5'800'000 }); - frame.displayed.push_back({ FrameType::Repeated, 6'100'000 }); + frame.displayed.PushBack({ FrameType::Application, 5'500'000 }); + frame.displayed.PushBack({ FrameType::Repeated, 5'800'000 }); + frame.displayed.PushBack({ FrameType::Repeated, 6'100'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 6'400'000 }); + next.displayed.PushBack({ FrameType::Application, 6'400'000 }); auto results1 = ComputeMetricsForPresent(qpc, frame, nullptr, chain); Assert::AreEqual(size_t(2), results1.size()); @@ -2195,11 +2195,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.readyTime = 7'100'000; frame.flipDelay = 100'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 7'500'000 }); + frame.displayed.PushBack({ FrameType::Application, 7'500'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 8'000'000 }); + next.displayed.PushBack({ FrameType::Application, 8'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -2222,11 +2222,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.readyTime = 7'100'000; frame.flipDelay = 0; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 7'500'000 }); + frame.displayed.PushBack({ FrameType::Application, 7'500'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 8'000'000 }); + next.displayed.PushBack({ FrameType::Application, 8'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -2248,11 +2248,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.readyTime = 7'100'000; frame.flipDelay = 50'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Repeated, 7'500'000 }); + frame.displayed.PushBack({ FrameType::Repeated, 7'500'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 8'000'000 }); + next.displayed.PushBack({ FrameType::Application, 8'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -2296,11 +2296,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 90'000; frame.readyTime = 9'100'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 9'500'000 }); + frame.displayed.PushBack({ FrameType::Application, 9'500'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 10'000'000 }); + next.displayed.PushBack({ FrameType::Application, 10'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -2319,13 +2319,13 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 90'000; frame.readyTime = 9'100'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 9'500'000 }); - frame.displayed.push_back({ FrameType::Repeated, 9'800'000 }); - frame.displayed.push_back({ FrameType::Repeated, 10'100'000 }); + frame.displayed.PushBack({ FrameType::Application, 9'500'000 }); + frame.displayed.PushBack({ FrameType::Repeated, 9'800'000 }); + frame.displayed.PushBack({ FrameType::Repeated, 10'100'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 10'400'000 }); + next.displayed.PushBack({ FrameType::Application, 10'400'000 }); auto results1 = ComputeMetricsForPresent(qpc, frame, nullptr, chain); Assert::AreEqual(size_t(2), results1.size()); @@ -2347,11 +2347,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 90'000; frame.readyTime = 9'100'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Repeated, 9'700'000 }); + frame.displayed.PushBack({ FrameType::Repeated, 9'700'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 10'000'000 }); + next.displayed.PushBack({ FrameType::Application, 10'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -2381,7 +2381,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) first.flipDelay = 200'000; // 0.02ms at 10MHz first.finalState = PresentResult::Presented; // First's screen time is 5'500'000 - first.displayed.push_back({ FrameType::Application, 5'500'000 }); + first.displayed.PushBack({ FrameType::Application, 5'500'000 }); // Second frame (next displayed) FrameData second{}; @@ -2392,7 +2392,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) second.finalState = PresentResult::Presented; // Second's raw screen time is 5'000'000, which is EARLIER than first's (5'500'000) // This triggers NV2 adjustment - second.displayed.push_back({ FrameType::Application, 5'000'000 }); + second.displayed.PushBack({ FrameType::Application, 5'000'000 }); // Process first frame with second as nextDisplayed auto resultsFirst = ComputeMetricsForPresent(qpc, first, &second, chain); @@ -2401,7 +2401,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) // Now process second frame (which should have been adjusted by NV2) FrameData third{}; third.finalState = PresentResult::Presented; - third.displayed.push_back({ FrameType::Application, 6'000'000 }); + third.displayed.PushBack({ FrameType::Application, 6'000'000 }); auto resultsSecond = ComputeMetricsForPresent(qpc, second, &third, chain); Assert::AreEqual(size_t(1), resultsSecond.size()); @@ -2445,11 +2445,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) current.flipDelay = 75'000; current.finalState = PresentResult::Presented; // Current screen time is LATER than lastDisplayedScreenTime, so no NV1 adjustment - current.displayed.push_back({ FrameType::Application, 4'000'000 }); + current.displayed.PushBack({ FrameType::Application, 4'000'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 5'000'000 }); + next.displayed.PushBack({ FrameType::Application, 5'000'000 }); auto results = ComputeMetricsForPresent(qpc, current, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -2487,7 +2487,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) first.flipDelay = 100'000; first.finalState = PresentResult::Presented; // First screen time is 5'000'000 - first.displayed.push_back({ FrameType::Application, 5'000'000 }); + first.displayed.PushBack({ FrameType::Application, 5'000'000 }); // Second frame with screen time >= first (no collapse condition) FrameData second{}; @@ -2497,14 +2497,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) second.flipDelay = 50'000; second.finalState = PresentResult::Presented; // Second screen time is equal to first (5'000'000), so NV2 should NOT adjust - second.displayed.push_back({ FrameType::Application, 5'000'000 }); + second.displayed.PushBack({ FrameType::Application, 5'000'000 }); auto resultsFirst = ComputeMetricsForPresent(qpc, first, &second, chain); Assert::AreEqual(size_t(1), resultsFirst.size()); FrameData third{}; third.finalState = PresentResult::Presented; - third.displayed.push_back({ FrameType::Application, 6'000'000 }); + third.displayed.PushBack({ FrameType::Application, 6'000'000 }); auto resultsSecond = ComputeMetricsForPresent(qpc, second, &third, chain); Assert::AreEqual(size_t(1), resultsSecond.size()); @@ -2544,7 +2544,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) current.readyTime = 4'100'000; current.flipDelay = 100'000; current.finalState = PresentResult::Presented; - current.displayed.push_back({ FrameType::Application, 5'000'000 }); + current.displayed.PushBack({ FrameType::Application, 5'000'000 }); auto results = ComputeMetricsForPresent(qpc, current, nullptr, chain, MetricsVersion::V1); Assert::AreEqual(size_t(1), results.size()); @@ -2588,11 +2588,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 50'000; frame.readyTime = 1'100'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 2'000'000 }); + frame.displayed.PushBack({ FrameType::Application, 2'000'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'500'000 }); + next.displayed.PushBack({ FrameType::Application, 2'500'000 }); // Set up chain with prior app present to establish cpuStart FrameData priorApp{}; @@ -2624,11 +2624,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 50'000; frame.readyTime = 1'100'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 2'000'000 }); + frame.displayed.PushBack({ FrameType::Application, 2'000'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'500'000 }); + next.displayed.PushBack({ FrameType::Application, 2'500'000 }); FrameData priorApp{}; priorApp.presentStartTime = 1'700'000; @@ -2681,11 +2681,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 50'000; frame.readyTime = 1'100'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 3'000'000 }); + frame.displayed.PushBack({ FrameType::Application, 3'000'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 3'500'000 }); + next.displayed.PushBack({ FrameType::Application, 3'500'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -2716,11 +2716,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 50'000; frame.readyTime = 1'500'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 2'000'000 }); + frame.displayed.PushBack({ FrameType::Application, 2'000'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'500'000 }); + next.displayed.PushBack({ FrameType::Application, 2'500'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -2746,11 +2746,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 50'000; frame.readyTime = 2'000'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 2'000'000 }); + frame.displayed.PushBack({ FrameType::Application, 2'000'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'500'000 }); + next.displayed.PushBack({ FrameType::Application, 2'500'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -2796,11 +2796,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 50'000; frame.readyTime = 70'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 2'000'000 }); + frame.displayed.PushBack({ FrameType::Application, 2'000'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'500'000 }); + next.displayed.PushBack({ FrameType::Application, 2'500'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -2837,13 +2837,13 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 50'000; frame.readyTime = 1'100'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 2'000'000 }); - frame.displayed.push_back({ FrameType::Repeated, 2'100'000 }); - frame.displayed.push_back({ FrameType::Repeated, 2'200'000 }); + frame.displayed.PushBack({ FrameType::Application, 2'000'000 }); + frame.displayed.PushBack({ FrameType::Repeated, 2'100'000 }); + frame.displayed.PushBack({ FrameType::Repeated, 2'200'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'500'000 }); + next.displayed.PushBack({ FrameType::Application, 2'500'000 }); FrameData priorApp{}; priorApp.presentStartTime = 800'000; @@ -2892,13 +2892,13 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 50'000; frame.readyTime = 1'500'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 2'000'000 }); - frame.displayed.push_back({ FrameType::Intel_XEFG, 2'100'000 }); - frame.displayed.push_back({ FrameType::Intel_XEFG, 2'200'000 }); + frame.displayed.PushBack({ FrameType::Application, 2'000'000 }); + frame.displayed.PushBack({ FrameType::Intel_XEFG, 2'100'000 }); + frame.displayed.PushBack({ FrameType::Intel_XEFG, 2'200'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'500'000 }); + next.displayed.PushBack({ FrameType::Application, 2'500'000 }); FrameData priorApp{}; priorApp.presentStartTime = 800'000; @@ -2951,7 +2951,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.flipDelay = 50'000; frame.finalState = PresentResult::Presented; // Raw screen time is 4'000'000, greater than next screen time - frame.displayed.push_back({ FrameType::Application, 4'000'000 }); + frame.displayed.PushBack({ FrameType::Application, 4'000'000 }); FrameData next1{}; next1.presentStartTime = 2'000'000; @@ -2959,14 +2959,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) next1.readyTime = 2'100'000; next1.flipDelay = 30'000; next1.finalState = PresentResult::Presented; - next1.displayed.push_back({ FrameType::Application, 3'000'000 }); + next1.displayed.PushBack({ FrameType::Application, 3'000'000 }); FrameData next2{}; next2.presentStartTime = 3'000'000; next2.timeInPresent = 50'000; next2.readyTime = 3'100'000; next2.finalState = PresentResult::Presented; - next2.displayed.push_back({ FrameType::Application, 5'000'000 }); + next2.displayed.PushBack({ FrameType::Application, 5'000'000 }); // Set up chain with prior app present to establish cpuStart FrameData priorApp{}; @@ -3015,7 +3015,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.flipDelay = 50'000; frame.finalState = PresentResult::Presented; // Raw screen time is 4'000'000, greater than next screen time - frame.displayed.push_back({ FrameType::Application, 4'000'000 }); + frame.displayed.PushBack({ FrameType::Application, 4'000'000 }); FrameData next1{}; next1.presentStartTime = 2'000'000; @@ -3023,14 +3023,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) next1.readyTime = 2'100'000; next1.flipDelay = 30'000; next1.finalState = PresentResult::Presented; - next1.displayed.push_back({ FrameType::Application, 3'000'000 }); + next1.displayed.PushBack({ FrameType::Application, 3'000'000 }); FrameData next2{}; next2.presentStartTime = 3'000'000; next2.timeInPresent = 50'000; next2.readyTime = 3'100'000; next2.finalState = PresentResult::Presented; - next2.displayed.push_back({ FrameType::Application, 5'000'000 }); + next2.displayed.PushBack({ FrameType::Application, 5'000'000 }); // Set up chain with prior app present to establish cpuStart FrameData priorApp{}; @@ -3074,11 +3074,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 50'000; frame.readyTime = 1'100'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 2'000'000 }); + frame.displayed.PushBack({ FrameType::Application, 2'000'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'500'000 }); + next.displayed.PushBack({ FrameType::Application, 2'500'000 }); FrameData priorApp{}; priorApp.presentStartTime = 2'500'000; @@ -3110,11 +3110,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 50'000; frame.readyTime = 3'000'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 2'000'000 }); + frame.displayed.PushBack({ FrameType::Application, 2'000'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'500'000 }); + next.displayed.PushBack({ FrameType::Application, 2'500'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3141,11 +3141,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 50'000; frame.readyTime = 1'100'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 2'000'000 }); + frame.displayed.PushBack({ FrameType::Application, 2'000'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'500'000 }); + next.displayed.PushBack({ FrameType::Application, 2'500'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3175,11 +3175,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 50'000; frame.readyTime = 1'100'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 2'000'000 }); + frame.displayed.PushBack({ FrameType::Application, 2'000'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'500'000 }); + next.displayed.PushBack({ FrameType::Application, 2'500'000 }); FrameData priorApp{}; priorApp.presentStartTime = 1'000'000; @@ -3215,7 +3215,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorFrame.timeInPresent = 200'000; priorFrame.readyTime = 1'100'000; priorFrame.finalState = PresentResult::Presented; - priorFrame.displayed.push_back({ FrameType::Application, 1'200'000 }); + priorFrame.displayed.PushBack({ FrameType::Application, 1'200'000 }); chain.lastAppPresent = priorFrame; @@ -3224,11 +3224,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 100'000; frame.readyTime = 1'200'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'300'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'300'000 }); FrameData next{}; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'400'000 }); + next.displayed.PushBack({ FrameType::Application, 1'400'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3255,7 +3255,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.appPropagatedPresentStartTime = 800'000; priorApp.appPropagatedTimeInPresent = 200'000; priorApp.finalState = PresentResult::Presented; - priorApp.displayed.push_back({ FrameType::Application, 1'300'000 }); + priorApp.displayed.PushBack({ FrameType::Application, 1'300'000 }); chain.lastAppPresent = priorApp; @@ -3265,7 +3265,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 100'000; frame.readyTime = 1'600'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'700'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'700'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -3273,7 +3273,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 80'000; next.readyTime = 2'100'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'200'000 }); + next.displayed.PushBack({ FrameType::Application, 2'200'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3301,7 +3301,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 100'000; frame.readyTime = 5'200'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 5'500'000 }); + frame.displayed.PushBack({ FrameType::Application, 5'500'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -3309,7 +3309,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 80'000; next.readyTime = 6'100'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 6'300'000 }); + next.displayed.PushBack({ FrameType::Application, 6'300'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3336,7 +3336,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorFrame.timeInPresent = 200'000; priorFrame.readyTime = 1'100'000; priorFrame.finalState = PresentResult::Presented; - priorFrame.displayed.push_back({ FrameType::Application, 1'200'000 }); + priorFrame.displayed.PushBack({ FrameType::Application, 1'200'000 }); chain.lastAppPresent = priorFrame; @@ -3346,7 +3346,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 0; // Zero present duration frame.readyTime = 1'000'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'100'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'100'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -3354,7 +3354,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'600'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'700'000 }); + next.displayed.PushBack({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3379,7 +3379,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorFrame.timeInPresent = 100'000; priorFrame.readyTime = 1'100'000; priorFrame.finalState = PresentResult::Presented; - priorFrame.displayed.push_back({ FrameType::Application, 1'200'000 }); + priorFrame.displayed.PushBack({ FrameType::Application, 1'200'000 }); chain.lastAppPresent = priorFrame; @@ -3389,7 +3389,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 200'000; // 20 ms frame.readyTime = 1'300'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'400'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'400'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -3397,7 +3397,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'900'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'000'000 }); + next.displayed.PushBack({ FrameType::Application, 2'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3421,7 +3421,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorFrame.timeInPresent = 100'000; priorFrame.readyTime = 1'100'000; priorFrame.finalState = PresentResult::Presented; - priorFrame.displayed.push_back({ FrameType::Application, 1'200'000 }); + priorFrame.displayed.PushBack({ FrameType::Application, 1'200'000 }); chain.lastAppPresent = priorFrame; @@ -3432,7 +3432,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.readyTime = 1'300'000; frame.appPropagatedTimeInPresent = 150'000; // 15 ms (propagated value) frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'400'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'400'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -3440,7 +3440,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'900'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'000'000 }); + next.displayed.PushBack({ FrameType::Application, 2'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3465,7 +3465,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorFrame.timeInPresent = 100'000; priorFrame.readyTime = 1'100'000; priorFrame.finalState = PresentResult::Presented; - priorFrame.displayed.push_back({ FrameType::Application, 1'200'000 }); + priorFrame.displayed.PushBack({ FrameType::Application, 1'200'000 }); chain.lastAppPresent = priorFrame; @@ -3475,7 +3475,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 0; // Zero CPU wait time frame.readyTime = 1'100'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'200'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'200'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -3483,7 +3483,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'700'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'800'000 }); + next.displayed.PushBack({ FrameType::Application, 1'800'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3513,7 +3513,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; priorApp.finalState = PresentResult::Presented; - priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + priorApp.displayed.PushBack({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -3525,7 +3525,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.gpuStartTime = 1'050'000; frame.gpuDuration = 200'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'400'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'400'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -3533,7 +3533,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'700'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'800'000 }); + next.displayed.PushBack({ FrameType::Application, 1'800'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3556,7 +3556,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; priorApp.finalState = PresentResult::Presented; - priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + priorApp.displayed.PushBack({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -3570,7 +3570,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.appPropagatedGPUStartTime = 1'080'000; frame.appPropagatedGPUDuration = 200'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'400'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'400'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -3578,7 +3578,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'700'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'800'000 }); + next.displayed.PushBack({ FrameType::Application, 1'800'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3602,7 +3602,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.timeInPresent = 500'000; // cpuStart = 2'000'000 priorApp.readyTime = 2'000'000; priorApp.finalState = PresentResult::Presented; - priorApp.displayed.push_back({ FrameType::Application, 2'100'000 }); + priorApp.displayed.PushBack({ FrameType::Application, 2'100'000 }); chain.lastAppPresent = priorApp; @@ -3614,7 +3614,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.gpuStartTime = 1'900'000; // Earlier than cpuStart frame.gpuDuration = 300'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 2'400'000 }); + frame.displayed.PushBack({ FrameType::Application, 2'400'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -3622,7 +3622,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 2'700'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'800'000 }); + next.displayed.PushBack({ FrameType::Application, 2'800'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3645,7 +3645,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; priorApp.finalState = PresentResult::Presented; - priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + priorApp.displayed.PushBack({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -3657,7 +3657,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.gpuStartTime = 1'050'000; frame.gpuDuration = 500'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'300'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'300'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -3665,7 +3665,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'600'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'700'000 }); + next.displayed.PushBack({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3689,7 +3689,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; priorApp.finalState = PresentResult::Presented; - priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + priorApp.displayed.PushBack({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -3701,7 +3701,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.gpuStartTime = 1'050'000; frame.gpuDuration = 0; // No GPU work frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'300'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'300'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -3709,7 +3709,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'600'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'700'000 }); + next.displayed.PushBack({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3732,7 +3732,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; priorApp.finalState = PresentResult::Presented; - priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + priorApp.displayed.PushBack({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -3746,7 +3746,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.appPropagatedGPUStartTime = 1'050'000; frame.appPropagatedGPUDuration = 450'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'300'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'300'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -3754,7 +3754,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'600'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'700'000 }); + next.displayed.PushBack({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3779,7 +3779,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; priorApp.finalState = PresentResult::Presented; - priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + priorApp.displayed.PushBack({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -3791,7 +3791,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.gpuStartTime = 1'000'000; frame.gpuDuration = 500'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'700'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'700'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -3799,7 +3799,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 2'000'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'100'000 }); + next.displayed.PushBack({ FrameType::Application, 2'100'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3828,7 +3828,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; priorApp.finalState = PresentResult::Presented; - priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + priorApp.displayed.PushBack({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -3840,7 +3840,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.gpuStartTime = 1'000'000; frame.gpuDuration = 600'000; // Equal to total frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'700'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'700'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -3848,7 +3848,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 2'000'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'100'000 }); + next.displayed.PushBack({ FrameType::Application, 2'100'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3874,7 +3874,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; priorApp.finalState = PresentResult::Presented; - priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + priorApp.displayed.PushBack({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -3886,7 +3886,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.gpuStartTime = 1'000'000; frame.gpuDuration = 700'000; // Greater than total (impossible) frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'700'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'700'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -3894,7 +3894,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 2'000'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'100'000 }); + next.displayed.PushBack({ FrameType::Application, 2'100'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3920,7 +3920,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; priorApp.finalState = PresentResult::Presented; - priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + priorApp.displayed.PushBack({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -3935,7 +3935,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.appPropagatedReadyTime = 1'550'000; frame.appPropagatedGPUDuration = 450'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'700'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'700'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -3943,7 +3943,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 2'000'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'100'000 }); + next.displayed.PushBack({ FrameType::Application, 2'100'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -3975,7 +3975,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; priorApp.finalState = PresentResult::Presented; - priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + priorApp.displayed.PushBack({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -3988,7 +3988,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.gpuDuration = 500'000; frame.gpuVideoDuration = 200'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'300'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'300'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -3996,7 +3996,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'600'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'700'000 }); + next.displayed.PushBack({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -4020,7 +4020,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; priorApp.finalState = PresentResult::Presented; - priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + priorApp.displayed.PushBack({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -4033,7 +4033,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.gpuDuration = 500'000; frame.gpuVideoDuration = 0; // No video work frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'300'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'300'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -4041,7 +4041,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'600'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'700'000 }); + next.displayed.PushBack({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -4063,7 +4063,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; priorApp.finalState = PresentResult::Presented; - priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + priorApp.displayed.PushBack({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -4079,7 +4079,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.appPropagatedGPUDuration = 450'000; frame.appPropagatedGPUVideoDuration = 180'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'300'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'300'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -4087,7 +4087,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'600'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'700'000 }); + next.displayed.PushBack({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -4112,7 +4112,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; priorApp.finalState = PresentResult::Presented; - priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + priorApp.displayed.PushBack({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -4125,7 +4125,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.gpuDuration = 500'000; frame.gpuVideoDuration = 200'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'300'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'300'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -4133,7 +4133,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'600'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'700'000 }); + next.displayed.PushBack({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -4160,7 +4160,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; priorApp.finalState = PresentResult::Presented; - priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + priorApp.displayed.PushBack({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -4173,7 +4173,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.gpuDuration = 300'000; // 30 ms frame.gpuVideoDuration = 500'000; // 50 ms (larger than gpuDuration) frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'300'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'300'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -4181,7 +4181,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'600'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'700'000 }); + next.displayed.PushBack({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -4216,7 +4216,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 100'000; frame.readyTime = 1'200'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'300'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'300'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -4224,7 +4224,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'600'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'700'000 }); + next.displayed.PushBack({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -4262,7 +4262,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.gpuStartTime = 1'150'000; frame.gpuDuration = 200'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Repeated, 1'300'000 }); + frame.displayed.PushBack({ FrameType::Repeated, 1'300'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -4270,7 +4270,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'600'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'700'000 }); + next.displayed.PushBack({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -4295,7 +4295,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; priorApp.finalState = PresentResult::Presented; - priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + priorApp.displayed.PushBack({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -4333,7 +4333,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) lastApp.timeInPresent = 200'000; lastApp.readyTime = 1'000'000; lastApp.finalState = PresentResult::Presented; - lastApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + lastApp.displayed.PushBack({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = lastApp; @@ -4343,7 +4343,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 100'000; frame.readyTime = 1'300'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'400'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'400'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -4351,7 +4351,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'700'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'800'000 }); + next.displayed.PushBack({ FrameType::Application, 1'800'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -4373,7 +4373,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) lastPresent.timeInPresent = 200'000; lastPresent.readyTime = 1'000'000; lastPresent.finalState = PresentResult::Presented; - lastPresent.displayed.push_back({ FrameType::Application, 1'100'000 }); + lastPresent.displayed.PushBack({ FrameType::Application, 1'100'000 }); chain.lastPresent = lastPresent; // lastAppPresent remains unset @@ -4384,7 +4384,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 100'000; frame.readyTime = 1'300'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'400'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'400'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -4392,7 +4392,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'700'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'800'000 }); + next.displayed.PushBack({ FrameType::Application, 1'800'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -4415,7 +4415,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 100'000; frame.readyTime = 5'200'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 5'500'000 }); + frame.displayed.PushBack({ FrameType::Application, 5'500'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -4423,7 +4423,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 6'100'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 6'300'000 }); + next.displayed.PushBack({ FrameType::Application, 6'300'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -4446,7 +4446,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.readyTime = 5'200'000; frame.flipDelay = 777; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 5'500'000 }); + frame.displayed.PushBack({ FrameType::Application, 5'500'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -4454,7 +4454,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 6'100'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 6'300'000 }); + next.displayed.PushBack({ FrameType::Application, 6'300'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -4484,7 +4484,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.timeInPresent = 100'000'000; priorApp.readyTime = 1'000'000'000; priorApp.finalState = PresentResult::Presented; - priorApp.displayed.push_back({ FrameType::Application, 1'100'000'000 }); + priorApp.displayed.PushBack({ FrameType::Application, 1'100'000'000 }); chain.lastAppPresent = priorApp; @@ -4494,7 +4494,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.timeInPresent = 100'000'000; frame.readyTime = 1'200'000'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'300'000'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'300'000'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -4502,7 +4502,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000'000; next.readyTime = 1'700'000'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'800'000'000 }); + next.displayed.PushBack({ FrameType::Application, 1'800'000'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -4528,7 +4528,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; priorApp.finalState = PresentResult::Presented; - priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + priorApp.displayed.PushBack({ FrameType::Application, 1'100'000 }); chain.lastAppPresent = priorApp; @@ -4540,7 +4540,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.gpuStartTime = 1'000'001; // Only 1 tick later than cpuStart frame.gpuDuration = 200'000; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'300'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'300'000 }); // Next displayed frame (required to process current frame's display) FrameData next{}; @@ -4548,7 +4548,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'600'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'700'000 }); + next.displayed.PushBack({ FrameType::Application, 1'700'000 }); auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); Assert::AreEqual(size_t(1), results.size()); @@ -4576,14 +4576,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frameA.gpuDuration = 400'000; frameA.gpuVideoDuration = 0; // No video frameA.finalState = PresentResult::Presented; - frameA.displayed.push_back({ FrameType::Application, 1'500'000 }); + frameA.displayed.PushBack({ FrameType::Application, 1'500'000 }); FrameData nextA{}; nextA.presentStartTime = 2'000'000; nextA.timeInPresent = 50'000; nextA.readyTime = 2'100'000; nextA.finalState = PresentResult::Presented; - nextA.displayed.push_back({ FrameType::Application, 2'200'000 }); + nextA.displayed.PushBack({ FrameType::Application, 2'200'000 }); auto resultsA = ComputeMetricsForPresent(qpc, frameA, &nextA, chain); Assert::AreEqual(size_t(1), resultsA.size()); @@ -4598,14 +4598,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frameB.gpuDuration = 400'000; frameB.gpuVideoDuration = 300'000; // 30 ms of video frameB.finalState = PresentResult::Presented; - frameB.displayed.push_back({ FrameType::Application, 2'600'000 }); + frameB.displayed.PushBack({ FrameType::Application, 2'600'000 }); FrameData nextB{}; nextB.presentStartTime = 3'000'000; nextB.timeInPresent = 50'000; nextB.readyTime = 3'100'000; nextB.finalState = PresentResult::Presented; - nextB.displayed.push_back({ FrameType::Application, 3'200'000 }); + nextB.displayed.PushBack({ FrameType::Application, 3'200'000 }); auto resultsB = ComputeMetricsForPresent(qpc, frameB, &nextB, chain); Assert::AreEqual(size_t(1), resultsB.size()); @@ -4648,11 +4648,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.appSimStartTime = 0; // No app instrumentation yet frame.pclSimStartTime = 0; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'000'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'000'000 }); // Verify frame setup Assert::AreEqual(uint64_t(0), frame.appSimStartTime); - Assert::AreEqual(size_t(1), frame.displayed.size()); + Assert::AreEqual(size_t(1), frame.displayed.Size()); // Create nextDisplayed to allow processing FrameData next{}; @@ -4660,7 +4660,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 400; next.readyTime = 2'500'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'000'000 }); + next.displayed.PushBack({ FrameType::Application, 2'000'000 }); // Action: Compute metrics for this frame auto metricsVector = ComputeMetricsForPresent(qpc, frame, &next, state); @@ -4710,7 +4710,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.appSimStartTime = 100; // First valid app sim start frame.pclSimStartTime = 0; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'000'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'000'000 }); // Create nextDisplayed FrameData frame2{}; @@ -4718,7 +4718,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame2.timeInPresent = 400; frame2.readyTime = 2'500'000; frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 2'000'000 }); + frame2.displayed.PushBack({ FrameType::Application, 2'000'000 }); // Action: Compute metrics auto metricsVector = ComputeMetricsForPresent(qpc, frame, &frame2, state); @@ -4764,12 +4764,12 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.timeInPresent = 300; frame1.appSimStartTime = 100; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 900'000 }); + frame1.displayed.PushBack({ FrameType::Application, 900'000 }); FrameData next1{}; next1.presentStartTime = 1'500'000; next1.finalState = PresentResult::Presented; - next1.displayed.push_back({ FrameType::Application, 1'500'000 }); + next1.displayed.PushBack({ FrameType::Application, 1'500'000 }); ComputeMetricsForPresent(qpc, frame1, &next1, state); // After this, source is AppProvider, firstAppSimStartTime = 100 @@ -4782,7 +4782,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.appSimStartTime = 150; // 50 ticks later frame.pclSimStartTime = 0; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'000'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'000'000 }); // Create nextDisplayed FrameData next{}; @@ -4790,7 +4790,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 400; next.readyTime = 2'500'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'000'000 }); + next.displayed.PushBack({ FrameType::Application, 2'000'000 }); // Action: Compute metrics auto metricsVector = ComputeMetricsForPresent(qpc, frame, &next, state); @@ -4848,14 +4848,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.appSimStartTime = 100; frame1.pclSimStartTime = 0; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 1'000'000 }); + frame1.displayed.PushBack({ FrameType::Application, 1'000'000 }); FrameData next1{}; next1.presentStartTime = 2'000'000; next1.timeInPresent = 400; next1.readyTime = 2'500'000; next1.finalState = PresentResult::Presented; - next1.displayed.push_back({ FrameType::Application, 2'000'000 }); + next1.displayed.PushBack({ FrameType::Application, 2'000'000 }); auto metrics1 = ComputeMetricsForPresent(qpc, frame1, &next1, state); Assert::AreEqual(size_t(1), metrics1.size()); @@ -4883,14 +4883,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame2.appSimStartTime = 150; frame2.pclSimStartTime = 0; frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 3'000'000 }); + frame2.displayed.PushBack({ FrameType::Application, 3'000'000 }); FrameData next2{}; next2.presentStartTime = 4'000'000; next2.timeInPresent = 400; next2.readyTime = 4'500'000; next2.finalState = PresentResult::Presented; - next2.displayed.push_back({ FrameType::Application, 4'000'000 }); + next2.displayed.PushBack({ FrameType::Application, 4'000'000 }); auto metrics2 = ComputeMetricsForPresent(qpc, frame2, &next2, state); Assert::AreEqual(size_t(1), metrics2.size()); @@ -4920,14 +4920,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame3.appSimStartTime = 250; frame3.pclSimStartTime = 0; frame3.finalState = PresentResult::Presented; - frame3.displayed.push_back({ FrameType::Application, 5'000'000 }); + frame3.displayed.PushBack({ FrameType::Application, 5'000'000 }); FrameData next3{}; next3.presentStartTime = 6'000'000; next3.timeInPresent = 400; next3.readyTime = 6'500'000; next3.finalState = PresentResult::Presented; - next3.displayed.push_back({ FrameType::Application, 6'000'000 }); + next3.displayed.PushBack({ FrameType::Application, 6'000'000 }); auto metrics3 = ComputeMetricsForPresent(qpc, frame3, &next3, state); Assert::AreEqual(size_t(1), metrics3.size()); @@ -5008,7 +5008,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frameDisplayed.readyTime = 3'050'000; frameDisplayed.appSimStartTime = 200; frameDisplayed.finalState = PresentResult::Presented; - frameDisplayed.displayed.push_back({ FrameType::Application, 3'500'000 }); + frameDisplayed.displayed.PushBack({ FrameType::Application, 3'500'000 }); // Dummy "next" frame – just to exercise the Case 3 path so that // UpdateAfterPresent() is called for frameDisplayed. @@ -5018,7 +5018,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frameNext.readyTime = 4'050'000; frameNext.appSimStartTime = 250; frameNext.finalState = PresentResult::Presented; - frameNext.displayed.push_back({ FrameType::Application, 4'500'000 }); + frameNext.displayed.PushBack({ FrameType::Application, 4'500'000 }); auto displayedResults = ComputeMetricsForPresent(qpc, frameDisplayed, &frameNext, state); Assert::AreEqual(size_t(1), displayedResults.size()); @@ -5072,11 +5072,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.appSimStartTime = 0; frame.pclSimStartTime = 0; // No PC latency instrumentation yet frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'000'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'000'000 }); // Verify frame setup Assert::AreEqual(uint64_t(0), frame.pclSimStartTime); - Assert::AreEqual(size_t(1), frame.displayed.size()); + Assert::AreEqual(size_t(1), frame.displayed.Size()); // Create nextDisplayed to allow processing FrameData next{}; @@ -5084,7 +5084,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 400; next.readyTime = 2'500'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'000'000 }); + next.displayed.PushBack({ FrameType::Application, 2'000'000 }); // Action: Compute metrics for this frame auto metricsVector = ComputeMetricsForPresent(qpc, frame, &next, state); @@ -5134,7 +5134,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.appSimStartTime = 0; frame.pclSimStartTime = 100; // First valid pcl sim start frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'000'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'000'000 }); // Create nextDisplayed FrameData frame2{}; @@ -5142,7 +5142,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame2.timeInPresent = 400; frame2.readyTime = 2'500'000; frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 2'000'000 }); + frame2.displayed.PushBack({ FrameType::Application, 2'000'000 }); // Action: Compute metrics auto metricsVector = ComputeMetricsForPresent(qpc, frame, &frame2, state); @@ -5187,12 +5187,12 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.timeInPresent = 300; frame1.pclSimStartTime = 100; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 900'000 }); + frame1.displayed.PushBack({ FrameType::Application, 900'000 }); FrameData next1{}; next1.presentStartTime = 1'500'000; next1.finalState = PresentResult::Presented; - next1.displayed.push_back({ FrameType::Application, 1'500'000 }); + next1.displayed.PushBack({ FrameType::Application, 1'500'000 }); ComputeMetricsForPresent(qpc, frame1, &next1, state); // After this, source is PCLatency, firstAppSimStartTime = 100 @@ -5205,7 +5205,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.appSimStartTime = 0; frame.pclSimStartTime = 200; // 100 ticks later frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'000'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'000'000 }); // Create nextDisplayed FrameData next{}; @@ -5213,7 +5213,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 400; next.readyTime = 2'500'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'000'000 }); + next.displayed.PushBack({ FrameType::Application, 2'000'000 }); // Action: Compute metrics auto metricsVector = ComputeMetricsForPresent(qpc, frame, &next, state); @@ -5272,14 +5272,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.appSimStartTime = 0; frame1.pclSimStartTime = 100; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 1'000'000 }); + frame1.displayed.PushBack({ FrameType::Application, 1'000'000 }); FrameData next1{}; next1.presentStartTime = 2'000'000; next1.timeInPresent = 400; next1.readyTime = 2'500'000; next1.finalState = PresentResult::Presented; - next1.displayed.push_back({ FrameType::Application, 2'000'000 }); + next1.displayed.PushBack({ FrameType::Application, 2'000'000 }); auto metrics1 = ComputeMetricsForPresent(qpc, frame1, &next1, state); Assert::AreEqual(size_t(1), metrics1.size()); @@ -5307,14 +5307,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame2.appSimStartTime = 0; frame2.pclSimStartTime = 150; frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 3'000'000 }); + frame2.displayed.PushBack({ FrameType::Application, 3'000'000 }); FrameData next2{}; next2.presentStartTime = 4'000'000; next2.timeInPresent = 400; next2.readyTime = 4'500'000; next2.finalState = PresentResult::Presented; - next2.displayed.push_back({ FrameType::Application, 4'000'000 }); + next2.displayed.PushBack({ FrameType::Application, 4'000'000 }); auto metrics2 = ComputeMetricsForPresent(qpc, frame2, &next2, state); Assert::AreEqual(size_t(1), metrics2.size()); @@ -5344,14 +5344,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame3.appSimStartTime = 0; frame3.pclSimStartTime = 250; frame3.finalState = PresentResult::Presented; - frame3.displayed.push_back({ FrameType::Application, 5'000'000 }); + frame3.displayed.PushBack({ FrameType::Application, 5'000'000 }); FrameData next3{}; next3.presentStartTime = 6'000'000; next3.timeInPresent = 400; next3.readyTime = 6'500'000; next3.finalState = PresentResult::Presented; - next3.displayed.push_back({ FrameType::Application, 6'000'000 }); + next3.displayed.PushBack({ FrameType::Application, 6'000'000 }); auto metrics3 = ComputeMetricsForPresent(qpc, frame3, &next3, state); Assert::AreEqual(size_t(1), metrics3.size()); @@ -5402,14 +5402,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.readyTime = 1'010'000; frame1.pclSimStartTime = 100; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 2'000'000 }); + frame1.displayed.PushBack({ FrameType::Application, 2'000'000 }); FrameData next1{}; next1.presentStartTime = 3'000'000; next1.timeInPresent = 10'000; next1.readyTime = 3'010'000; next1.finalState = PresentResult::Presented; - next1.displayed.push_back({ FrameType::Application, 4'000'000 }); + next1.displayed.PushBack({ FrameType::Application, 4'000'000 }); auto metrics1 = ComputeMetricsForPresent(qpc, frame1, &next1, chain); Assert::AreEqual(size_t(1), metrics1.size()); @@ -5455,14 +5455,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame3.readyTime = 6'010'000; frame3.pclSimStartTime = 300; frame3.finalState = PresentResult::Presented; - frame3.displayed.push_back({ FrameType::Application, 7'000'000 }); + frame3.displayed.PushBack({ FrameType::Application, 7'000'000 }); FrameData next3{}; next3.presentStartTime = 8'000'000; next3.timeInPresent = 10'000; next3.readyTime = 8'010'000; next3.finalState = PresentResult::Presented; - next3.displayed.push_back({ FrameType::Application, 9'000'000 }); + next3.displayed.PushBack({ FrameType::Application, 9'000'000 }); auto metrics3 = ComputeMetricsForPresent(qpc, frame3, &next3, chain); Assert::AreEqual(size_t(1), metrics3.size()); @@ -5506,12 +5506,12 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.timeInPresent = 300; frame1.pclSimStartTime = 100; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 900'000 }); + frame1.displayed.PushBack({ FrameType::Application, 900'000 }); FrameData next1{}; next1.presentStartTime = 1'500'000; next1.finalState = PresentResult::Presented; - next1.displayed.push_back({ FrameType::Application, 1'500'000 }); + next1.displayed.PushBack({ FrameType::Application, 1'500'000 }); auto metrics1 = ComputeMetricsForPresent(qpc, frame1, &next1, state); Assert::AreEqual(size_t(1), metrics1.size()); @@ -5526,7 +5526,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.appSimStartTime = 0; frame.pclSimStartTime = 0; // PCL data disappeared frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'400'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'400'000 }); // Create nextDisplayed FrameData next{}; @@ -5534,7 +5534,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'700'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'800'000 }); + next.displayed.PushBack({ FrameType::Application, 1'800'000 }); // Action: Compute metrics auto metricsVector = ComputeMetricsForPresent(qpc, frame, &next, state); @@ -5588,10 +5588,10 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.appSimStartTime = 0; frame.pclSimStartTime = 0; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'000'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'000'000 }); // Verify frame setup - Assert::AreEqual(size_t(1), frame.displayed.size()); + Assert::AreEqual(size_t(1), frame.displayed.Size()); // Create nextDisplayed to allow processing FrameData next{}; @@ -5599,7 +5599,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 400; next.readyTime = 2'500'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 2'000'000 }); + next.displayed.PushBack({ FrameType::Application, 2'000'000 }); // Action: Compute metrics for this frame auto metricsVector = ComputeMetricsForPresent(qpc, frame, &next, state); @@ -5648,7 +5648,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) priorApp.timeInPresent = 200'000; priorApp.readyTime = 1'000'000; priorApp.finalState = PresentResult::Presented; - priorApp.displayed.push_back({ FrameType::Application, 1'100'000 }); + priorApp.displayed.PushBack({ FrameType::Application, 1'100'000 }); state.lastAppPresent = priorApp; @@ -5660,7 +5660,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame.appSimStartTime = 0; frame.pclSimStartTime = 0; frame.finalState = PresentResult::Presented; - frame.displayed.push_back({ FrameType::Application, 1'400'000 }); + frame.displayed.PushBack({ FrameType::Application, 1'400'000 }); // Create nextDisplayed FrameData next{}; @@ -5668,7 +5668,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) next.timeInPresent = 50'000; next.readyTime = 1'700'000; next.finalState = PresentResult::Presented; - next.displayed.push_back({ FrameType::Application, 1'800'000 }); + next.displayed.PushBack({ FrameType::Application, 1'800'000 }); // Action: Compute metrics auto metricsVector = ComputeMetricsForPresent(qpc, frame, &next, state); @@ -5716,7 +5716,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) prior.timeInPresent = 100'000; // cpuStart1 = 1'100'000 prior.readyTime = 1'200'000; prior.finalState = PresentResult::Presented; - prior.displayed.push_back({ FrameType::Application, 1'300'000 }); + prior.displayed.PushBack({ FrameType::Application, 1'300'000 }); state.lastAppPresent = prior; // Sanity: no provider sim-start yet. @@ -5733,14 +5733,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.appSimStartTime = 0; frame1.pclSimStartTime = 0; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 2'500'000 }); + frame1.displayed.PushBack({ FrameType::Application, 2'500'000 }); FrameData next1{}; next1.presentStartTime = 3'000'000; next1.timeInPresent = 50'000; next1.readyTime = 3'100'000; next1.finalState = PresentResult::Presented; - next1.displayed.push_back({ FrameType::Application, 3'500'000 }); + next1.displayed.PushBack({ FrameType::Application, 3'500'000 }); auto metrics1 = ComputeMetricsForPresent(qpc, frame1, &next1, state); Assert::AreEqual(size_t(1), metrics1.size()); @@ -5767,14 +5767,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame2.appSimStartTime = 0; frame2.pclSimStartTime = 0; frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 4'600'000 }); + frame2.displayed.PushBack({ FrameType::Application, 4'600'000 }); FrameData next2{}; next2.presentStartTime = 5'000'000; next2.timeInPresent = 50'000; next2.readyTime = 5'100'000; next2.finalState = PresentResult::Presented; - next2.displayed.push_back({ FrameType::Application, 5'500'000 }); + next2.displayed.PushBack({ FrameType::Application, 5'500'000 }); auto metrics2 = ComputeMetricsForPresent(qpc, frame2, &next2, state); Assert::AreEqual(size_t(1), metrics2.size()); @@ -5815,12 +5815,12 @@ TEST_CLASS(ComputeMetricsForPresentTests) present.timeInPresent = 100; present.appSimStartTime = 150; present.finalState = PresentResult::Presented; - present.displayed.push_back({ FrameType::Application, 200 }); + present.displayed.PushBack({ FrameType::Application, 200 }); FrameData nextPresent{}; nextPresent.presentStartTime = 2000; nextPresent.finalState = PresentResult::Presented; - nextPresent.displayed.push_back({ FrameType::Application, 2100 }); + nextPresent.displayed.PushBack({ FrameType::Application, 2100 }); auto results = ComputeMetricsForPresent(qpc, present, &nextPresent, state); @@ -5843,25 +5843,25 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.timeInPresent = 100; frame1.appSimStartTime = 100; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 1000 }); + frame1.displayed.PushBack({ FrameType::Application, 1000 }); FrameData frame2{}; frame2.presentStartTime = 2000; frame2.timeInPresent = 100; frame2.appSimStartTime = 150; // sim elapsed = 50 frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 1050 }); // display elapsed = 50 + frame2.displayed.PushBack({ FrameType::Application, 1050 }); // display elapsed = 50 // Process frame 1 FrameData dummyNext{}; dummyNext.finalState = PresentResult::Presented; - dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + dummyNext.displayed.PushBack({ FrameType::Application, 2000 }); auto results1 = ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); // Process frame 2 FrameData frame3{}; frame3.finalState = PresentResult::Presented; - frame3.displayed.push_back({ FrameType::Application, 3000 }); + frame3.displayed.PushBack({ FrameType::Application, 3000 }); auto results2 = ComputeMetricsForPresent(qpc, frame2, &frame3, state); Assert::AreEqual(size_t(1), results2.size()); @@ -5883,23 +5883,23 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.timeInPresent = 100; frame1.appSimStartTime = 100; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 1000 }); + frame1.displayed.PushBack({ FrameType::Application, 1000 }); FrameData frame2{}; frame2.presentStartTime = 2000; frame2.timeInPresent = 100; frame2.appSimStartTime = 140; // sim elapsed = 40 frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 1050 }); // display elapsed = 50 + frame2.displayed.PushBack({ FrameType::Application, 1050 }); // display elapsed = 50 FrameData dummyNext{}; dummyNext.finalState = PresentResult::Presented; - dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + dummyNext.displayed.PushBack({ FrameType::Application, 2000 }); ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); FrameData frame3{}; frame3.finalState = PresentResult::Presented; - frame3.displayed.push_back({ FrameType::Application, 3000 }); + frame3.displayed.PushBack({ FrameType::Application, 3000 }); auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); Assert::IsTrue(results[0].metrics.msAnimationError.has_value()); @@ -5922,23 +5922,23 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.timeInPresent = 100; frame1.appSimStartTime = 100; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 1000 }); + frame1.displayed.PushBack({ FrameType::Application, 1000 }); FrameData frame2{}; frame2.presentStartTime = 2000; frame2.timeInPresent = 100; frame2.appSimStartTime = 160; // sim elapsed = 60 frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 1050 }); // display elapsed = 50 + frame2.displayed.PushBack({ FrameType::Application, 1050 }); // display elapsed = 50 FrameData dummyNext{}; dummyNext.finalState = PresentResult::Presented; - dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + dummyNext.displayed.PushBack({ FrameType::Application, 2000 }); ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); FrameData frame3{}; frame3.finalState = PresentResult::Presented; - frame3.displayed.push_back({ FrameType::Application, 3000 }); + frame3.displayed.PushBack({ FrameType::Application, 3000 }); auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); Assert::IsTrue(results[0].metrics.msAnimationError.has_value()); @@ -5961,23 +5961,23 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.timeInPresent = 100; frame1.appSimStartTime = 150; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 1050 }); + frame1.displayed.PushBack({ FrameType::Application, 1050 }); FrameData frame2{}; frame2.presentStartTime = 2000; frame2.timeInPresent = 100; frame2.appSimStartTime = 140; // backwards! frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 1100 }); + frame2.displayed.PushBack({ FrameType::Application, 1100 }); FrameData dummyNext{}; dummyNext.finalState = PresentResult::Presented; - dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + dummyNext.displayed.PushBack({ FrameType::Application, 2000 }); ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); FrameData frame3{}; frame3.finalState = PresentResult::Presented; - frame3.displayed.push_back({ FrameType::Application, 3000 }); + frame3.displayed.PushBack({ FrameType::Application, 3000 }); auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); Assert::IsFalse(results[0].metrics.msAnimationError.has_value(), @@ -5997,23 +5997,23 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.timeInPresent = 100; frame1.appSimStartTime = 100; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 1000 }); + frame1.displayed.PushBack({ FrameType::Application, 1000 }); FrameData frame2{}; frame2.presentStartTime = 2000; frame2.timeInPresent = 100; frame2.appSimStartTime = 0; // no instrumentation frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 1050 }); + frame2.displayed.PushBack({ FrameType::Application, 1050 }); FrameData dummyNext{}; dummyNext.finalState = PresentResult::Presented; - dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + dummyNext.displayed.PushBack({ FrameType::Application, 2000 }); ComputeMetricsForPresent(qpc, frame1, &dummyNext, swapChain); FrameData frame3{}; frame3.finalState = PresentResult::Presented; - frame3.displayed.push_back({ FrameType::Application, 3000 }); + frame3.displayed.PushBack({ FrameType::Application, 3000 }); auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, swapChain); Assert::IsFalse(results[0].metrics.msAnimationError.has_value(), @@ -6033,23 +6033,23 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.timeInPresent = 100; frame1.appSimStartTime = 100; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 1000 }); + frame1.displayed.PushBack({ FrameType::Application, 1000 }); FrameData frame2{}; frame2.presentStartTime = 2000; frame2.timeInPresent = 100; frame2.appSimStartTime = 150; // sim elapsed = 50 frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 1000 }); // same screen time! + frame2.displayed.PushBack({ FrameType::Application, 1000 }); // same screen time! FrameData dummyNext{}; dummyNext.finalState = PresentResult::Presented; - dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + dummyNext.displayed.PushBack({ FrameType::Application, 2000 }); ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); FrameData frame3{}; frame3.finalState = PresentResult::Presented; - frame3.displayed.push_back({ FrameType::Application, 3000 }); + frame3.displayed.PushBack({ FrameType::Application, 3000 }); auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); Assert::IsFalse(results[0].metrics.msAnimationError.has_value()); @@ -6070,23 +6070,23 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.timeInPresent = 100; frame1.pclSimStartTime = 100; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 1000 }); + frame1.displayed.PushBack({ FrameType::Application, 1000 }); FrameData frame2{}; frame2.presentStartTime = 2000; frame2.timeInPresent = 100; frame2.pclSimStartTime = 140; // PCL sim elapsed = 40 frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 1050 }); // display elapsed = 50 + frame2.displayed.PushBack({ FrameType::Application, 1050 }); // display elapsed = 50 FrameData dummyNext{}; dummyNext.finalState = PresentResult::Presented; - dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + dummyNext.displayed.PushBack({ FrameType::Application, 2000 }); ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); FrameData frame3{}; frame3.finalState = PresentResult::Presented; - frame3.displayed.push_back({ FrameType::Application, 3000 }); + frame3.displayed.PushBack({ FrameType::Application, 3000 }); auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); Assert::IsTrue(results[0].metrics.msAnimationError.has_value()); @@ -6109,7 +6109,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.timeInPresent = 100; frame1.pclSimStartTime = 100; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 1000 }); + frame1.displayed.PushBack({ FrameType::Application, 1000 }); FrameData frame2{}; frame2.presentStartTime = 2000; @@ -6117,16 +6117,16 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame2.pclSimStartTime = 0; // PCL unavailable frame2.appSimStartTime = 150; // app available, but should not be used frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 1050 }); + frame2.displayed.PushBack({ FrameType::Application, 1050 }); FrameData dummyNext{}; dummyNext.finalState = PresentResult::Presented; - dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + dummyNext.displayed.PushBack({ FrameType::Application, 2000 }); ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); FrameData frame3{}; frame3.finalState = PresentResult::Presented; - frame3.displayed.push_back({ FrameType::Application, 3000 }); + frame3.displayed.PushBack({ FrameType::Application, 3000 }); auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); Assert::IsFalse(results[0].metrics.msAnimationError.has_value(), @@ -6146,11 +6146,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) present.timeInPresent = 100; present.pclSimStartTime = 100; // first valid PCL present.finalState = PresentResult::Presented; - present.displayed.push_back({ FrameType::Application, 1000 }); + present.displayed.PushBack({ FrameType::Application, 1000 }); FrameData nextPresent{}; nextPresent.finalState = PresentResult::Presented; - nextPresent.displayed.push_back({ FrameType::Application, 2000 }); + nextPresent.displayed.PushBack({ FrameType::Application, 2000 }); auto results = ComputeMetricsForPresent(qpc, present, &nextPresent, state); @@ -6171,14 +6171,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.presentStartTime = 800; frame1.timeInPresent = 100; // CPU end = 900 frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 1800 }); + frame1.displayed.PushBack({ FrameType::Application, 1800 }); // Frame 2: Continue with CPU source FrameData frame2{}; frame2.presentStartTime = 1000; frame2.timeInPresent = 100; // CPU end = 1100 frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 2000 }); + frame2.displayed.PushBack({ FrameType::Application, 2000 }); // Frame 3: PCL becomes available, triggers source switch FrameData frame3{}; @@ -6187,14 +6187,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame3.pclSimStartTime = 150; // PCL data present frame3.appSimStartTime = 150; frame3.finalState = PresentResult::Presented; - frame3.displayed.push_back({ FrameType::Application, 2100 }); + frame3.displayed.PushBack({ FrameType::Application, 2100 }); // Frame 4: Next frame for completion FrameData frame4{}; frame4.presentStartTime = 1400; frame4.timeInPresent = 100; frame4.finalState = PresentResult::Presented; - frame4.displayed.push_back({ FrameType::Application, 2200 }); + frame4.displayed.PushBack({ FrameType::Application, 2200 }); ComputeMetricsForPresent(qpc, frame1, &frame2, state); ComputeMetricsForPresent(qpc, frame2, &frame3, state); @@ -6225,7 +6225,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.pclSimStartTime = 100; frame1.appSimStartTime = 200; // different value frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 1000 }); + frame1.displayed.PushBack({ FrameType::Application, 1000 }); FrameData frame2{}; frame2.presentStartTime = 2000; @@ -6233,16 +6233,16 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame2.pclSimStartTime = 150; // PCL elapsed = 50 frame2.appSimStartTime = 300; // app elapsed = 100 (should be ignored) frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 1050 }); // display elapsed = 50 + frame2.displayed.PushBack({ FrameType::Application, 1050 }); // display elapsed = 50 FrameData dummyNext{}; dummyNext.finalState = PresentResult::Presented; - dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + dummyNext.displayed.PushBack({ FrameType::Application, 2000 }); ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); FrameData frame3{}; frame3.finalState = PresentResult::Presented; - frame3.displayed.push_back({ FrameType::Application, 3000 }); + frame3.displayed.PushBack({ FrameType::Application, 3000 }); auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); Assert::IsTrue(results[0].metrics.msAnimationError.has_value()); @@ -6270,33 +6270,33 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.presentStartTime = 800; frame1.timeInPresent = 100; // CPU sim start for next frame = 900 frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 1900 }); + frame1.displayed.PushBack({ FrameType::Application, 1900 }); FrameData frame2{}; frame2.presentStartTime = 1000; frame2.timeInPresent = 100; // CPU sim start for next frame = 1100 frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 2000 }); + frame2.displayed.PushBack({ FrameType::Application, 2000 }); FrameData frame3{}; frame3.presentStartTime = 1200; frame3.timeInPresent = 100; // CPU sim start = 1300, elapsed from 1100 = 200 frame3.finalState = PresentResult::Presented; - frame3.displayed.push_back({ FrameType::Application, 2050 }); // display elapsed from 2000 = 50 + frame3.displayed.PushBack({ FrameType::Application, 2050 }); // display elapsed from 2000 = 50 FrameData dummyNext1{}; dummyNext1.finalState = PresentResult::Presented; - dummyNext1.displayed.push_back({ FrameType::Application, 2500 }); + dummyNext1.displayed.PushBack({ FrameType::Application, 2500 }); ComputeMetricsForPresent(qpc, frame1, &dummyNext1, state); FrameData dummyNext2{}; dummyNext2.finalState = PresentResult::Presented; - dummyNext2.displayed.push_back({ FrameType::Application, 3000 }); + dummyNext2.displayed.PushBack({ FrameType::Application, 3000 }); ComputeMetricsForPresent(qpc, frame2, &dummyNext2, state); FrameData frame4{}; frame4.finalState = PresentResult::Presented; - frame4.displayed.push_back({ FrameType::Application, 4000 }); + frame4.displayed.PushBack({ FrameType::Application, 4000 }); auto results = ComputeMetricsForPresent(qpc, frame3, &frame4, state); Assert::IsTrue(results[0].metrics.msAnimationError.has_value()); @@ -6328,13 +6328,13 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.presentStartTime = 55454457377; frame1.timeInPresent = 2411; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 55454512384 }); + frame1.displayed.PushBack({ FrameType::Application, 55454512384 }); FrameData frame2{}; frame2.presentStartTime = 55454612236; frame2.timeInPresent = 3056; frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 55454615330 }); + frame2.displayed.PushBack({ FrameType::Application, 55454615330 }); ComputeMetricsForPresent(qpc, frame1, nullptr, state); auto results = ComputeMetricsForPresent(qpc, frame1, &frame2, state); @@ -6354,23 +6354,23 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.presentStartTime = 1000; frame1.timeInPresent = 100; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 2000 }); + frame1.displayed.PushBack({ FrameType::Application, 2000 }); FrameData frame2{}; frame2.presentStartTime = 2000; frame2.timeInPresent = 100; frame2.appSimStartTime = 100; // app instrumentation appears frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 2050 }); + frame2.displayed.PushBack({ FrameType::Application, 2050 }); FrameData dummyNext{}; dummyNext.finalState = PresentResult::Presented; - dummyNext.displayed.push_back({ FrameType::Application, 3000 }); + dummyNext.displayed.PushBack({ FrameType::Application, 3000 }); ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); FrameData frame3{}; frame3.finalState = PresentResult::Presented; - frame3.displayed.push_back({ FrameType::Application, 4000 }); + frame3.displayed.PushBack({ FrameType::Application, 4000 }); auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); Assert::IsFalse(results[0].metrics.msAnimationError.has_value(), @@ -6396,11 +6396,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) present.timeInPresent = 100; present.appSimStartTime = 150; present.finalState = PresentResult::Presented; - present.displayed.push_back({ FrameType::Repeated, 2000 }); // Not Application! + present.displayed.PushBack({ FrameType::Repeated, 2000 }); // Not Application! FrameData nextPresent{}; nextPresent.finalState = PresentResult::Presented; - nextPresent.displayed.push_back({ FrameType::Application, 3000 }); + nextPresent.displayed.PushBack({ FrameType::Application, 3000 }); auto results = ComputeMetricsForPresent(qpc, present, &nextPresent, state); @@ -6425,11 +6425,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) present.appSimStartTime = 0; // no instrumentation present.pclSimStartTime = 0; present.finalState = PresentResult::Presented; - present.displayed.push_back({ FrameType::Application, 2000 }); + present.displayed.PushBack({ FrameType::Application, 2000 }); FrameData nextPresent{}; nextPresent.finalState = PresentResult::Presented; - nextPresent.displayed.push_back({ FrameType::Application, 3000 }); + nextPresent.displayed.PushBack({ FrameType::Application, 3000 }); auto results = ComputeMetricsForPresent(qpc, present, &nextPresent, state); @@ -6450,23 +6450,23 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.timeInPresent = 100; frame1.appSimStartTime = 100; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 1100 }); + frame1.displayed.PushBack({ FrameType::Application, 1100 }); FrameData frame2{}; frame2.presentStartTime = 2000; frame2.timeInPresent = 100; frame2.appSimStartTime = 150; // sim elapsed = 50 frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 1050 }); // screen time backward! + frame2.displayed.PushBack({ FrameType::Application, 1050 }); // screen time backward! FrameData dummyNext{}; dummyNext.finalState = PresentResult::Presented; - dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + dummyNext.displayed.PushBack({ FrameType::Application, 2000 }); ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); FrameData frame3{}; frame3.finalState = PresentResult::Presented; - frame3.displayed.push_back({ FrameType::Application, 3000 }); + frame3.displayed.PushBack({ FrameType::Application, 3000 }); auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); Assert::IsFalse(results[0].metrics.msAnimationError.has_value(), @@ -6486,23 +6486,23 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.timeInPresent = 100; frame1.appSimStartTime = 100; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 1000 }); + frame1.displayed.PushBack({ FrameType::Application, 1000 }); FrameData frame2{}; frame2.presentStartTime = 2000; frame2.timeInPresent = 100; frame2.appSimStartTime = 500; // sim elapsed = 400 ticks frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Application, 1010 }); // display elapsed = 10 ticks + frame2.displayed.PushBack({ FrameType::Application, 1010 }); // display elapsed = 10 ticks FrameData dummyNext{}; dummyNext.finalState = PresentResult::Presented; - dummyNext.displayed.push_back({ FrameType::Application, 2000 }); + dummyNext.displayed.PushBack({ FrameType::Application, 2000 }); ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); FrameData frame3{}; frame3.finalState = PresentResult::Presented; - frame3.displayed.push_back({ FrameType::Application, 3000 }); + frame3.displayed.PushBack({ FrameType::Application, 3000 }); auto results = ComputeMetricsForPresent(qpc, frame2, &frame3, state); Assert::IsTrue(results[0].metrics.msAnimationError.has_value()); @@ -6528,11 +6528,11 @@ TEST_CLASS(ComputeMetricsForPresentTests) present.timeInPresent = 100; present.appSimStartTime = 150; present.finalState = PresentResult::Presented; - present.displayed.push_back({ FrameType::Repeated, 2000 }); + present.displayed.PushBack({ FrameType::Repeated, 2000 }); FrameData nextPresent{}; nextPresent.finalState = PresentResult::Presented; - nextPresent.displayed.push_back({ FrameType::Application, 3000 }); + nextPresent.displayed.PushBack({ FrameType::Application, 3000 }); auto results = ComputeMetricsForPresent(qpc, present, &nextPresent, state); @@ -6556,7 +6556,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame1.timeInPresent = 100; frame1.appSimStartTime = 100; frame1.finalState = PresentResult::Presented; - frame1.displayed.push_back({ FrameType::Application, 1000 }); + frame1.displayed.PushBack({ FrameType::Application, 1000 }); // Frame with multiple display instances FrameData frame2{}; @@ -6564,13 +6564,13 @@ TEST_CLASS(ComputeMetricsForPresentTests) frame2.timeInPresent = 100; frame2.appSimStartTime = 150; frame2.finalState = PresentResult::Presented; - frame2.displayed.push_back({ FrameType::Repeated, 2000 }); // [0] - frame2.displayed.push_back({ FrameType::Application, 2050 }); // [1] - appIndex - frame2.displayed.push_back({ FrameType::Repeated, 2100 }); // [2] + frame2.displayed.PushBack({ FrameType::Repeated, 2000 }); // [0] + frame2.displayed.PushBack({ FrameType::Application, 2050 }); // [1] - appIndex + frame2.displayed.PushBack({ FrameType::Repeated, 2100 }); // [2] FrameData dummyNext{}; dummyNext.finalState = PresentResult::Presented; - dummyNext.displayed.push_back({ FrameType::Application, 3000 }); + dummyNext.displayed.PushBack({ FrameType::Application, 3000 }); ComputeMetricsForPresent(qpc, frame1, &dummyNext, state); // Process frame2 without next (should process [0] and [1]) @@ -6634,7 +6634,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p1.appSimStartTime = 475'000; p1.pclSimStartTime = 0; p1.finalState = PresentResult::Presented; - p1.displayed.push_back({ FrameType::Application, 1'000'000 }); + p1.displayed.PushBack({ FrameType::Application, 1'000'000 }); // Arrival of P1 -> Case 2 (no nextDisplayed yet), becomes pending auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, state); @@ -6657,7 +6657,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p2.appSimStartTime = 575'000; p2.pclSimStartTime = 0; p2.finalState = PresentResult::Presented; - p2.displayed.push_back({ FrameType::Application, 1'100'000 }); + p2.displayed.PushBack({ FrameType::Application, 1'100'000 }); // Arrival of P2: // 1) Flush pending P1 using P2 as nextDisplayed -> Case 3, finalize P1. @@ -6697,7 +6697,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p3.appSimStartTime = 675'000; p3.pclSimStartTime = 0; p3.finalState = PresentResult::Presented; - p3.displayed.push_back({ FrameType::Application, 1'200'000 }); + p3.displayed.PushBack({ FrameType::Application, 1'200'000 }); // Arrival of P3: // 1) Flush pending P2 using P3 as nextDisplayed -> Case 3, finalize P2. @@ -6796,7 +6796,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p1.appSimStartTime = 475'000; // APC-provided sim start (QPC ticks) p1.pclSimStartTime = 0; p1.finalState = PresentResult::Presented; - p1.displayed.push_back({ FrameType::Application, 1'000'000 }); // screen time + p1.displayed.PushBack({ FrameType::Application, 1'000'000 }); // screen time // P1 arrives -> Case 2 (no nextDisplayed yet), becomes pending auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, state); @@ -6854,7 +6854,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p3.appSimStartTime = 675'000; // another +100'000 ticks in sim space p3.pclSimStartTime = 0; p3.finalState = PresentResult::Presented; - p3.displayed.push_back({ FrameType::Application, 1'100'000 }); // next screen time + p3.displayed.PushBack({ FrameType::Application, 1'100'000 }); // next screen time // P3 arrives: // 1) Flush pending P1 using P3 as nextDisplayed -> Case 3, finalize P1. @@ -6923,7 +6923,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p1.inputTime = 0; p1.appSimStartTime = 450'000; p1.finalState = PresentResult::Presented; - p1.displayed.push_back({ FrameType::Application, 1'000'000 }); + p1.displayed.PushBack({ FrameType::Application, 1'000'000 }); // P2: next displayed frame FrameData p2{}; @@ -6932,7 +6932,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p2.mouseClickTime = 0; p2.inputTime = 0; p2.finalState = PresentResult::Presented; - p2.displayed.push_back({ FrameType::Application, 1'100'000 }); + p2.displayed.PushBack({ FrameType::Application, 1'100'000 }); // P1 arrives (pending) auto p1_pending = ComputeMetricsForPresent(qpc, p1, nullptr, state); @@ -6992,14 +6992,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) p2.mouseClickTime = 0; p2.inputTime = 0; p2.finalState = PresentResult::Presented; - p2.displayed.push_back({ FrameType::Application, 1'000'000 }); + p2.displayed.PushBack({ FrameType::Application, 1'000'000 }); // P3: later frame to finalize P2 FrameData p3{}; p3.presentStartTime = 1'050'000; p3.timeInPresent = 50'000; p3.finalState = PresentResult::Presented; - p3.displayed.push_back({ FrameType::Application, 1'100'000 }); + p3.displayed.PushBack({ FrameType::Application, 1'100'000 }); // P1 arrives (dropped) auto p1_results = ComputeMetricsForPresent(qpc, p1, nullptr, state); @@ -7075,14 +7075,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) p3.inputTime = 0; p3.mouseClickTime = 0; p3.finalState = PresentResult::Presented; - p3.displayed.push_back({ FrameType::Application, 1'000'000 }); + p3.displayed.PushBack({ FrameType::Application, 1'000'000 }); // P4: later frame to finalize P3 FrameData p4{}; p4.presentStartTime = 1'050'000; p4.timeInPresent = 50'000; p4.finalState = PresentResult::Presented; - p4.displayed.push_back({ FrameType::Application, 1'100'000 }); + p4.displayed.PushBack({ FrameType::Application, 1'100'000 }); // P1 arrives (dropped) auto p1_results = ComputeMetricsForPresent(qpc, p1, nullptr, state); @@ -7150,14 +7150,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) p1.inputTime = 500'000; p1.mouseClickTime = 0; p1.finalState = PresentResult::Presented; - p1.displayed.push_back({ FrameType::Application, 1'000'000 }); + p1.displayed.PushBack({ FrameType::Application, 1'000'000 }); // P2: later frame to finalize P1 FrameData p2{}; p2.presentStartTime = 1'050'000; p2.timeInPresent = 50'000; p2.finalState = PresentResult::Presented; - p2.displayed.push_back({ FrameType::Application, 1'100'000 }); + p2.displayed.PushBack({ FrameType::Application, 1'100'000 }); // P0 arrives (dropped) auto p0_results = ComputeMetricsForPresent(qpc, p0, nullptr, state); @@ -7207,7 +7207,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p1.appSimStartTime = 475'000; p1.pclSimStartTime = 0; p1.finalState = PresentResult::Presented; - p1.displayed.push_back({ FrameType::Application, 1'000'000 }); + p1.displayed.PushBack({ FrameType::Application, 1'000'000 }); // P2: Frame with input (we assert on this) FrameData p2{}; @@ -7218,7 +7218,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p2.appInputSample.first = 500'000; // Using same value for simplicity p2.appInputSample.second = InputDeviceType::Mouse; p2.finalState = PresentResult::Presented; - p2.displayed.push_back({ FrameType::Application, 1'100'000 }); + p2.displayed.PushBack({ FrameType::Application, 1'100'000 }); // P3: Later frame to finalize P2 FrameData p3{}; @@ -7226,7 +7226,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p3.timeInPresent = 100'000; p3.appSimStartTime = 675'000; p3.finalState = PresentResult::Presented; - p3.displayed.push_back({ FrameType::Application, 1'200'000 }); + p3.displayed.PushBack({ FrameType::Application, 1'200'000 }); // P1 arrives (pending) auto p1_pending = ComputeMetricsForPresent(qpc, p1, nullptr, state); @@ -7358,7 +7358,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.pclSimStartTime = 20'000; // SIM0 p0.finalState = PresentResult::Discarded; - p0.displayed.clear(); // not displayed + p0.displayed.Clear(); // not displayed // P0 arrival -> Case 1 (not displayed), process immediately. auto p0_metrics_list = ComputeMetricsForPresent(qpc, p0, nullptr, state); @@ -7394,7 +7394,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p1.pclSimStartTime = 30'000; // SIM1 p1.finalState = PresentResult::Discarded; - p1.displayed.clear(); // not displayed + p1.displayed.Clear(); // not displayed auto p1_metrics_list = ComputeMetricsForPresent(qpc, p1, nullptr, state); Assert::AreEqual(size_t(1), p1_metrics_list.size(), @@ -7431,8 +7431,8 @@ TEST_CLASS(ComputeMetricsForPresentTests) p2.pclSimStartTime = 40'000; // SIM2 p2.finalState = PresentResult::Presented; - p2.displayed.clear(); - p2.displayed.push_back({ FrameType::Application, 50'000 }); // SCR2 + p2.displayed.Clear(); + p2.displayed.PushBack({ FrameType::Application, 50'000 }); // SCR2 // P2 arrival: Case 2 (displayed, no nextDisplayed), becomes pending. auto p2_phase1 = ComputeMetricsForPresent(qpc, p2, nullptr, state); @@ -7460,8 +7460,8 @@ TEST_CLASS(ComputeMetricsForPresentTests) p3.pclSimStartTime = 0; // no PCL for P3 itself p3.finalState = PresentResult::Presented; - p3.displayed.clear(); - p3.displayed.push_back({ FrameType::Application, 60'000 }); // some later screen time + p3.displayed.Clear(); + p3.displayed.PushBack({ FrameType::Application, 60'000 }); // some later screen time // P3 arrival: // 1) Flush pending P2 using P3 as nextDisplayed -> Case 3, finalize P2. @@ -7547,7 +7547,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p1.processId = PROCESS_ID; p1.swapChainAddress = SWAPCHAIN; p1.finalState = PresentResult::Presented; - p1.displayed.push_back({ FrameType::Application, 100'000 }); + p1.displayed.PushBack({ FrameType::Application, 100'000 }); auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, state); Assert::AreEqual(size_t(0), p1_phase1.size(), @@ -7564,7 +7564,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p2.processId = PROCESS_ID; p2.swapChainAddress = SWAPCHAIN; p2.finalState = PresentResult::Presented; - p2.displayed.push_back({ FrameType::Application, 120'000 }); + p2.displayed.PushBack({ FrameType::Application, 120'000 }); auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, state); Assert::AreEqual(size_t(1), p1_final.size(), @@ -7587,7 +7587,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p3.processId = PROCESS_ID; p3.swapChainAddress = SWAPCHAIN; p3.finalState = PresentResult::Presented; - p3.displayed.push_back({ FrameType::Application, 140'000 }); + p3.displayed.PushBack({ FrameType::Application, 140'000 }); auto p2_final = ComputeMetricsForPresent(qpc, p2, &p3, state); Assert::AreEqual(size_t(1), p2_final.size(), @@ -7628,8 +7628,8 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.pclInputPingTime = 10'000; p0.pclSimStartTime = 20'000; p0.finalState = PresentResult::Presented; - p0.displayed.push_back({ FrameType::Application, 50'000 }); - p0.displayed.push_back({ FrameType::Application, 60'000 }); + p0.displayed.PushBack({ FrameType::Application, 50'000 }); + p0.displayed.PushBack({ FrameType::Application, 60'000 }); auto p0_results = ComputeMetricsForPresent(qpc, p0, nullptr, state); Assert::AreEqual(size_t(1), p0_results.size(), @@ -7679,7 +7679,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.pclInputPingTime = 10'000; p0.pclSimStartTime = 20'000; p0.finalState = PresentResult::Presented; - p0.displayed.push_back({ FrameType::Application, 50'000 }); + p0.displayed.PushBack({ FrameType::Application, 50'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, state); Assert::AreEqual(size_t(0), p0_phase1.size(), @@ -7692,7 +7692,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p1.pclInputPingTime = 30'000; p1.pclSimStartTime = 40'000; p1.finalState = PresentResult::Presented; - p1.displayed.push_back({ FrameType::Application, 70'000 }); + p1.displayed.PushBack({ FrameType::Application, 70'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, state); Assert::AreEqual(size_t(1), p0_final.size(), @@ -7714,7 +7714,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) // -------------------------------------------------------------------- FrameData p2{}; p2.finalState = PresentResult::Presented; - p2.displayed.push_back({ FrameType::Application, 90'000 }); + p2.displayed.PushBack({ FrameType::Application, 90'000 }); auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, state); Assert::AreEqual(size_t(1), p1_final.size(), @@ -7855,7 +7855,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.pclInputPingTime = 10'000; p0.pclSimStartTime = 20'000; p0.finalState = PresentResult::Presented; - p0.displayed.push_back({ FrameType::Application, 50'000 }); + p0.displayed.PushBack({ FrameType::Application, 50'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, state); Assert::AreEqual(size_t(0), p0_phase1.size()); @@ -7864,7 +7864,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p1.pclInputPingTime = 0; p1.pclSimStartTime = 35'000; p1.finalState = PresentResult::Presented; - p1.displayed.push_back({ FrameType::Application, 70'000 }); + p1.displayed.PushBack({ FrameType::Application, 70'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, state); Assert::AreEqual(size_t(1), p0_final.size()); @@ -7877,7 +7877,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p2{}; p2.finalState = PresentResult::Presented; - p2.displayed.push_back({ FrameType::Application, 90'000 }); + p2.displayed.PushBack({ FrameType::Application, 90'000 }); auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, state); Assert::AreEqual(size_t(1), p1_final.size()); @@ -7915,7 +7915,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.pclInputPingTime = 10'000; p0.pclSimStartTime = 30'000; p0.finalState = PresentResult::Presented; - p0.displayed.push_back({ FrameType::Application, 70'000 }); + p0.displayed.PushBack({ FrameType::Application, 70'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, state); Assert::AreEqual(size_t(0), p0_phase1.size()); @@ -7924,7 +7924,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p1.pclInputPingTime = 0; p1.pclSimStartTime = 0; p1.finalState = PresentResult::Presented; - p1.displayed.push_back({ FrameType::Application, 90'000 }); + p1.displayed.PushBack({ FrameType::Application, 90'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, state); Assert::AreEqual(size_t(1), p0_final.size()); @@ -7941,7 +7941,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p2{}; p2.finalState = PresentResult::Presented; - p2.displayed.push_back({ FrameType::Application, 110'000 }); + p2.displayed.PushBack({ FrameType::Application, 110'000 }); auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, state); Assert::AreEqual(size_t(1), p1_final.size()); @@ -8047,14 +8047,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.pclInputPingTime = 100'000; p0.pclSimStartTime = 120'000; p0.finalState = PresentResult::Presented; - p0.displayed.push_back({ FrameType::Application, 150'000 }); + p0.displayed.PushBack({ FrameType::Application, 150'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, state); Assert::AreEqual(size_t(0), p0_phase1.size()); FrameData p1{}; p1.finalState = PresentResult::Presented; - p1.displayed.push_back({ FrameType::Application, 180'000 }); + p1.displayed.PushBack({ FrameType::Application, 180'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, state); Assert::AreEqual(size_t(1), p0_final.size()); @@ -8159,8 +8159,8 @@ TEST_CLASS(ComputeMetricsForPresentTests) // Mark as displayed Application frame p0.finalState = PresentResult::Presented; - p0.displayed.clear(); - p0.displayed.push_back({ FrameType::Application, 50'000 }); // screenTime = 50'000 + p0.displayed.Clear(); + p0.displayed.PushBack({ FrameType::Application, 50'000 }); // screenTime = 50'000 // First call: P0 arrives, becomes pending (no metrics yet). auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); @@ -8179,8 +8179,8 @@ TEST_CLASS(ComputeMetricsForPresentTests) p1.readyTime = 0; p1.finalState = PresentResult::Presented; - p1.displayed.clear(); - p1.displayed.push_back({ FrameType::Application, 60'000 }); // later display, not important + p1.displayed.Clear(); + p1.displayed.PushBack({ FrameType::Application, 60'000 }); // later display, not important // Second call: P1 arrives, finalize P0 using P1 as nextDisplayed (Case 3). auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); @@ -8287,8 +8287,8 @@ TEST_CLASS(ComputeMetricsForPresentTests) // Mark as displayed Application frame with a single screen time. p0.finalState = PresentResult::Presented; - p0.displayed.clear(); - p0.displayed.push_back({ FrameType::Application, 30'000 }); // screenTime = 30'000 + p0.displayed.Clear(); + p0.displayed.PushBack({ FrameType::Application, 30'000 }); // screenTime = 30'000 // First call: P0 arrives, becomes pending. auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); @@ -8307,8 +8307,8 @@ TEST_CLASS(ComputeMetricsForPresentTests) p1.readyTime = 0; p1.finalState = PresentResult::Presented; - p1.displayed.clear(); - p1.displayed.push_back({ FrameType::Application, 40'000 }); // later display + p1.displayed.Clear(); + p1.displayed.PushBack({ FrameType::Application, 40'000 }); // later display // Second call: finalize P0 with nextDisplayed=P1 auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); @@ -8394,8 +8394,8 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.appSimStartTime = 70'000; p0.gpuStartTime = 90'000; p0.finalState = PresentResult::Presented; - p0.displayed.clear(); - p0.displayed.push_back({ FrameType::Application, 120'000 }); + p0.displayed.Clear(); + p0.displayed.PushBack({ FrameType::Application, 120'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); Assert::AreEqual(size_t(0), p0_phase1.size(), @@ -8406,8 +8406,8 @@ TEST_CLASS(ComputeMetricsForPresentTests) p1.processId = PROCESS_ID; p1.swapChainAddress = SWAPCHAIN; p1.finalState = PresentResult::Presented; - p1.displayed.clear(); - p1.displayed.push_back({ FrameType::Application, 150'000 }); + p1.displayed.Clear(); + p1.displayed.PushBack({ FrameType::Application, 150'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); Assert::AreEqual(size_t(1), p0_final.size(), @@ -8464,8 +8464,8 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.swapChainAddress = SWAPCHAIN; p0.gpuStartTime = 80'000; p0.finalState = PresentResult::Presented; - p0.displayed.clear(); - p0.displayed.push_back({ FrameType::Application, 100'000 }); + p0.displayed.Clear(); + p0.displayed.PushBack({ FrameType::Application, 100'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); Assert::AreEqual(size_t(0), p0_phase1.size()); @@ -8474,7 +8474,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p1.processId = PROCESS_ID; p1.swapChainAddress = SWAPCHAIN; p1.finalState = PresentResult::Presented; - p1.displayed.push_back({ FrameType::Application, 120'000 }); + p1.displayed.PushBack({ FrameType::Application, 120'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); Assert::AreEqual(size_t(1), p0_final.size()); @@ -8581,8 +8581,8 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.pclSimStartTime = 72'000; p0.gpuStartTime = 90'000; p0.finalState = PresentResult::Presented; - p0.displayed.clear(); - p0.displayed.push_back({ FrameType::Repeated, 120'000 }); + p0.displayed.Clear(); + p0.displayed.PushBack({ FrameType::Repeated, 120'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); Assert::AreEqual(size_t(0), p0_phase1.size()); @@ -8591,7 +8591,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) p1.processId = PROCESS_ID; p1.swapChainAddress = SWAPCHAIN; p1.finalState = PresentResult::Presented; - p1.displayed.push_back({ FrameType::Application, 150'000 }); + p1.displayed.PushBack({ FrameType::Application, 150'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); Assert::AreEqual(size_t(1), p0_final.size()); @@ -8627,14 +8627,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.readyTime = 80'000; p0.appSleepEndTime = 50'000; p0.finalState = PresentResult::Presented; - p0.displayed.push_back({ FrameType::Application, 100'000 }); + p0.displayed.PushBack({ FrameType::Application, 100'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); Assert::AreEqual(size_t(0), p0_phase1.size()); FrameData p1{}; p1.finalState = PresentResult::Presented; - p1.displayed.push_back({ FrameType::Application, 130'000 }); + p1.displayed.PushBack({ FrameType::Application, 130'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); Assert::AreEqual(size_t(1), p0_final.size()); @@ -8684,14 +8684,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.readyTime = 30'000; p0.appSleepEndTime = 0; p0.finalState = PresentResult::Presented; - p0.displayed.push_back({ FrameType::Application, 60'000 }); + p0.displayed.PushBack({ FrameType::Application, 60'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); Assert::AreEqual(size_t(0), p0_phase1.size()); FrameData p1{}; p1.finalState = PresentResult::Presented; - p1.displayed.push_back({ FrameType::Application, 90'000 }); + p1.displayed.PushBack({ FrameType::Application, 90'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); Assert::AreEqual(size_t(1), p0_final.size()); @@ -8735,14 +8735,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.appRenderSubmitStartTime = 12'000; p0.readyTime = 32'000; p0.finalState = PresentResult::Presented; - p0.displayed.push_back({ FrameType::Application, 70'000 }); + p0.displayed.PushBack({ FrameType::Application, 70'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); Assert::AreEqual(size_t(0), p0_phase1.size()); FrameData p1{}; p1.finalState = PresentResult::Presented; - p1.displayed.push_back({ FrameType::Application, 90'000 }); + p1.displayed.PushBack({ FrameType::Application, 90'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); Assert::AreEqual(size_t(1), p0_final.size()); @@ -8783,14 +8783,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) p0.readyTime = 30'000; p0.appSleepEndTime = 5'000; p0.finalState = PresentResult::Presented; - p0.displayed.push_back({ FrameType::Repeated, 60'000 }); + p0.displayed.PushBack({ FrameType::Repeated, 60'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); Assert::AreEqual(size_t(0), p0_phase1.size()); FrameData p1{}; p1.finalState = PresentResult::Presented; - p1.displayed.push_back({ FrameType::Application, 90'000 }); + p1.displayed.PushBack({ FrameType::Application, 90'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); Assert::AreEqual(size_t(1), p0_final.size()); @@ -8871,7 +8871,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) // P1: Displayed Application frame without its own AppInputSample. FrameData p1{}; p1.finalState = PresentResult::Presented; - p1.displayed.push_back({ FrameType::Application, 70'000 }); + p1.displayed.PushBack({ FrameType::Application, 70'000 }); auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, chain); Assert::AreEqual(size_t(0), p1_phase1.size()); @@ -8879,7 +8879,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) // P2: Simple next displayed frame to flush P1. FrameData p2{}; p2.finalState = PresentResult::Presented; - p2.displayed.push_back({ FrameType::Application, 90'000 }); + p2.displayed.PushBack({ FrameType::Application, 90'000 }); auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, chain); Assert::AreEqual(size_t(1), p1_final.size()); @@ -8932,14 +8932,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p1{}; p1.appInputSample = { directInputTime, InputDeviceType::Mouse }; p1.finalState = PresentResult::Presented; - p1.displayed.push_back({ FrameType::Application, 60'000 }); + p1.displayed.PushBack({ FrameType::Application, 60'000 }); auto p1_phase1 = ComputeMetricsForPresent(qpc, p1, nullptr, chain); Assert::AreEqual(size_t(0), p1_phase1.size()); FrameData p2{}; p2.finalState = PresentResult::Presented; - p2.displayed.push_back({ FrameType::Application, 80'000 }); + p2.displayed.PushBack({ FrameType::Application, 80'000 }); auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, chain); Assert::AreEqual(size_t(1), p1_final.size()); @@ -8981,14 +8981,14 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p0{}; p0.appInputSample = { ignoredInputTime, InputDeviceType::Mouse }; p0.finalState = PresentResult::Presented; - p0.displayed.push_back({ FrameType::Repeated, 50'000 }); + p0.displayed.PushBack({ FrameType::Repeated, 50'000 }); auto p0_phase1 = ComputeMetricsForPresent(qpc, p0, nullptr, chain); Assert::AreEqual(size_t(0), p0_phase1.size()); FrameData p1{}; p1.finalState = PresentResult::Presented; - p1.displayed.push_back({ FrameType::Application, 80'000 }); + p1.displayed.PushBack({ FrameType::Application, 80'000 }); auto p0_final = ComputeMetricsForPresent(qpc, p0, &p1, chain); Assert::AreEqual(size_t(1), p0_final.size()); @@ -9000,7 +9000,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) FrameData p2{}; p2.finalState = PresentResult::Presented; - p2.displayed.push_back({ FrameType::Application, 100'000 }); + p2.displayed.PushBack({ FrameType::Application, 100'000 }); auto p1_final = ComputeMetricsForPresent(qpc, p1, &p2, chain); Assert::AreEqual(size_t(1), p1_final.size()); From c4e5a013444b3d47918cc1bcea49dc14e1c625cb Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 14 Jan 2026 05:03:55 +0900 Subject: [PATCH 108/205] use util FrameData in DataStores --- .../Interprocess/Interprocess.vcxproj | 1 - .../Interprocess/Interprocess.vcxproj.filters | 3 - .../Interprocess/source/DataStores.h | 6 +- .../source/FrameDataPlaceholder.h | 56 ---------------- .../PresentMonService/FrameBroadcaster.h | 67 +------------------ 5 files changed, 7 insertions(+), 126 deletions(-) delete mode 100644 IntelPresentMon/Interprocess/source/FrameDataPlaceholder.h diff --git a/IntelPresentMon/Interprocess/Interprocess.vcxproj b/IntelPresentMon/Interprocess/Interprocess.vcxproj index b3804b30..4d2ef325 100644 --- a/IntelPresentMon/Interprocess/Interprocess.vcxproj +++ b/IntelPresentMon/Interprocess/Interprocess.vcxproj @@ -32,7 +32,6 @@ - diff --git a/IntelPresentMon/Interprocess/Interprocess.vcxproj.filters b/IntelPresentMon/Interprocess/Interprocess.vcxproj.filters index b40c0548..667fb90b 100644 --- a/IntelPresentMon/Interprocess/Interprocess.vcxproj.filters +++ b/IntelPresentMon/Interprocess/Interprocess.vcxproj.filters @@ -164,9 +164,6 @@ Header Files - - Header Files - Header Files diff --git a/IntelPresentMon/Interprocess/source/DataStores.h b/IntelPresentMon/Interprocess/source/DataStores.h index 21b3bdce..a397d4b0 100644 --- a/IntelPresentMon/Interprocess/source/DataStores.h +++ b/IntelPresentMon/Interprocess/source/DataStores.h @@ -1,11 +1,11 @@ -#pragma once +#pragma once #include "SharedMemoryTypes.h" #include "ShmRing.h" #include "TelemetryMap.h" #include "../../CommonUtilities/Exception.h" #include "../../CommonUtilities/log/Log.h" +#include "../../CommonUtilities/mc/MetricsTypes.h" #include "../../PresentMonAPI2/PresentMonAPI.h" -#include "FrameDataPlaceholder.h" #include #include @@ -15,6 +15,8 @@ namespace pmon::ipc { + using FrameData = util::metrics::FrameData; + class MetricCapabilities; namespace intro { diff --git a/IntelPresentMon/Interprocess/source/FrameDataPlaceholder.h b/IntelPresentMon/Interprocess/source/FrameDataPlaceholder.h deleted file mode 100644 index 6c4bc802..00000000 --- a/IntelPresentMon/Interprocess/source/FrameDataPlaceholder.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once -#include "../../../PresentData/PresentMonTraceConsumer.hpp" -#include -#include - -namespace pmon::ipc -{ - // temporary; replace with structure dictated by new PresentMon calculation library - struct FrameData { - // Timing Data - uint64_t presentStartTime = 0; - uint64_t readyTime = 0; - uint64_t timeInPresent = 0; - uint64_t gpuStartTime = 0; - uint64_t gpuDuration = 0; - uint64_t gpuVideoDuration = 0; - - // Used to track the application work when Intel XeSS-FG is enabled - uint64_t appPropagatedPresentStartTime = 0; - uint64_t appPropagatedTimeInPresent = 0; - uint64_t appPropagatedGPUStartTime = 0; - uint64_t appPropagatedReadyTime = 0; - uint64_t appPropagatedGPUDuration = 0; - uint64_t appPropagatedGPUVideoDuration = 0; - - // Instrumented Timestamps - uint64_t appSimStartTime = 0; - uint64_t appSleepStartTime = 0; - uint64_t appSleepEndTime = 0; - uint64_t appRenderSubmitStartTime = 0; - uint64_t appRenderSubmitEndTime = 0; - uint64_t appPresentStartTime = 0; - uint64_t appPresentEndTime = 0; - std::pair appInputSample; // time, input type - - // Input Device Timestamps - uint64_t inputTime = 0; // All input devices - uint64_t mouseClickTime = 0; // Mouse click specific - - boost::container::static_vector, 10> displayed; - - // PC Latency data - uint64_t pclSimStartTime = 0; - uint64_t pclInputPingTime = 0; - uint64_t flipDelay = 0; - uint32_t flipToken = 0; - - // Metadata - PresentResult finalState; - uint32_t processId = 0; - uint32_t threadId = 0; - uint64_t swapChainAddress = 0; - uint32_t frameId = 0; - uint32_t appFrameId = 0; - }; -} diff --git a/IntelPresentMon/PresentMonService/FrameBroadcaster.h b/IntelPresentMon/PresentMonService/FrameBroadcaster.h index d4a212c0..623e3877 100644 --- a/IntelPresentMon/PresentMonService/FrameBroadcaster.h +++ b/IntelPresentMon/PresentMonService/FrameBroadcaster.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "../Interprocess/source/Interprocess.h" #include "../../PresentData/PresentMonTraceConsumer.hpp" #include "../CommonUtilities/win/Utilities.h" @@ -13,67 +13,6 @@ namespace pmon::svc using ipc::FrameData; using namespace std::literals; - namespace - { - FrameData FrameDataFromPresentEvent(const PresentEvent& present) - { - using DisplayedEntry = std::pair; - using DisplayedVector = boost::container::static_vector; - - return FrameData{ - // Timing data - .presentStartTime = present.PresentStartTime, - .readyTime = present.ReadyTime, - .timeInPresent = present.TimeInPresent, - .gpuStartTime = present.GPUStartTime, - .gpuDuration = present.GPUDuration, - .gpuVideoDuration = present.GPUVideoDuration, - - // XeSS-FG propagated timing - .appPropagatedPresentStartTime = present.AppPropagatedPresentStartTime, - .appPropagatedTimeInPresent = present.AppPropagatedTimeInPresent, - .appPropagatedGPUStartTime = present.AppPropagatedGPUStartTime, - .appPropagatedReadyTime = present.AppPropagatedReadyTime, - .appPropagatedGPUDuration = present.AppPropagatedGPUDuration, - .appPropagatedGPUVideoDuration = present.AppPropagatedGPUVideoDuration, - - // Instrumented timestamps - .appSimStartTime = present.AppSimStartTime, - .appSleepStartTime = present.AppSleepStartTime, - .appSleepEndTime = present.AppSleepEndTime, - .appRenderSubmitStartTime = present.AppRenderSubmitStartTime, - .appRenderSubmitEndTime = present.AppRenderSubmitEndTime, - .appPresentStartTime = present.AppPresentStartTime, - .appPresentEndTime = present.AppPresentEndTime, - .appInputSample = present.AppInputSample, - - // Input device timestamps - .inputTime = present.InputTime, - .mouseClickTime = present.MouseClickTime, - - // Displayed history (no explicit bounds check; assumed to fit) - .displayed = DisplayedVector{ - present.Displayed.begin(), - present.Displayed.end() - }, - - // PC Latency data - .pclSimStartTime = present.PclSimStartTime, - .pclInputPingTime = present.PclInputPingTime, - .flipDelay = present.FlipDelay, - .flipToken = present.FlipToken, - - // Metadata - .finalState = present.FinalState, - .processId = present.ProcessId, - .threadId = present.ThreadId, - .swapChainAddress = present.SwapChainAddress, - .frameId = present.FrameId, - .appFrameId = present.AppFrameId, - }; - } - } - class FrameBroadcaster { public: @@ -121,7 +60,7 @@ namespace pmon::svc { std::lock_guard lk{ mtx_ }; if (auto pSegment = comms_.GetFrameDataSegment(present.ProcessId)) { - pSegment->GetStore().frameData.Push(FrameDataFromPresentEvent(present)); + pSegment->GetStore().frameData.Push(FrameData::CopyFrameData(present)); } } void HandleTargetProcessEvent(const ProcessEvent& targetProcessEvent) @@ -150,4 +89,4 @@ namespace pmon::svc ipc::ServiceComms& comms_; mutable std::mutex mtx_; }; -} \ No newline at end of file +} From e6b6c0c8519a035f14b4eb34dafed540d93a3356 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 14 Jan 2026 11:54:06 +0900 Subject: [PATCH 109/205] paced frame event tests --- IntelPresentMon/PresentMonAPI2Tests/Folders.h | 10 +- .../PresentMonAPI2Tests/ModuleInit.cpp | 6 +- .../PresentMonAPI2Tests/PacedFrameTests.cpp | 563 ++++++++++++++++++ .../PresentMonAPI2Tests.vcxproj | 3 +- IntelPresentMon/SampleClient/CliOptions.h | 2 + .../SampleClient/PacedFramePlayback.cpp | 332 +++++++++++ .../SampleClient/PacedFramePlayback.h | 5 + IntelPresentMon/SampleClient/SampleClient.cpp | 7 +- .../SampleClient/SampleClient.vcxproj | 2 + 9 files changed, 923 insertions(+), 7 deletions(-) create mode 100644 IntelPresentMon/PresentMonAPI2Tests/PacedFrameTests.cpp create mode 100644 IntelPresentMon/SampleClient/PacedFramePlayback.cpp create mode 100644 IntelPresentMon/SampleClient/PacedFramePlayback.h diff --git a/IntelPresentMon/PresentMonAPI2Tests/Folders.h b/IntelPresentMon/PresentMonAPI2Tests/Folders.h index 3d513943..00b97c6f 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/Folders.h +++ b/IntelPresentMon/PresentMonAPI2Tests/Folders.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once namespace MultiClientTests { @@ -17,6 +17,12 @@ namespace PacedPolling static constexpr const char* outFolder_ = "TestOutput\\PacedPolling"; } +namespace PacedFrame +{ + static constexpr const char* logFolder_ = "TestLogs\\PacedFrame"; + static constexpr const char* outFolder_ = "TestOutput\\PacedFrame"; +} + namespace InterimBroadcasterTests { static constexpr const char* logFolder_ = "TestLogs\\InterimBroadcaster"; @@ -27,4 +33,4 @@ namespace IpcComponentTests { static constexpr const char* logFolder_ = "TestLogs\\IpcComponent"; static constexpr const char* outFolder_ = "TestOutput\\IpcComponent"; -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonAPI2Tests/ModuleInit.cpp b/IntelPresentMon/PresentMonAPI2Tests/ModuleInit.cpp index 0cbf1e3d..c90a09f4 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/ModuleInit.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/ModuleInit.cpp @@ -1,4 +1,4 @@ -#include "../CommonUtilities/win/WinAPI.h" +#include "../CommonUtilities/win/WinAPI.h" #include "CppUnitTest.h" #include #include "Folders.h" @@ -34,6 +34,8 @@ TEST_MODULE_INITIALIZE(Api2TestModuleInit) WipeAndRecreate(EtlLoggerTests::outFolder_); WipeAndRecreate(PacedPolling::logFolder_); WipeAndRecreate(PacedPolling::outFolder_); + WipeAndRecreate(PacedFrame::logFolder_); + WipeAndRecreate(PacedFrame::outFolder_); WipeAndRecreate(InterimBroadcasterTests::logFolder_); WipeAndRecreate(InterimBroadcasterTests::outFolder_); -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonAPI2Tests/PacedFrameTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/PacedFrameTests.cpp new file mode 100644 index 00000000..2b13de63 --- /dev/null +++ b/IntelPresentMon/PresentMonAPI2Tests/PacedFrameTests.cpp @@ -0,0 +1,563 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#include "../CommonUtilities/win/WinAPI.h" +#include "../CommonUtilities/str/String.h" +#include "CppUnitTest.h" +#include "TestProcess.h" +#include "Folders.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +namespace fs = std::filesystem; +using namespace std::literals; +using namespace pmon; + +namespace PacedFrame +{ + class TestFixture : public CommonTestFixture + { + public: + const CommonProcessArgs& GetCommonArgs() const override + { + static CommonProcessArgs args{ + .ctrlPipe = R"(\\.\pipe\pm-paced-frame-test-ctrl)", + .shmNamePrefix = "pm_paced_frame_test_intro", + .logLevel = "debug", + .logFolder = logFolder_, + .sampleClientMode = "PacedFramePlayback", + }; + return args; + } + }; + + enum class ColumnIndex : size_t + { + Application = 0, + ProcessID = 1, + SwapChainAddress = 2, + PresentRuntime = 3, + SyncInterval = 4, + PresentFlags = 5, + AllowsTearing = 6, + PresentMode = 7, + FrameType = 8, + CPUStartTime = 9, + MsBetweenSimulationStart = 10, + MsBetweenPresents = 11, + MsBetweenDisplayChange = 12, + MsInPresentAPI = 13, + MsRenderPresentLatency = 14, + MsUntilDisplayed = 15, + MsPCLatency = 16, + MsBetweenAppStart = 17, + MsCPUBusy = 18, + MsCPUWait = 19, + MsGPULatency = 20, + MsGPUTime = 21, + MsGPUBusy = 22, + MsGPUWait = 23, + MsVideoBusy = 24, + MsAnimationError = 25, + AnimationTime = 26, + MsFlipDelay = 27, + MsAllInputToPhotonLatency = 28, + MsClickToPhotonLatency = 29, + MsInstrumentedLatency = 30, + }; + + const std::array kFrameCsvHeader{ + "Application", + "ProcessID", + "SwapChainAddress", + "PresentRuntime", + "SyncInterval", + "PresentFlags", + "AllowsTearing", + "PresentMode", + "FrameType", + "CPUStartTime", + "MsBetweenSimulationStart", + "MsBetweenPresents", + "MsBetweenDisplayChange", + "MsInPresentAPI", + "MsRenderPresentLatency", + "MsUntilDisplayed", + "MsPCLatency", + "MsBetweenAppStart", + "MsCPUBusy", + "MsCPUWait", + "MsGPULatency", + "MsGPUTime", + "MsGPUBusy", + "MsGPUWait", + "MsVideoBusy", + "MsAnimationError", + "AnimationTime", + "MsFlipDelay", + "MsAllInputToPhotonLatency", + "MsClickToPhotonLatency", + "MsInstrumentedLatency", + }; + + struct FrameCsvRow + { + std::string application; + uint32_t processId = 0; + uint64_t swapChainAddress = 0; + std::string presentRuntime; + int32_t syncInterval = 0; + uint32_t presentFlags = 0; + uint32_t allowsTearing = 0; + std::string presentMode; + std::string frameType; + std::optional cpuStartTime; + std::optional msBetweenSimulationStart; + double msBetweenPresents = 0.0; + std::optional msBetweenDisplayChange; + double msInPresentApi = 0.0; + double msRenderPresentLatency = 0.0; + std::optional msUntilDisplayed; + std::optional msPcLatency; + double msBetweenAppStart = 0.0; + double msCpuBusy = 0.0; + double msCpuWait = 0.0; + double msGpuLatency = 0.0; + double msGpuTime = 0.0; + double msGpuBusy = 0.0; + double msGpuWait = 0.0; + double msVideoBusy = 0.0; + std::optional msAnimationError; + std::optional animationTime; + std::optional msFlipDelay; + std::optional msAllInputToPhotonLatency; + std::optional msClickToPhotonLatency; + std::optional msInstrumentedLatency; + }; + + std::wstring MakeFailMessage(size_t row, const char* column, const std::string& expected, + const std::string& actual) + { + return pmon::util::str::ToWide(std::format( + "Row {} column {} expected [{}] got [{}]", row, column, expected, actual)); + } + + std::wstring MakeFailMessage(size_t row, const char* column) + { + return pmon::util::str::ToWide(std::format("Row {} column {} mismatch", row, column)); + } + + void StripUtf8Bom(std::string& value) + { + const char bom[] = { char(0xEF), char(0xBB), char(0xBF), 0 }; + if (value.rfind(bom, 0) == 0) { + value.erase(0, 3); + } + } + + bool IsMissingToken(const std::string& value) + { + return value == "NA" || value == "NaN" || value == "nan"; + } + + uint64_t ParseUint64(const std::string& value, size_t row, const char* column) + { + try { + return std::stoull(value, nullptr, 0); + } + catch (...) { + Assert::Fail(MakeFailMessage(row, column).c_str()); + } + return 0; + } + + uint32_t ParseUint32(const std::string& value, size_t row, const char* column) + { + try { + return static_cast(std::stoul(value, nullptr, 0)); + } + catch (...) { + Assert::Fail(MakeFailMessage(row, column).c_str()); + } + return 0; + } + + int32_t ParseInt32(const std::string& value, size_t row, const char* column) + { + try { + return std::stoi(value, nullptr, 0); + } + catch (...) { + Assert::Fail(MakeFailMessage(row, column).c_str()); + } + return 0; + } + + double ParseDouble(const std::string& value, size_t row, const char* column) + { + try { + return std::stod(value); + } + catch (...) { + Assert::Fail(MakeFailMessage(row, column).c_str()); + } + return 0.0; + } + + std::optional ParseOptionalDouble(const std::string& value, size_t row, const char* column) + { + if (IsMissingToken(value)) { + return std::nullopt; + } + return ParseDouble(value, row, column); + } + + void ValidateHeader(const std::vector& header) + { + Assert::IsTrue(header.size() == kFrameCsvHeader.size(), L"Unexpected header column count"); + for (size_t i = 0; i < kFrameCsvHeader.size(); ++i) { + Assert::IsTrue(header[i] == kFrameCsvHeader[i], + MakeFailMessage(0, "Header", kFrameCsvHeader[i], header[i]).c_str()); + } + } + + FrameCsvRow ParseFrameRow(const std::vector& row, size_t rowIndex) + { + FrameCsvRow parsed; + parsed.application = row[static_cast(ColumnIndex::Application)]; + parsed.processId = ParseUint32(row[static_cast(ColumnIndex::ProcessID)], rowIndex, "ProcessID"); + parsed.swapChainAddress = ParseUint64(row[static_cast(ColumnIndex::SwapChainAddress)], rowIndex, "SwapChainAddress"); + parsed.presentRuntime = row[static_cast(ColumnIndex::PresentRuntime)]; + parsed.syncInterval = ParseInt32(row[static_cast(ColumnIndex::SyncInterval)], rowIndex, "SyncInterval"); + parsed.presentFlags = ParseUint32(row[static_cast(ColumnIndex::PresentFlags)], rowIndex, "PresentFlags"); + parsed.allowsTearing = ParseUint32(row[static_cast(ColumnIndex::AllowsTearing)], rowIndex, "AllowsTearing"); + parsed.presentMode = row[static_cast(ColumnIndex::PresentMode)]; + parsed.frameType = row[static_cast(ColumnIndex::FrameType)]; + parsed.cpuStartTime = ParseOptionalDouble( + row[static_cast(ColumnIndex::CPUStartTime)], rowIndex, "CPUStartTime"); + parsed.msBetweenSimulationStart = ParseOptionalDouble( + row[static_cast(ColumnIndex::MsBetweenSimulationStart)], rowIndex, "MsBetweenSimulationStart"); + parsed.msBetweenPresents = ParseDouble( + row[static_cast(ColumnIndex::MsBetweenPresents)], rowIndex, "MsBetweenPresents"); + parsed.msBetweenDisplayChange = ParseOptionalDouble( + row[static_cast(ColumnIndex::MsBetweenDisplayChange)], rowIndex, "MsBetweenDisplayChange"); + parsed.msInPresentApi = ParseDouble( + row[static_cast(ColumnIndex::MsInPresentAPI)], rowIndex, "MsInPresentAPI"); + parsed.msRenderPresentLatency = ParseDouble( + row[static_cast(ColumnIndex::MsRenderPresentLatency)], rowIndex, "MsRenderPresentLatency"); + parsed.msUntilDisplayed = ParseOptionalDouble( + row[static_cast(ColumnIndex::MsUntilDisplayed)], rowIndex, "MsUntilDisplayed"); + parsed.msPcLatency = ParseOptionalDouble( + row[static_cast(ColumnIndex::MsPCLatency)], rowIndex, "MsPCLatency"); + parsed.msBetweenAppStart = ParseDouble( + row[static_cast(ColumnIndex::MsBetweenAppStart)], rowIndex, "MsBetweenAppStart"); + parsed.msCpuBusy = ParseDouble( + row[static_cast(ColumnIndex::MsCPUBusy)], rowIndex, "MsCPUBusy"); + parsed.msCpuWait = ParseDouble( + row[static_cast(ColumnIndex::MsCPUWait)], rowIndex, "MsCPUWait"); + parsed.msGpuLatency = ParseDouble( + row[static_cast(ColumnIndex::MsGPULatency)], rowIndex, "MsGPULatency"); + parsed.msGpuTime = ParseDouble( + row[static_cast(ColumnIndex::MsGPUTime)], rowIndex, "MsGPUTime"); + parsed.msGpuBusy = ParseDouble( + row[static_cast(ColumnIndex::MsGPUBusy)], rowIndex, "MsGPUBusy"); + parsed.msGpuWait = ParseDouble( + row[static_cast(ColumnIndex::MsGPUWait)], rowIndex, "MsGPUWait"); + parsed.msVideoBusy = ParseDouble( + row[static_cast(ColumnIndex::MsVideoBusy)], rowIndex, "MsVideoBusy"); + parsed.msAnimationError = ParseOptionalDouble( + row[static_cast(ColumnIndex::MsAnimationError)], rowIndex, "MsAnimationError"); + parsed.animationTime = ParseOptionalDouble( + row[static_cast(ColumnIndex::AnimationTime)], rowIndex, "AnimationTime"); + parsed.msFlipDelay = ParseOptionalDouble( + row[static_cast(ColumnIndex::MsFlipDelay)], rowIndex, "MsFlipDelay"); + parsed.msAllInputToPhotonLatency = ParseOptionalDouble( + row[static_cast(ColumnIndex::MsAllInputToPhotonLatency)], rowIndex, "MsAllInputToPhotonLatency"); + parsed.msClickToPhotonLatency = ParseOptionalDouble( + row[static_cast(ColumnIndex::MsClickToPhotonLatency)], rowIndex, "MsClickToPhotonLatency"); + parsed.msInstrumentedLatency = ParseOptionalDouble( + row[static_cast(ColumnIndex::MsInstrumentedLatency)], rowIndex, "MsInstrumentedLatency"); + return parsed; + } + + std::vector LoadCsvRows(const std::string& path, uint32_t targetPid) + { + csv::CSVReader reader{ path }; + auto header = reader.get_col_names(); + if (!header.empty()) { + StripUtf8Bom(header.front()); + } + ValidateHeader(header); + + std::vector rows; + std::vector values; + size_t rowIndex = 0; + for (auto& row : reader) { + values.clear(); + values.reserve(row.size()); + for (auto& field : row) { + values.push_back(field.get()); + } + if (values.size() < kFrameCsvHeader.size()) { + ++rowIndex; + continue; + } + auto rowPid = ParseUint32(values[static_cast(ColumnIndex::ProcessID)], + rowIndex, "ProcessID"); + if (rowPid != targetPid) { + ++rowIndex; + continue; + } + auto parsed = ParseFrameRow(values, rowIndex); + rows.push_back(std::move(parsed)); + ++rowIndex; + } + return rows; + } + + std::optional FindProcessNameInCsv(const std::string& path, uint32_t targetPid) + { + if (!fs::exists(path)) { + return std::nullopt; + } + csv::CSVReader reader{ path }; + auto header = reader.get_col_names(); + if (header.empty()) { + return std::nullopt; + } + StripUtf8Bom(header.front()); + + size_t pidIndex = SIZE_MAX; + size_t appIndex = SIZE_MAX; + for (size_t i = 0; i < header.size(); ++i) { + if (header[i] == "ProcessID") { + pidIndex = i; + } + else if (header[i] == "Application") { + appIndex = i; + } + } + if (pidIndex == SIZE_MAX || appIndex == SIZE_MAX) { + return std::nullopt; + } + + for (auto& row : reader) { + if (row.size() <= pidIndex || row.size() <= appIndex) { + continue; + } + auto pidValue = row[pidIndex].get(); + if (ParseUint32(pidValue, 0, "ProcessID") == targetPid) { + return row[appIndex].get(); + } + } + return std::nullopt; + } + + void CompareOptionalDouble(const std::optional& expected, const std::optional& actual, + size_t rowIndex, const char* column) + { + if (expected.has_value() != actual.has_value()) { + Assert::Fail(MakeFailMessage(rowIndex, column).c_str()); + } + if (expected && actual && *expected != *actual) { + Assert::Fail(MakeFailMessage(rowIndex, column).c_str()); + } + } + + void CompareRows(const FrameCsvRow& expected, const FrameCsvRow& actual, size_t rowIndex) + { + if (expected.application != actual.application) { + Assert::Fail(MakeFailMessage(rowIndex, "Application", expected.application, actual.application).c_str()); + } + if (expected.processId != actual.processId) { + Assert::Fail(MakeFailMessage(rowIndex, "ProcessID").c_str()); + } + if (expected.swapChainAddress != actual.swapChainAddress) { + Assert::Fail(MakeFailMessage(rowIndex, "SwapChainAddress").c_str()); + } + if (expected.presentRuntime != actual.presentRuntime) { + Assert::Fail(MakeFailMessage(rowIndex, "PresentRuntime", + expected.presentRuntime, actual.presentRuntime).c_str()); + } + if (expected.syncInterval != actual.syncInterval) { + Assert::Fail(MakeFailMessage(rowIndex, "SyncInterval").c_str()); + } + if (expected.presentFlags != actual.presentFlags) { + Assert::Fail(MakeFailMessage(rowIndex, "PresentFlags").c_str()); + } + if (expected.allowsTearing != actual.allowsTearing) { + Assert::Fail(MakeFailMessage(rowIndex, "AllowsTearing").c_str()); + } + if (expected.presentMode != actual.presentMode) { + Assert::Fail(MakeFailMessage(rowIndex, "PresentMode", + expected.presentMode, actual.presentMode).c_str()); + } + if (expected.frameType != actual.frameType) { + Assert::Fail(MakeFailMessage(rowIndex, "FrameType", + expected.frameType, actual.frameType).c_str()); + } + CompareOptionalDouble(expected.cpuStartTime, actual.cpuStartTime, + rowIndex, "CPUStartTime"); + CompareOptionalDouble(expected.msBetweenSimulationStart, actual.msBetweenSimulationStart, + rowIndex, "MsBetweenSimulationStart"); + if (expected.msBetweenPresents != actual.msBetweenPresents) { + Assert::Fail(MakeFailMessage(rowIndex, "MsBetweenPresents").c_str()); + } + CompareOptionalDouble(expected.msBetweenDisplayChange, actual.msBetweenDisplayChange, + rowIndex, "MsBetweenDisplayChange"); + if (expected.msInPresentApi != actual.msInPresentApi) { + Assert::Fail(MakeFailMessage(rowIndex, "MsInPresentAPI").c_str()); + } + if (expected.msRenderPresentLatency != actual.msRenderPresentLatency) { + Assert::Fail(MakeFailMessage(rowIndex, "MsRenderPresentLatency").c_str()); + } + CompareOptionalDouble(expected.msUntilDisplayed, actual.msUntilDisplayed, + rowIndex, "MsUntilDisplayed"); + CompareOptionalDouble(expected.msPcLatency, actual.msPcLatency, + rowIndex, "MsPCLatency"); + if (expected.msBetweenAppStart != actual.msBetweenAppStart) { + Assert::Fail(MakeFailMessage(rowIndex, "MsBetweenAppStart").c_str()); + } + if (expected.msCpuBusy != actual.msCpuBusy) { + Assert::Fail(MakeFailMessage(rowIndex, "MsCPUBusy").c_str()); + } + if (expected.msCpuWait != actual.msCpuWait) { + Assert::Fail(MakeFailMessage(rowIndex, "MsCPUWait").c_str()); + } + if (expected.msGpuLatency != actual.msGpuLatency) { + Assert::Fail(MakeFailMessage(rowIndex, "MsGPULatency").c_str()); + } + if (expected.msGpuTime != actual.msGpuTime) { + Assert::Fail(MakeFailMessage(rowIndex, "MsGPUTime").c_str()); + } + if (expected.msGpuBusy != actual.msGpuBusy) { + Assert::Fail(MakeFailMessage(rowIndex, "MsGPUBusy").c_str()); + } + if (expected.msGpuWait != actual.msGpuWait) { + Assert::Fail(MakeFailMessage(rowIndex, "MsGPUWait").c_str()); + } + if (expected.msVideoBusy != actual.msVideoBusy) { + Assert::Fail(MakeFailMessage(rowIndex, "MsVideoBusy").c_str()); + } + CompareOptionalDouble(expected.msAnimationError, actual.msAnimationError, + rowIndex, "MsAnimationError"); + CompareOptionalDouble(expected.animationTime, actual.animationTime, + rowIndex, "AnimationTime"); + CompareOptionalDouble(expected.msFlipDelay, actual.msFlipDelay, + rowIndex, "MsFlipDelay"); + CompareOptionalDouble(expected.msAllInputToPhotonLatency, actual.msAllInputToPhotonLatency, + rowIndex, "MsAllInputToPhotonLatency"); + CompareOptionalDouble(expected.msClickToPhotonLatency, actual.msClickToPhotonLatency, + rowIndex, "MsClickToPhotonLatency"); + CompareOptionalDouble(expected.msInstrumentedLatency, actual.msInstrumentedLatency, + rowIndex, "MsInstrumentedLatency"); + } + + void CompareCsvFiles(const std::string& goldPath, const std::string& runPath, uint32_t targetPid) + { + auto goldRows = LoadCsvRows(goldPath, targetPid); + auto runRows = LoadCsvRows(runPath, targetPid); + + Assert::IsTrue(!goldRows.empty(), L"No gold rows found for target pid"); + Assert::IsTrue(goldRows.size() == runRows.size(), L"Row count mismatch"); + for (size_t i = 0; i < goldRows.size(); ++i) { + CompareRows(goldRows[i], runRows[i], i); + } + } + + void ExecutePacedFrameTest(const std::string& testName, uint32_t targetPid, + size_t frameLimit, TestFixture& fixture) + { + const auto goldCsvPath = std::format(R"(..\..\Tests\AuxData\Data\{}_gold.csv)", testName); + const auto outCsvPath = std::format("{}\\{}.csv", outFolder_, testName); + Logger::WriteMessage(std::format("Frame test output csv: {}\n", + fs::absolute(outCsvPath).string()).c_str()); + Logger::WriteMessage(std::format("Frame test gold csv search path: {}\n", + fs::absolute(goldCsvPath).string()).c_str()); + + std::optional processName; + if (fs::exists(goldCsvPath)) { + processName = FindProcessNameInCsv(goldCsvPath, targetPid); + } + + std::vector args{ + "--process-id"s, std::to_string(targetPid), + "--output-path"s, outCsvPath, + "--frame-limit"s, std::to_string(frameLimit), + }; + if (processName) { + args.push_back("--process-name"s); + args.push_back(*processName); + } + + fixture.LaunchClient(args); + + Assert::IsTrue(fs::exists(outCsvPath), L"Output CSV not created"); + Assert::IsTrue(fs::exists(goldCsvPath), L"Gold CSV missing"); + CompareCsvFiles(goldCsvPath, outCsvPath, targetPid); + } + +#define TEST_NAME F00HeaWin2080 +#define ETL_NAME P00HeaWin2080 + TEST_CLASS(TEST_NAME) + { + TestFixture fixture_; + public: + TEST_METHOD_INITIALIZE(Setup) + { + Logger::WriteMessage(std::format("Frame test etl path: {}\n", + fs::absolute(std::format(R"(..\..\Tests\AuxData\Data\{}.etl)", STRINGIFY(ETL_NAME))).string()).c_str()); + fixture_.Setup({ + "--etl-test-file"s, std::format(R"(..\..\Tests\AuxData\Data\{}.etl)", STRINGIFY(ETL_NAME)), + "--pace-playback"s, + }); + } + TEST_METHOD_CLEANUP(Cleanup) + { + fixture_.Cleanup(); + } + TEST_METHOD(PollFrame) + { + const uint32_t targetPid = 12820; + const size_t frameLimit = 1903; + ExecutePacedFrameTest(STRINGIFY(TEST_NAME), targetPid, frameLimit, fixture_); + } + }; +#undef TEST_NAME +#undef ETL_NAME + +#define TEST_NAME F01TimeSpyDemoFS2080 +#define ETL_NAME P01TimeSpyDemoFS2080 + TEST_CLASS(TEST_NAME) + { + TestFixture fixture_; + public: + TEST_METHOD_INITIALIZE(Setup) + { + Logger::WriteMessage(std::format("Frame test etl path: {}\n", + fs::absolute(std::format(R"(..\..\Tests\AuxData\Data\{}.etl)", STRINGIFY(ETL_NAME))).string()).c_str()); + fixture_.Setup({ + "--etl-test-file"s, std::format(R"(..\..\Tests\AuxData\Data\{}.etl)", STRINGIFY(ETL_NAME)), + "--pace-playback"s, + }); + } + TEST_METHOD_CLEANUP(Cleanup) + { + fixture_.Cleanup(); + } + TEST_METHOD(PollFrame) + { + const uint32_t targetPid = 19736; + const size_t frameLimit = 0; + ExecutePacedFrameTest(STRINGIFY(TEST_NAME), targetPid, frameLimit, fixture_); + } + }; +#undef TEST_NAME +#undef ETL_NAME +} diff --git a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj index 12d27a8a..7ebfef85 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj +++ b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj @@ -101,6 +101,7 @@ + @@ -145,4 +146,4 @@ - \ No newline at end of file + diff --git a/IntelPresentMon/SampleClient/CliOptions.h b/IntelPresentMon/SampleClient/CliOptions.h index e4ae973d..637888bc 100644 --- a/IntelPresentMon/SampleClient/CliOptions.h +++ b/IntelPresentMon/SampleClient/CliOptions.h @@ -26,6 +26,7 @@ namespace clio MultiClient, EtlLogger, PacedPlayback, + PacedFramePlayback, IpcComponentServer, Count, }; @@ -52,6 +53,7 @@ namespace clio Option runStart{ this, "--run-start", 1., "How many seconds to delay before beginning run" }; Option pollPeriod{ this, "--poll-period", 0.1, "Period in seconds for polling the API query" }; Option outputPath{ this, "--output-path", {}, "Full path to output to" }; + Option frameLimit{ this, "--frame-limit", 0, "Maximum number of frames to capture (0 for unlimited)" }; private: Group gl_{ this, "Logging", "Control logging behavior" }; public: Option logLevel{ this, "--log-level", log::Level::Error, "Severity to log at", CLI::CheckedTransformer{ log::GetLevelMapNarrow(), CLI::ignore_case } }; Option logTraceLevel{ this, "--log-trace-level", log::Level::Error, "Severity to print stacktrace at", CLI::CheckedTransformer{ log::GetLevelMapNarrow(), CLI::ignore_case } }; diff --git a/IntelPresentMon/SampleClient/PacedFramePlayback.cpp b/IntelPresentMon/SampleClient/PacedFramePlayback.cpp new file mode 100644 index 00000000..7c5e55d4 --- /dev/null +++ b/IntelPresentMon/SampleClient/PacedFramePlayback.cpp @@ -0,0 +1,332 @@ +#include "PacedFramePlayback.h" +#include "CliOptions.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace pmon; +using namespace std::literals; + +namespace +{ + const std::array kFrameCsvHeader{ + "Application", + "ProcessID", + "SwapChainAddress", + "PresentRuntime", + "SyncInterval", + "PresentFlags", + "AllowsTearing", + "PresentMode", + "FrameType", + "CPUStartTime", + "MsBetweenSimulationStart", + "MsBetweenPresents", + "MsBetweenDisplayChange", + "MsInPresentAPI", + "MsRenderPresentLatency", + "MsUntilDisplayed", + "MsPCLatency", + "MsBetweenAppStart", + "MsCPUBusy", + "MsCPUWait", + "MsGPULatency", + "MsGPUTime", + "MsGPUBusy", + "MsGPUWait", + "MsVideoBusy", + "MsAnimationError", + "AnimationTime", + "MsFlipDelay", + "MsAllInputToPhotonLatency", + "MsClickToPhotonLatency", + "MsInstrumentedLatency", + }; + + std::string TranslateGraphicsRuntime(PM_GRAPHICS_RUNTIME runtime) + { + switch (runtime) { + case PM_GRAPHICS_RUNTIME_DXGI: + return "DXGI"; + case PM_GRAPHICS_RUNTIME_D3D9: + return "D3D9"; + default: + return "Other"; + } + } + + std::string TranslatePresentMode(PM_PRESENT_MODE presentMode) + { + switch (presentMode) { + case PM_PRESENT_MODE_HARDWARE_LEGACY_FLIP: + return "Hardware: Legacy Flip"; + case PM_PRESENT_MODE_HARDWARE_LEGACY_COPY_TO_FRONT_BUFFER: + return "Hardware: Legacy Copy to front buffer"; + case PM_PRESENT_MODE_HARDWARE_INDEPENDENT_FLIP: + return "Hardware: Independent Flip"; + case PM_PRESENT_MODE_COMPOSED_FLIP: + return "Composed: Flip"; + case PM_PRESENT_MODE_HARDWARE_COMPOSED_INDEPENDENT_FLIP: + return "Hardware Composed: Independent Flip"; + case PM_PRESENT_MODE_COMPOSED_COPY_WITH_GPU_GDI: + return "Composed: Copy with GPU GDI"; + case PM_PRESENT_MODE_COMPOSED_COPY_WITH_CPU_GDI: + return "Composed: Copy with CPU GDI"; + default: + return "Other"; + } + } + + std::string TranslateFrameType(PM_FRAME_TYPE frameType) + { + switch (frameType) { + case PM_FRAME_TYPE_NOT_SET: + case PM_FRAME_TYPE_UNSPECIFIED: + case PM_FRAME_TYPE_APPLICATION: + return "Application"; + case PM_FRAME_TYPE_AMD_AFMF: + return "AMD_AFMF"; + case PM_FRAME_TYPE_INTEL_XEFG: + return "Intel XeSS-FG"; + default: + return "Other"; + } + } + + void WriteHeader(std::ofstream& csv) + { + for (size_t i = 0; i < kFrameCsvHeader.size(); ++i) { + if (i > 0) { + csv << ","; + } + csv << kFrameCsvHeader[i]; + } + csv << "\n"; + } + + void WriteOptionalDouble(std::ofstream& csv, double value) + { + if (std::isnan(value)) { + csv << "NA"; + return; + } + csv << value; + } + + void WriteOptionalElement(std::ofstream& csv, const pmapi::FixedQueryElement& element) + { + if (!element.IsAvailable()) { + csv << "NA"; + return; + } + WriteOptionalDouble(csv, element.As()); + } +} + +int PacedFramePlaybackTest(std::unique_ptr pSession) +{ + auto& opt = clio::Options::Get(); + + std::optional errorStatus; + + try { + if (!opt.processId) { + pmlog_error("need pid"); + } + if (!opt.outputPath) { + pmlog_error("need output path"); + } + + if (opt.etwFlushPeriodMs) { + pSession->SetEtwFlushPeriod(*opt.etwFlushPeriodMs); + } + if (opt.telemetryPeriodMs) { + pSession->SetTelemetryPollingPeriod(0, *opt.telemetryPeriodMs); + } + + std::string line; + std::getline(std::cin, line); + if (line != "%ping") { + std::cout << "%%{ping-error}%%" << std::endl; + return -1; + } + std::cout << "%%{ping-ok}%%" << std::endl; + + const auto processName = opt.processName.AsOptional().value_or("unknown"s); + const auto frameLimit = static_cast(*opt.frameLimit); + + PM_BEGIN_FIXED_FRAME_QUERY(FrameQuery) + pmapi::FixedQueryElement swapChain{ this, PM_METRIC_SWAP_CHAIN_ADDRESS, PM_STAT_NONE }; + pmapi::FixedQueryElement presentRuntime{ this, PM_METRIC_PRESENT_RUNTIME, PM_STAT_NONE }; + pmapi::FixedQueryElement syncInterval{ this, PM_METRIC_SYNC_INTERVAL, PM_STAT_NONE }; + pmapi::FixedQueryElement presentFlags{ this, PM_METRIC_PRESENT_FLAGS, PM_STAT_NONE }; + pmapi::FixedQueryElement allowsTearing{ this, PM_METRIC_ALLOWS_TEARING, PM_STAT_NONE }; + pmapi::FixedQueryElement presentMode{ this, PM_METRIC_PRESENT_MODE, PM_STAT_NONE }; + pmapi::FixedQueryElement frameType{ this, PM_METRIC_FRAME_TYPE, PM_STAT_NONE }; + pmapi::FixedQueryElement cpuStartTime{ this, PM_METRIC_CPU_START_TIME, PM_STAT_NONE }; + pmapi::FixedQueryElement msBetweenSimStart{ this, PM_METRIC_BETWEEN_SIMULATION_START, PM_STAT_NONE }; + pmapi::FixedQueryElement msBetweenPresents{ this, PM_METRIC_BETWEEN_PRESENTS, PM_STAT_NONE }; + pmapi::FixedQueryElement msBetweenDisplayChange{ this, PM_METRIC_BETWEEN_DISPLAY_CHANGE, PM_STAT_NONE }; + pmapi::FixedQueryElement msInPresentApi{ this, PM_METRIC_IN_PRESENT_API, PM_STAT_NONE }; + pmapi::FixedQueryElement msRenderPresentLatency{ this, PM_METRIC_RENDER_PRESENT_LATENCY, PM_STAT_NONE }; + pmapi::FixedQueryElement msUntilDisplayed{ this, PM_METRIC_UNTIL_DISPLAYED, PM_STAT_NONE }; + pmapi::FixedQueryElement msPcLatency{ this, PM_METRIC_PC_LATENCY, PM_STAT_NONE }; + pmapi::FixedQueryElement msBetweenAppStart{ this, PM_METRIC_BETWEEN_APP_START, PM_STAT_NONE }; + pmapi::FixedQueryElement msCpuBusy{ this, PM_METRIC_CPU_BUSY, PM_STAT_NONE }; + pmapi::FixedQueryElement msCpuWait{ this, PM_METRIC_CPU_WAIT, PM_STAT_NONE }; + pmapi::FixedQueryElement msGpuLatency{ this, PM_METRIC_GPU_LATENCY, PM_STAT_NONE }; + pmapi::FixedQueryElement msGpuTime{ this, PM_METRIC_GPU_TIME, PM_STAT_NONE }; + pmapi::FixedQueryElement msGpuBusy{ this, PM_METRIC_GPU_BUSY, PM_STAT_NONE }; + pmapi::FixedQueryElement msGpuWait{ this, PM_METRIC_GPU_WAIT, PM_STAT_NONE }; + pmapi::FixedQueryElement msAnimationError{ this, PM_METRIC_ANIMATION_ERROR, PM_STAT_NONE }; + pmapi::FixedQueryElement animationTime{ this, PM_METRIC_ANIMATION_TIME, PM_STAT_NONE }; + pmapi::FixedQueryElement msFlipDelay{ this, PM_METRIC_FLIP_DELAY, PM_STAT_NONE }; + pmapi::FixedQueryElement msAllInputToPhotonLatency{ this, PM_METRIC_ALL_INPUT_TO_PHOTON_LATENCY, PM_STAT_NONE }; + pmapi::FixedQueryElement msClickToPhotonLatency{ this, PM_METRIC_CLICK_TO_PHOTON_LATENCY, PM_STAT_NONE }; + pmapi::FixedQueryElement msInstrumentedLatency{ this, PM_METRIC_INSTRUMENTED_LATENCY, PM_STAT_NONE }; + PM_END_FIXED_QUERY query{ *pSession, 512 }; + + auto tracker = pSession->TrackProcess(*opt.processId); + + std::ofstream csv{ *opt.outputPath }; + if (!csv.is_open()) { + pmlog_error("failed to open output file"); + return -1; + } + csv << std::fixed << std::setprecision(14); + WriteHeader(csv); + + using Clock = std::chrono::high_resolution_clock; + const auto start = Clock::now(); + size_t emptyPollCount = 0; + size_t totalRecorded = 0; + const size_t emptyLimit = 10; + + while (true) { + const auto elapsed = std::chrono::duration(Clock::now() - start).count(); + const auto processed = query.ForEachConsume(tracker, [&] { + if (frameLimit > 0 && totalRecorded >= frameLimit) { + return; + } + csv << processName << ","; + csv << *opt.processId << ","; + csv << std::hex << std::uppercase << "0x" << query.swapChain.As() + << std::dec << std::nouppercase << ","; + csv << TranslateGraphicsRuntime(query.presentRuntime.As()) << ","; + csv << query.syncInterval.As() << ","; + csv << query.presentFlags.As() << ","; + csv << (query.allowsTearing.As() ? 1 : 0) << ","; + csv << TranslatePresentMode(query.presentMode.As()) << ","; + csv << TranslateFrameType(query.frameType.As()) << ","; + WriteOptionalElement(csv, query.cpuStartTime); + csv << ","; + WriteOptionalElement(csv, query.msBetweenSimStart); + csv << ","; + WriteOptionalElement(csv, query.msBetweenPresents); + csv << ","; + WriteOptionalElement(csv, query.msBetweenDisplayChange); + csv << ","; + WriteOptionalElement(csv, query.msInPresentApi); + csv << ","; + WriteOptionalElement(csv, query.msRenderPresentLatency); + csv << ","; + WriteOptionalElement(csv, query.msUntilDisplayed); + csv << ","; + WriteOptionalElement(csv, query.msPcLatency); + csv << ","; + WriteOptionalElement(csv, query.msBetweenAppStart); + csv << ","; + WriteOptionalElement(csv, query.msCpuBusy); + csv << ","; + WriteOptionalElement(csv, query.msCpuWait); + csv << ","; + WriteOptionalElement(csv, query.msGpuLatency); + csv << ","; + WriteOptionalElement(csv, query.msGpuTime); + csv << ","; + WriteOptionalElement(csv, query.msGpuBusy); + csv << ","; + WriteOptionalElement(csv, query.msGpuWait); + csv << ","; + WriteOptionalDouble(csv, 0.0); + csv << ","; + WriteOptionalElement(csv, query.msAnimationError); + csv << ","; + WriteOptionalElement(csv, query.animationTime); + csv << ","; + WriteOptionalElement(csv, query.msFlipDelay); + csv << ","; + WriteOptionalElement(csv, query.msAllInputToPhotonLatency); + csv << ","; + WriteOptionalElement(csv, query.msClickToPhotonLatency); + csv << ","; + WriteOptionalElement(csv, query.msInstrumentedLatency); + csv << "\n"; + ++totalRecorded; + }); + + if (frameLimit > 0 && totalRecorded >= frameLimit) { + break; + } + + if (processed == 0) { + if (totalRecorded > 0) { + if (++emptyPollCount >= emptyLimit) { + break; + } + } + else if (elapsed >= 1.0) { + break; + } + } + else { + emptyPollCount = 0; + } + + if (processed == 0) { + std::this_thread::sleep_for(8ms); + } + } + } + catch (const pmapi::ApiErrorException& e) { + if (!opt.testExpectError) { + throw; + } + errorStatus = e.GetCode(); + } + + std::string line; + + if (errorStatus) { + std::getline(std::cin, line); + if (line != "%err-check") { + std::cout << "%%{err-check-error}%%" << std::endl; + return -1; + } + auto&& err = pmapi::EnumMap::GetKeyMap(PM_ENUM_STATUS)->at(*errorStatus).narrowSymbol; + std::cout << "%%{err-check-ok:" << err << "}%%" << std::endl; + } + + while (std::getline(std::cin, line)) { + if (line == "%quit") { + std::cout << "%%{quit-ok}%%" << std::endl; + std::this_thread::sleep_for(25ms); + return 0; + } + else { + std::cout << "%%{err-bad-command}%%" << std::endl; + } + } + + return -1; +} diff --git a/IntelPresentMon/SampleClient/PacedFramePlayback.h b/IntelPresentMon/SampleClient/PacedFramePlayback.h new file mode 100644 index 00000000..398da241 --- /dev/null +++ b/IntelPresentMon/SampleClient/PacedFramePlayback.h @@ -0,0 +1,5 @@ +#pragma once +#include +#include "../PresentMonAPIWrapper/PresentMonAPIWrapper.h" + +int PacedFramePlaybackTest(std::unique_ptr pSession); diff --git a/IntelPresentMon/SampleClient/SampleClient.cpp b/IntelPresentMon/SampleClient/SampleClient.cpp index bcfb9789..d2aaecf8 100644 --- a/IntelPresentMon/SampleClient/SampleClient.cpp +++ b/IntelPresentMon/SampleClient/SampleClient.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #include "../CommonUtilities/win/WinAPI.h" #include @@ -35,6 +35,7 @@ #include "EtlLogger.h" #include "IpcComponentServer.h" #include "PacedPlayback.h" +#include "PacedFramePlayback.h" #include "LogDemo.h" #include "DiagnosticDemo.h" #include "LogSetup.h" @@ -373,6 +374,8 @@ int main(int argc, char* argv[]) return EtlLoggerTest(ConnectSession()); case clio::Mode::PacedPlayback: return PacedPlaybackTest(ConnectSession()); + case clio::Mode::PacedFramePlayback: + return PacedFramePlaybackTest(ConnectSession()); case clio::Mode::PlaybackDynamicQuery: RunPlaybackDynamicQueryN(); break; case clio::Mode::PlaybackFrameQuery: @@ -393,4 +396,4 @@ int main(int argc, char* argv[]) } return 0; -} \ No newline at end of file +} diff --git a/IntelPresentMon/SampleClient/SampleClient.vcxproj b/IntelPresentMon/SampleClient/SampleClient.vcxproj index 46ae1010..a6afd7fa 100644 --- a/IntelPresentMon/SampleClient/SampleClient.vcxproj +++ b/IntelPresentMon/SampleClient/SampleClient.vcxproj @@ -113,6 +113,7 @@ + @@ -130,6 +131,7 @@ + From 1ffe5c8251af4c6d4a8264c3c986b5d4756eda71 Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 15 Jan 2026 16:26:42 +0900 Subject: [PATCH 110/205] draft of frame event query (non-building) --- .../CommonUtilities/mc/MetricsTypes.h | 1 - .../Interprocess/source/SystemDeviceId.h | 1 + .../ConcreteMiddleware.cpp | 2 +- .../PresentMonMiddleware/FrameEventQuery.cpp | 2048 +++-------------- .../PresentMonMiddleware/FrameEventQuery.h | 117 +- 5 files changed, 414 insertions(+), 1755 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h index b4687583..90b8de8b 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h @@ -4,7 +4,6 @@ #include #include #include - #include "../cnr/FixedVector.h" // Forward declarations for external types diff --git a/IntelPresentMon/Interprocess/source/SystemDeviceId.h b/IntelPresentMon/Interprocess/source/SystemDeviceId.h index 7de46287..3aa851ab 100644 --- a/IntelPresentMon/Interprocess/source/SystemDeviceId.h +++ b/IntelPresentMon/Interprocess/source/SystemDeviceId.h @@ -3,5 +3,6 @@ namespace pmon::ipc { + inline constexpr uint32_t kUniversalDeviceId = 0; inline constexpr uint32_t kSystemDeviceId = 65536; } diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index 4d57d040..378f9928 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -1256,7 +1256,7 @@ static void ReportMetrics( PM_FRAME_QUERY* mid::ConcreteMiddleware::RegisterFrameEventQuery(std::span queryElements, uint32_t& blobSize) { - const auto pQuery = new PM_FRAME_QUERY{ queryElements }; + const auto pQuery = new PM_FRAME_QUERY{ queryElements, *pComms }; blobSize = (uint32_t)pQuery->GetBlobSize(); return pQuery; } diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index 8235120d..ab8e531b 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -1,1737 +1,423 @@ // Copyright (C) 2017-2024 Intel Corporation -// SPDX-License-Identifier: MIT -#define NOMINMAX -#include "../PresentMonUtils/StreamFormat.h" #include "FrameEventQuery.h" #include "../PresentMonAPIWrapperCommon/Introspection.h" +#include "../Interprocess/source/Interprocess.h" #include "../Interprocess/source/SystemDeviceId.h" -#include "../CommonUtilities/Memory.h" -#include "../CommonUtilities/Meta.h" +#include "../Interprocess/source/IntrospectionHelpers.h" #include "../CommonUtilities/log/Log.h" #include "../CommonUtilities/Exception.h" +#include "../CommonUtilities/Memory.h" +#include "../CommonUtilities/Qpc.h" #include #include #include -using namespace pmon; -using namespace pmon::util; -using Context = PM_FRAME_QUERY::Context; - namespace pmon::mid { - class GatherCommand_ - { - public: - virtual ~GatherCommand_() = default; - virtual void Gather(Context& ctx, uint8_t* pDestBlob) const = 0; - virtual uint32_t GetBeginOffset() const = 0; - virtual uint32_t GetEndOffset() const = 0; - virtual uint32_t GetOutputOffset() const = 0; - uint32_t GetDataSize() const { return GetEndOffset() - GetOutputOffset(); } - uint32_t GetTotalSize() const { return GetEndOffset() - GetBeginOffset(); } - }; -} - -namespace -{ - double TimestampDeltaToMilliSeconds(uint64_t timestampDelta, double performanceCounterPeriodMs) - { - return performanceCounterPeriodMs * double(timestampDelta); - } - - double TimestampDeltaToUnsignedMilliSeconds(uint64_t timestampFrom, uint64_t timestampTo, double performanceCounterPeriodMs) - { - return timestampFrom == 0 || timestampTo <= timestampFrom ? 0.0 : - TimestampDeltaToMilliSeconds(timestampTo - timestampFrom, performanceCounterPeriodMs); - } - - double TimestampDeltaToMilliSeconds(uint64_t timestampFrom, uint64_t timestampTo, double performanceCounterPeriodMs) - { - return timestampFrom == 0 || timestampTo == 0 || timestampFrom == timestampTo ? 0.0 : - timestampTo > timestampFrom ? TimestampDeltaToMilliSeconds(timestampTo - timestampFrom, performanceCounterPeriodMs) - : -TimestampDeltaToMilliSeconds(timestampFrom - timestampTo, performanceCounterPeriodMs); - } + using namespace util; - template - constexpr auto GetSubstructurePointer() - { - using SubstructureType = util::MemberPointerInfo::StructType; - if constexpr (std::same_as) { - return &PmNsmFrameData::present_event; - } - else if constexpr (std::same_as) { - return &PmNsmFrameData::power_telemetry; - } - else if constexpr (std::same_as) { - return &PmNsmFrameData::cpu_telemetry; - } - } - - template - class CopyGatherCommand_ : public mid::GatherCommand_ - { - using Type = util::MemberPointerInfo::MemberType; - public: - CopyGatherCommand_(size_t nextAvailableByteOffset, uint16_t index = 0) - : - inputIndex_{ index } - { - outputPaddingSize_ = (uint16_t)util::GetPadding(nextAvailableByteOffset, alignof(Type)); - outputOffset_ = uint32_t(nextAvailableByteOffset) + outputPaddingSize_; - } - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - constexpr auto pSubstruct = GetSubstructurePointer(); - if constexpr (std::is_array_v) { - if constexpr (std::is_same_v, char>) { - const auto val = (ctx.pSourceFrameData->*pSubstruct.*pMember)[inputIndex_]; - // TODO: only getting first character of application name. Hmmm. - strncpy_s(reinterpret_cast(&pDestBlob[outputOffset_]), 260, &val, _TRUNCATE); - } - else { - const auto val = (ctx.pSourceFrameData->*pSubstruct.*pMember)[inputIndex_]; - reinterpret_cast&>(pDestBlob[outputOffset_]) = val; - } - } - else { - const auto val = ctx.pSourceFrameData->*pSubstruct.*pMember; - reinterpret_cast&>(pDestBlob[outputOffset_]) = val; - } - } - uint32_t GetBeginOffset() const override - { - return outputOffset_ - outputPaddingSize_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(Type); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - uint16_t outputPaddingSize_; - uint16_t inputIndex_; - }; - class CopyGatherFrameTypeCommand_ : public mid::GatherCommand_ + PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::MiddlewareComms& comms) + : + comms_{ comms } { - public: - CopyGatherFrameTypeCommand_(size_t nextAvailableByteOffset, uint16_t index = 0) - : - inputIndex_{ index } - { - outputPaddingSize_ = (uint16_t)util::GetPadding(nextAvailableByteOffset, alignof(FrameType)); - outputOffset_ = uint32_t(nextAvailableByteOffset) + outputPaddingSize_; - } - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - auto val = ctx.pSourceFrameData->present_event.Displayed_FrameType[ctx.sourceFrameDisplayIndex]; - // Currently not reporting out not set or repeated frames. - if (val == FrameType::NotSet || val == FrameType::Repeated) { - val = FrameType::Application; - } - reinterpret_cast&>(pDestBlob[outputOffset_]) = val; - } - uint32_t GetBeginOffset() const override - { - return outputOffset_ - outputPaddingSize_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(FrameType); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - uint16_t outputPaddingSize_; - uint16_t inputIndex_; - }; - template - class QpcDurationGatherCommand_ : public pmon::mid::GatherCommand_ - { - public: - QpcDurationGatherCommand_(size_t nextAvailableByteOffset) - { - outputPaddingSize_ = (uint16_t)util::GetPadding(nextAvailableByteOffset, alignof(double)); - outputOffset_ = uint32_t(nextAvailableByteOffset) + outputPaddingSize_; - } - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - if constexpr (usesAppIndex) { - if (ctx.sourceFrameDisplayIndex == ctx.appIndex) { - if (ctx.pSourceFrameData->present_event.*pAppPropagatedMember != 0) { - const auto val = ctx.performanceCounterPeriodMs * double(ctx.pSourceFrameData->present_event.*pAppPropagatedMember); - reinterpret_cast(pDestBlob[outputOffset_]) = val; - return; + if (queryElements.empty()) { + pmlog_error("Frame query requires at least one query element").diag(); + throw Except("Empty frame query"); + } + + const auto pIntro = comms.GetIntrospectionRoot(); + if (!pIntro) { + pmlog_error("Failed to acquire introspection root for frame query").diag(); + throw Except("No introspection root for frame query"); + } + // TODO: consider direct use (unwrapped) + pmapi::intro::Root introRoot{ pIntro, [](const PM_INTROSPECTION_ROOT* p) { + free(const_cast(p)); + } }; + + // TODO: use data type ipc bridger + auto GetDataTypeAlignment = [](PM_DATA_TYPE dataType) { + switch (dataType) { + case PM_DATA_TYPE_DOUBLE: + return alignof(double); + case PM_DATA_TYPE_UINT64: + return alignof(uint64_t); + case PM_DATA_TYPE_INT32: + return alignof(int32_t); + case PM_DATA_TYPE_UINT32: + return alignof(uint32_t); + case PM_DATA_TYPE_ENUM: + return alignof(int); + case PM_DATA_TYPE_BOOL: + return alignof(bool); + case PM_DATA_TYPE_STRING: + return alignof(char); + default: + return size_t{ 1u }; + } + }; + + size_t blobCursor = 0; + gatherCommands_.reserve(queryElements.size()); + + for (auto& q : queryElements) { + const auto metricView = introRoot.FindMetric(q.metric); + if (!pmapi::intro::MetricTypeIsFrameEvent(metricView.GetType())) { + pmlog_error("Non-frame metric used in frame query") + .pmwatch(metricView.Introspect().GetSymbol()).diag(); + throw Except("Frame query contains non-frame metric"); + } + + if (q.stat != PM_STAT_NONE) { + pmlog_warn("Frame query stat should be NONE") + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch((int)q.stat).diag(); + } + + const auto frameType = metricView.GetDataTypeInfo().GetFrameType(); + const auto frameTypeSize = ipc::intro::GetDataTypeSize(frameType); + if (frameTypeSize == 0) { + pmlog_error("Unsupported frame query data type") + .pmwatch(metricView.Introspect().GetSymbol()).diag(); + throw Except("Unsupported frame query data type"); + } + + if (q.deviceId != ipc::kUniversalDeviceId) { + // TODO: check that device exists in introspection + } + + const auto deviceMetricInfo = [&]() -> std::optional { + for (auto info : metricView.GetDeviceMetricInfo()) { + if (info.GetDevice().GetId() == q.deviceId) { + return info; } - else if (ctx.pSourceFrameData->present_event.*pMember != 0) { - const auto val = ctx.performanceCounterPeriodMs * double(ctx.pSourceFrameData->present_event.*pMember); - reinterpret_cast(pDestBlob[outputOffset_]) = val; - return; - } - } - reinterpret_cast(pDestBlob[outputOffset_]) = 0.; - } else { - const auto val = ctx.pSourceFrameData->present_event.*pMember != 0 ? - ctx.performanceCounterPeriodMs * double(ctx.pSourceFrameData->present_event.*pMember) : 0.; - reinterpret_cast(pDestBlob[outputOffset_]) = val; - } - - } - uint32_t GetBeginOffset() const override - { - return outputOffset_ - outputPaddingSize_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(double); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - uint16_t outputPaddingSize_; - }; - template - class QpcDeltaGatherWithBackupCommand_ : public pmon::mid::GatherCommand_ - { - public: - QpcDeltaGatherWithBackupCommand_(size_t nextAvailableByteOffset) - { - outputPaddingSize_ = (uint16_t)util::GetPadding(nextAvailableByteOffset, alignof(double)); - outputOffset_ = uint32_t(nextAvailableByteOffset) + outputPaddingSize_; - } - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - const auto qpcFrom = ctx.pSourceFrameData->present_event.*pFromMember; - const auto qpcBackupFrom = ctx.pSourceFrameData->present_event.*pBackupFromMember; - uint64_t qpcTo = 0; - if (ctx.pSourceFrameData->present_event.AppPropagatedGPUStartTime != 0) { - qpcTo = ctx.pSourceFrameData->present_event.AppPropagatedGPUStartTime; - } - else { - qpcTo = ctx.pSourceFrameData->present_event.*pToMember; - } - auto instrumentedStart = qpcFrom != 0 ? qpcFrom : qpcBackupFrom; - if (instrumentedStart != 0) { - if (ctx.sourceFrameDisplayIndex == ctx.appIndex) { - const auto val = TimestampDeltaToUnsignedMilliSeconds(instrumentedStart, qpcTo, ctx.performanceCounterPeriodMs); - reinterpret_cast(pDestBlob[outputOffset_]) = val; - } - else { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - } - } - else { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - } - } - uint32_t GetBeginOffset() const override - { - return outputOffset_ - outputPaddingSize_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(double); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - uint16_t outputPaddingSize_; - }; - template - class QpcDeltaGatherFromToCommand_ : public pmon::mid::GatherCommand_ - { - public: - QpcDeltaGatherFromToCommand_(size_t nextAvailableByteOffset) - { - outputPaddingSize_ = (uint16_t)util::GetPadding(nextAvailableByteOffset, alignof(double)); - outputOffset_ = uint32_t(nextAvailableByteOffset) + outputPaddingSize_; - } - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - const auto qpcFrom = ctx.pSourceFrameData->present_event.*pFromMember; - const auto qpcTo = ctx.pSourceFrameData->present_event.*pToMember; - if (qpcFrom != 0 && qpcTo != 0) { - if constexpr (usesAppIndex) { - if (ctx.sourceFrameDisplayIndex == ctx.appIndex) { - const auto val = TimestampDeltaToUnsignedMilliSeconds(qpcFrom, qpcTo, ctx.performanceCounterPeriodMs); - reinterpret_cast(pDestBlob[outputOffset_]) = val; - } - else { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - } - } - else { - const auto val = TimestampDeltaToUnsignedMilliSeconds(qpcFrom, qpcTo, ctx.performanceCounterPeriodMs); - reinterpret_cast(pDestBlob[outputOffset_]) = val; - } - } - else { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - } - } - uint32_t GetBeginOffset() const override - { - return outputOffset_ - outputPaddingSize_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(double); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - uint16_t outputPaddingSize_; - }; - template - class QpcDeltaGatherToCommand_ : public pmon::mid::GatherCommand_ - { - public: - QpcDeltaGatherToCommand_(size_t nextAvailableByteOffset) - { - outputPaddingSize_ = (uint16_t)util::GetPadding(nextAvailableByteOffset, alignof(double)); - outputOffset_ = uint32_t(nextAvailableByteOffset) + outputPaddingSize_; - } - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - const auto qpcFrom = ctx.previousPresentStartQpc; - const auto qpcTo = ctx.pSourceFrameData->present_event.*pToMember; - if (qpcFrom != 0 && qpcTo != 0) { - const auto val = TimestampDeltaToUnsignedMilliSeconds(qpcFrom, qpcTo, ctx.performanceCounterPeriodMs); - reinterpret_cast(pDestBlob[outputOffset_]) = val; - } else { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - } - } - uint32_t GetBeginOffset() const override - { - return outputOffset_ - outputPaddingSize_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(double); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - uint16_t outputPaddingSize_; - }; - class GpuTimeGatherCommand_ : public pmon::mid::GatherCommand_ - { - public: - GpuTimeGatherCommand_(size_t nextAvailableByteOffset) - { - outputPaddingSize_ = (uint16_t)util::GetPadding(nextAvailableByteOffset, alignof(double)); - outputOffset_ = uint32_t(nextAvailableByteOffset) + outputPaddingSize_; - } - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - if (ctx.sourceFrameDisplayIndex == ctx.appIndex) { - double gpuDuration = 0.; - if (ctx.pSourceFrameData->present_event.AppPropagatedGPUStartTime != 0) { - gpuDuration = TimestampDeltaToUnsignedMilliSeconds(ctx.pSourceFrameData->present_event.AppPropagatedGPUStartTime, - ctx.pSourceFrameData->present_event.AppPropagatedReadyTime, ctx.performanceCounterPeriodMs); - } else { - gpuDuration = TimestampDeltaToUnsignedMilliSeconds(ctx.pSourceFrameData->present_event.GPUStartTime, - ctx.pSourceFrameData->present_event.ReadyTime, ctx.performanceCounterPeriodMs); - } - double gpuBusy = 0.; - if (ctx.pSourceFrameData->present_event.AppPropagatedGPUDuration != 0) { - gpuBusy = TimestampDeltaToMilliSeconds(ctx.pSourceFrameData->present_event.AppPropagatedGPUDuration, - ctx.performanceCounterPeriodMs); - } else { - gpuBusy = TimestampDeltaToMilliSeconds(ctx.pSourceFrameData->present_event.GPUDuration, - ctx.performanceCounterPeriodMs); - } - const auto gpuWait = std::max(0., gpuDuration - gpuBusy); - reinterpret_cast(pDestBlob[outputOffset_]) = gpuBusy + gpuWait; - } - else { - reinterpret_cast(pDestBlob[outputOffset_]) = 0.; - } - } - uint32_t GetBeginOffset() const override - { - return outputOffset_ - outputPaddingSize_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(double); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - uint16_t outputPaddingSize_; - }; - class ClickToPhotonGatherCommand_ : public pmon::mid::GatherCommand_ - { - public: - ClickToPhotonGatherCommand_(size_t nextAvailableByteOffset) - { - outputPaddingSize_ = (uint16_t)util::GetPadding(nextAvailableByteOffset, alignof(double)); - outputOffset_ = uint32_t(nextAvailableByteOffset) + outputPaddingSize_; - } - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - if (ctx.dropped) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - return; - } - uint64_t start = ctx.pSourceFrameData->present_event.InputTime; - if (start == 0ull) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - return; - } - if (ctx.sourceFrameDisplayIndex == ctx.appIndex) { - auto ScreenTime = ctx.pSourceFrameData->present_event.Displayed_ScreenTime[ctx.sourceFrameDisplayIndex]; - auto ii = ctx.frameTimingData.flipDelayDataMap.find(ctx.pSourceFrameData->present_event.FrameId); - if (ii != ctx.frameTimingData.flipDelayDataMap.end()) { - ScreenTime = ii->second.displayQpc; - } - const auto val = TimestampDeltaToUnsignedMilliSeconds(start, - ScreenTime, ctx.performanceCounterPeriodMs); - reinterpret_cast(pDestBlob[outputOffset_]) = val; - } - else - { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - } - } - uint32_t GetBeginOffset() const override - { - return outputOffset_ - outputPaddingSize_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(double); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - uint16_t outputPaddingSize_; - }; - class DroppedGatherCommand_ : public pmon::mid::GatherCommand_ - { - public: - DroppedGatherCommand_(size_t nextAvailableByteOffset) : outputOffset_{ (uint32_t)nextAvailableByteOffset } {} - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - reinterpret_cast(pDestBlob[outputOffset_]) = ctx.dropped; - } - uint32_t GetBeginOffset() const override - { - return outputOffset_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(bool); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - }; - template - class StartDifferenceGatherCommand_ : public pmon::mid::GatherCommand_ - { - public: - StartDifferenceGatherCommand_(size_t nextAvailableByteOffset) - { - outputPaddingSize_ = (uint16_t)util::GetPadding(nextAvailableByteOffset, alignof(double)); - outputOffset_ = uint32_t(nextAvailableByteOffset) + outputPaddingSize_; - } - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - if constexpr (calcPresentStartTime) { - const auto qpcDuration = ctx.pSourceFrameData->present_event.*pEnd - ctx.qpcStart; - const auto val = ctx.performanceCounterPeriodMs * double(qpcDuration); - reinterpret_cast(pDestBlob[outputOffset_]) = val; - } else { - const auto qpcDuration = ctx.cpuStart - ctx.qpcStart; - const auto val = ctx.performanceCounterPeriodMs * double(qpcDuration); - reinterpret_cast(pDestBlob[outputOffset_]) = val; - } - } - uint32_t GetBeginOffset() const override - { - return outputOffset_ - outputPaddingSize_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(double); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - uint16_t outputPaddingSize_; - }; - class CpuFrameQpcGatherCommand_ : public pmon::mid::GatherCommand_ - { - public: - CpuFrameQpcGatherCommand_(size_t nextAvailableByteOffset) : outputOffset_{ (uint32_t)nextAvailableByteOffset } {} - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - reinterpret_cast(pDestBlob[outputOffset_]) = ctx.cpuStart; - } - uint32_t GetBeginOffset() const override - { - return outputOffset_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(uint64_t); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - }; - template - class CpuFrameQpcDifferenceGatherCommand_ : public pmon::mid::GatherCommand_ - { - public: - CpuFrameQpcDifferenceGatherCommand_(size_t nextAvailableByteOffset) - { - outputPaddingSize_ = (uint16_t)util::GetPadding(nextAvailableByteOffset, alignof(double)); - outputOffset_ = uint32_t(nextAvailableByteOffset) + outputPaddingSize_; - } - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - if constexpr (doDroppedCheck) { - if (ctx.dropped) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - return; - } - } - if (ctx.sourceFrameDisplayIndex == ctx.appIndex) { - if (ctx.pSourceFrameData->present_event.*pPropagatedEnd != 0) { - const auto val = TimestampDeltaToUnsignedMilliSeconds(ctx.cpuStart, - ctx.pSourceFrameData->present_event.*pPropagatedEnd, ctx.performanceCounterPeriodMs); - reinterpret_cast(pDestBlob[outputOffset_]) = val; - } else { - const auto val = TimestampDeltaToUnsignedMilliSeconds(ctx.cpuStart, - ctx.pSourceFrameData->present_event.*pEnd, ctx.performanceCounterPeriodMs); - reinterpret_cast(pDestBlob[outputOffset_]) = val; - } - } - else{ - reinterpret_cast(pDestBlob[outputOffset_]) = 0.; - } - } - uint32_t GetBeginOffset() const override - { - return outputOffset_ - outputPaddingSize_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(double); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - uint16_t outputPaddingSize_; - }; - template - class DisplayLatencyGatherFromCommand_ : public pmon::mid::GatherCommand_ - { - public: - DisplayLatencyGatherFromCommand_(size_t nextAvailableByteOffset) - { - outputPaddingSize_ = (uint16_t)util::GetPadding(nextAvailableByteOffset, alignof(double)); - outputOffset_ = uint32_t(nextAvailableByteOffset) + outputPaddingSize_; - } - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - if (ctx.dropped) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - return; - } - - // Calculate the current frame's displayed time - auto ScreenTime = ctx.pSourceFrameData->present_event.Displayed_ScreenTime[ctx.sourceFrameDisplayIndex]; - auto ii = ctx.frameTimingData.flipDelayDataMap.find(ctx.pSourceFrameData->present_event.FrameId); - if (ii != ctx.frameTimingData.flipDelayDataMap.end()) { - ScreenTime = ii->second.displayQpc; - } - auto NextScreenTime = ctx.sourceFrameDisplayIndex == ctx.pSourceFrameData->present_event.DisplayedCount - 1 - ? ctx.nextDisplayedQpc - : ctx.pSourceFrameData->present_event.Displayed_ScreenTime[ctx.sourceFrameDisplayIndex + 1]; - const auto displayedTime = TimestampDeltaToUnsignedMilliSeconds(ScreenTime, NextScreenTime, ctx.performanceCounterPeriodMs); - - uint64_t startQpc = 0; - if (ctx.pSourceFrameData->present_event.*pFrom == 0 || - displayedTime == 0) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - return; - } - const auto val = TimestampDeltaToUnsignedMilliSeconds(ctx.pSourceFrameData->present_event.*pFrom, - ScreenTime, - ctx.performanceCounterPeriodMs); - reinterpret_cast(pDestBlob[outputOffset_]) = val; - return; - } - uint32_t GetBeginOffset() const override - { - return outputOffset_ - outputPaddingSize_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(double); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - uint16_t outputPaddingSize_; - }; - template - class DisplayLatencyGatherCommand_ : public pmon::mid::GatherCommand_ - { - public: - DisplayLatencyGatherCommand_(size_t nextAvailableByteOffset) - { - outputPaddingSize_ = (uint16_t)util::GetPadding(nextAvailableByteOffset, alignof(double)); - outputOffset_ = uint32_t(nextAvailableByteOffset) + outputPaddingSize_; - } - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - if (ctx.dropped) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - return; - } - - // Calculate the current frame's displayed time - auto ScreenTime = ctx.pSourceFrameData->present_event.Displayed_ScreenTime[ctx.sourceFrameDisplayIndex]; - auto ii = ctx.frameTimingData.flipDelayDataMap.find(ctx.pSourceFrameData->present_event.FrameId); - if (ii != ctx.frameTimingData.flipDelayDataMap.end()) { - ScreenTime = ii->second.displayQpc; - } - auto NextScreenTime = ctx.sourceFrameDisplayIndex == ctx.pSourceFrameData->present_event.DisplayedCount - 1 - ? ctx.nextDisplayedQpc - : ctx.pSourceFrameData->present_event.Displayed_ScreenTime[ctx.sourceFrameDisplayIndex + 1]; - const auto displayedTime = TimestampDeltaToUnsignedMilliSeconds(ScreenTime, NextScreenTime, ctx.performanceCounterPeriodMs); - - uint64_t startQpc = 0; - if constexpr (isXellDisplayLatency) { - if ((ctx.pSourceFrameData->present_event.AppSleepEndTime == 0 && - ctx.pSourceFrameData->present_event.AppSimStartTime == 0) || - displayedTime == 0) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - return; - } - const auto xellStartTime = ctx.pSourceFrameData->present_event.AppSleepEndTime != 0 ? - ctx.pSourceFrameData->present_event.AppSleepEndTime : - ctx.pSourceFrameData->present_event.AppSimStartTime; - const auto val = TimestampDeltaToUnsignedMilliSeconds(xellStartTime, - ScreenTime, - ctx.performanceCounterPeriodMs); - reinterpret_cast(pDestBlob[outputOffset_]) = val; - return; - } - else if constexpr (isBetweenDisplayChange) { - if (ctx.previousDisplayedQpc == 0) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - return; - } - const auto val = TimestampDeltaToUnsignedMilliSeconds(ctx.previousDisplayedQpc, ScreenTime, - ctx.performanceCounterPeriodMs); - reinterpret_cast(pDestBlob[outputOffset_]) = val; - return; - } else { - if (displayedTime == 0) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - return; - } - const auto val = TimestampDeltaToUnsignedMilliSeconds(ctx.cpuStart, ScreenTime, - ctx.performanceCounterPeriodMs); - reinterpret_cast(pDestBlob[outputOffset_]) = val; - return; - } - } - uint32_t GetBeginOffset() const override - { - return outputOffset_ - outputPaddingSize_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(double); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - uint16_t outputPaddingSize_; - }; - class ReturnNanGatherCommand_ : public pmon::mid::GatherCommand_ - { - public: - ReturnNanGatherCommand_(size_t nextAvailableByteOffset) - { - outputPaddingSize_ = (uint16_t)util::GetPadding(nextAvailableByteOffset, alignof(double)); - outputOffset_ = uint32_t(nextAvailableByteOffset) + outputPaddingSize_; - } - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - } - uint32_t GetBeginOffset() const override - { - return outputOffset_ - outputPaddingSize_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(double); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - uint16_t outputPaddingSize_; - }; - class DisplayDifferenceGatherCommand_ : public pmon::mid::GatherCommand_ - { - public: - DisplayDifferenceGatherCommand_(size_t nextAvailableByteOffset) - { - outputPaddingSize_ = (uint16_t)util::GetPadding(nextAvailableByteOffset, alignof(double)); - outputOffset_ = uint32_t(nextAvailableByteOffset) + outputPaddingSize_; - } - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - if (ctx.dropped) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - return; - } - auto ScreenTime = ctx.pSourceFrameData->present_event.Displayed_ScreenTime[ctx.sourceFrameDisplayIndex]; - auto ii = ctx.frameTimingData.flipDelayDataMap.find(ctx.pSourceFrameData->present_event.FrameId); - if (ii != ctx.frameTimingData.flipDelayDataMap.end()) { - ScreenTime = ii->second.displayQpc; - } - auto NextScreenTime = ctx.sourceFrameDisplayIndex == ctx.pSourceFrameData->present_event.DisplayedCount - 1 - ? ctx.nextDisplayedQpc - : ctx.pSourceFrameData->present_event.Displayed_ScreenTime[ctx.sourceFrameDisplayIndex + 1]; - const auto val = TimestampDeltaToUnsignedMilliSeconds(ScreenTime, NextScreenTime, ctx.performanceCounterPeriodMs); - if (val == 0.) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - } - else { - reinterpret_cast(pDestBlob[outputOffset_]) = val; - } - } - uint32_t GetBeginOffset() const override - { - return outputOffset_ - outputPaddingSize_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(double); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - uint16_t outputPaddingSize_; - }; - template - class AnimationErrorGatherCommand_ : public pmon::mid::GatherCommand_ - { - public: - AnimationErrorGatherCommand_(size_t nextAvailableByteOffset) - { - outputPaddingSize_ = (uint16_t) util::GetPadding(nextAvailableByteOffset, alignof(double)); - outputOffset_ = uint32_t(nextAvailableByteOffset) + outputPaddingSize_; - } - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - if constexpr (doDroppedCheck) { - if (ctx.dropped) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - return; - } - } - if constexpr (doZeroCheck) { - if (ctx.frameTimingData.lastDisplayedAppSimStartTime == 0 && ctx.lastDisplayedCpuStart == 0) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - return; - } - } - if (ctx.sourceFrameDisplayIndex == ctx.appIndex) { - auto ScreenTime = ctx.pSourceFrameData->present_event.Displayed_ScreenTime[ctx.sourceFrameDisplayIndex]; - auto ii = ctx.frameTimingData.flipDelayDataMap.find(ctx.pSourceFrameData->present_event.FrameId); - if (ii != ctx.frameTimingData.flipDelayDataMap.end()) { - ScreenTime = ii->second.displayQpc; - } - auto NextScreenTime = ctx.sourceFrameDisplayIndex == ctx.pSourceFrameData->present_event.DisplayedCount - 1 - ? ctx.nextDisplayedQpc - : ctx.pSourceFrameData->present_event.Displayed_ScreenTime[ctx.sourceFrameDisplayIndex + 1]; - const auto displayedTime = TimestampDeltaToUnsignedMilliSeconds(ScreenTime, NextScreenTime, ctx.performanceCounterPeriodMs); - if (displayedTime == 0.0) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - return; - } - auto PrevScreenTime = ctx.frameTimingData.lastDisplayedAppScreenTime; - // Next calculate the animation error. First calculate the simulation - // start time. Simulation start can be either an app provided sim start time via the provider or - // PCL stats or, if not present,the cpu start. - uint64_t simStartTime = 0; - if (ctx.frameTimingData.animationErrorSource == AnimationErrorSource::AppProvider && - ctx.frameTimingData.lastDisplayedAppSimStartTime != 0) { - // If the app provider is the source of the animation error then use the app sim start time. - simStartTime = ctx.pSourceFrameData->present_event.AppSimStartTime; } - else if (ctx.frameTimingData.animationErrorSource == AnimationErrorSource::PCLatency && - ctx.frameTimingData.lastDisplayedAppSimStartTime != 0) { - // If the pcl latency is the source of the animation error then use the pcl sim start time. - simStartTime = ctx.pSourceFrameData->present_event.PclSimStartTime; + return std::nullopt; + }(); + + if (!deviceMetricInfo.has_value() || !deviceMetricInfo->IsAvailable()) { + pmlog_error("Metric not supported by device in frame query") + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch(q.deviceId).diag(); + throw Except("Metric not supported by device in frame query"); + } + + const auto arraySize = deviceMetricInfo->GetArraySize(); + if (q.arrayIndex >= arraySize) { + pmlog_error("Frame query array index out of bounds") + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch(q.arrayIndex) + .pmwatch(arraySize).diag(); + throw Except("Frame query array index out of bounds"); + } + + const auto alignment = GetDataTypeAlignment(frameType); + blobCursor = util::PadToAlignment(blobCursor, alignment); + + GatherCommand_ cmd{}; + if (q.deviceId > 0 && q.deviceId <= ipc::kSystemDeviceId) { + const auto& teleMap = q.deviceId == ipc::kSystemDeviceId ? + comms_.GetSystemDataStore().telemetryData : + comms_.GetGpuDataStore(q.deviceId).telemetryData; + + if (teleMap.ArraySize(q.metric) == 0) { + pmlog_error("Telemetry ring missing for metric in frame query") + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch(q.deviceId).diag(); + throw Except("Telemetry ring missing for metric in frame query"); } - else if (ctx.frameTimingData.lastDisplayedAppSimStartTime == 0) { - // If the cpu start time is the source of the animation error then use the cpu start time. - simStartTime = ctx.cpuStart; - } - auto PrevSimStartTime = ctx.frameTimingData.lastDisplayedAppSimStartTime != 0 ? - ctx.frameTimingData.lastDisplayedAppSimStartTime : - ctx.lastDisplayedCpuStart; - // If the simulation start time is less than the last displated simulation start time it means - // we are transitioning to app provider events. - if (simStartTime > PrevSimStartTime) { - const auto val = TimestampDeltaToMilliSeconds(ScreenTime - PrevScreenTime, - simStartTime - PrevSimStartTime, ctx.performanceCounterPeriodMs); - reinterpret_cast(pDestBlob[outputOffset_]) = val; - } else { - reinterpret_cast(pDestBlob[outputOffset_]) = std::numeric_limits::quiet_NaN(); - return; - } - } - else { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - } - } - uint32_t GetBeginOffset() const override - { - return outputOffset_ - outputPaddingSize_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(double); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - uint16_t outputPaddingSize_; - }; - class AnimationTimeGatherCommand_ : public pmon::mid::GatherCommand_ - { - public: - AnimationTimeGatherCommand_(size_t nextAvailableByteOffset) - { - outputPaddingSize_ = (uint16_t)util::GetPadding(nextAvailableByteOffset, alignof(double)); - outputOffset_ = uint32_t(nextAvailableByteOffset) + outputPaddingSize_; - } - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - if (ctx.dropped) { - reinterpret_cast(pDestBlob[outputOffset_]) = std::numeric_limits::quiet_NaN(); - return; - } - // Calculate the current frame's displayed time - - // Check to see if the current present is collapsed and if so use the correct display qpc. - auto ScreenTime = ctx.pSourceFrameData->present_event.Displayed_ScreenTime[ctx.sourceFrameDisplayIndex]; - auto ii = ctx.frameTimingData.flipDelayDataMap.find(ctx.pSourceFrameData->present_event.FrameId); - if (ii != ctx.frameTimingData.flipDelayDataMap.end()) { - ScreenTime = ii->second.displayQpc; - } - auto NextScreenTime = ctx.sourceFrameDisplayIndex == ctx.pSourceFrameData->present_event.DisplayedCount - 1 - ? ctx.nextDisplayedQpc - : ctx.pSourceFrameData->present_event.Displayed_ScreenTime[ctx.sourceFrameDisplayIndex + 1]; - const auto displayedTime = TimestampDeltaToUnsignedMilliSeconds(ScreenTime, NextScreenTime, ctx.performanceCounterPeriodMs); - if (ctx.sourceFrameDisplayIndex != ctx.appIndex || - displayedTime == 0.0) { - reinterpret_cast(pDestBlob[outputOffset_]) = std::numeric_limits::quiet_NaN(); - return; - } - const auto firstSimStartTime = ctx.frameTimingData.firstAppSimStartTime != 0 ? - ctx.frameTimingData.firstAppSimStartTime : - ctx.qpcStart; - uint64_t currentSimTime = 0; - if (ctx.frameTimingData.animationErrorSource == AnimationErrorSource::AppProvider) { - if (ctx.frameTimingData.lastDisplayedAppSimStartTime != 0) { - // If the app provider is the source of the animation error then use the app sim start time. - currentSimTime = ctx.pSourceFrameData->present_event.AppSimStartTime; - } - else { - reinterpret_cast(pDestBlob[outputOffset_]) = 0.; - return; - } - } - else if (ctx.frameTimingData.animationErrorSource == AnimationErrorSource::PCLatency) { - if (ctx.frameTimingData.lastDisplayedAppSimStartTime != 0) { - // If the pcl latency is the source of the animation error then use the pcl sim start time. - currentSimTime = ctx.pSourceFrameData->present_event.PclSimStartTime; - } - else { - reinterpret_cast(pDestBlob[outputOffset_]) = 0.; - return; + cmd.metricId = q.metric; + cmd.gatherType = frameType; + cmd.blobOffset = static_cast(blobCursor); + cmd.frameMetricsOffset = 0u; + cmd.deviceId = q.deviceId; + cmd.arrayIdx = q.arrayIndex; + cmd.isOptional = false; + cmd.qpcToMs = 0.0; + } + else if (q.deviceId == ipc::kUniversalDeviceId) { + cmd = MapQueryElementToFrameGatherCommand_(q, blobCursor); + + if (cmd.gatherType == PM_DATA_TYPE_VOID) { + pmlog_error("Unsupported frame metric in frame query") + .pmwatch(metricView.Introspect().GetSymbol()).diag(); + throw Except("Unsupported frame metric in frame query"); } - } - else if (ctx.frameTimingData.lastDisplayedAppSimStartTime == 0) { - // If the cpu start time is the source of the animation error then use the cpu start time. - currentSimTime = ctx.cpuStart; - } - const auto val = TimestampDeltaToUnsignedMilliSeconds(firstSimStartTime, currentSimTime, ctx.performanceCounterPeriodMs); - reinterpret_cast(pDestBlob[outputOffset_]) = val; - } - uint32_t GetBeginOffset() const override - { - return outputOffset_ - outputPaddingSize_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(double); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - uint16_t outputPaddingSize_; - }; - class CpuFrameQpcFrameTimeCommand_ : public pmon::mid::GatherCommand_ - { - public: - CpuFrameQpcFrameTimeCommand_(size_t nextAvailableByteOffset) : outputOffset_{ (uint32_t)nextAvailableByteOffset } {} - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - if (ctx.sourceFrameDisplayIndex == ctx.appIndex) { - double cpuBusy = 0.; - if (ctx.pSourceFrameData->present_event.AppPropagatedPresentStartTime != 0) { - cpuBusy = TimestampDeltaToUnsignedMilliSeconds(ctx.cpuStart, ctx.pSourceFrameData->present_event.AppPropagatedPresentStartTime, - ctx.performanceCounterPeriodMs); - } - else { - cpuBusy = TimestampDeltaToUnsignedMilliSeconds(ctx.cpuStart, ctx.pSourceFrameData->present_event.PresentStartTime, - ctx.performanceCounterPeriodMs); - } - double cpuWait = 0.; - if (ctx.pSourceFrameData->present_event.AppPropagatedTimeInPresent != 0) { - cpuWait = TimestampDeltaToMilliSeconds(ctx.pSourceFrameData->present_event.AppPropagatedTimeInPresent, - ctx.performanceCounterPeriodMs); - } - else { - cpuWait = TimestampDeltaToMilliSeconds(ctx.pSourceFrameData->present_event.TimeInPresent, - ctx.performanceCounterPeriodMs); - } - reinterpret_cast(pDestBlob[outputOffset_]) = cpuBusy + cpuWait; - } - else { - reinterpret_cast(pDestBlob[outputOffset_]) = 0.; - } - } - uint32_t GetBeginOffset() const override - { - return outputOffset_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(uint64_t); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - }; - class GpuWaitGatherCommand_ : public pmon::mid::GatherCommand_ - { - public: - GpuWaitGatherCommand_(size_t nextAvailableByteOffset) : outputOffset_{ (uint32_t)nextAvailableByteOffset } {} - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - if (ctx.sourceFrameDisplayIndex == ctx.appIndex) { - double gpuDuration = 0.; - double gpuBusy = 0.; - if (ctx.pSourceFrameData->present_event.AppPropagatedGPUStartTime != 0) { - gpuDuration = TimestampDeltaToUnsignedMilliSeconds(ctx.pSourceFrameData->present_event.AppPropagatedGPUStartTime, - ctx.pSourceFrameData->present_event.AppPropagatedReadyTime, ctx.performanceCounterPeriodMs); - gpuBusy = TimestampDeltaToMilliSeconds(ctx.pSourceFrameData->present_event.AppPropagatedGPUDuration, - ctx.performanceCounterPeriodMs); - } - else { - gpuDuration = TimestampDeltaToUnsignedMilliSeconds(ctx.pSourceFrameData->present_event.GPUStartTime, - ctx.pSourceFrameData->present_event.ReadyTime, ctx.performanceCounterPeriodMs); - gpuBusy = TimestampDeltaToMilliSeconds(ctx.pSourceFrameData->present_event.GPUDuration, - ctx.performanceCounterPeriodMs); - } - const auto val = std::max(0., gpuDuration - gpuBusy); - reinterpret_cast(pDestBlob[outputOffset_]) = val; - } - else { - reinterpret_cast(pDestBlob[outputOffset_]) = 0.; - } - } - uint32_t GetBeginOffset() const override - { - return outputOffset_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(uint64_t); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - }; - template - class InputLatencyGatherCommand_ : public pmon::mid::GatherCommand_ - { - public: - InputLatencyGatherCommand_(size_t nextAvailableByteOffset) - { - outputPaddingSize_ = (uint16_t)util::GetPadding(nextAvailableByteOffset, alignof(double)); - outputOffset_ = uint32_t(nextAvailableByteOffset) + outputPaddingSize_; - } - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - if constexpr (doDroppedCheck) { - if (ctx.dropped) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - return; - } - } - // Check to see if the current present is collapsed and if so use the correct display qpc. - uint64_t displayQpc = ctx.pSourceFrameData->present_event.Displayed_ScreenTime[ctx.sourceFrameDisplayIndex]; - auto ii = ctx.frameTimingData.flipDelayDataMap.find(ctx.pSourceFrameData->present_event.FrameId); - if (ii != ctx.frameTimingData.flipDelayDataMap.end()) { - displayQpc = ii->second.displayQpc; - } - - double updatedInputTime = 0.; - double val = 0.; - if (ctx.sourceFrameDisplayIndex == ctx.appIndex) { - if (isMouseClick) { - updatedInputTime = ctx.lastReceivedNotDisplayedClickQpc == 0 ? 0. : - TimestampDeltaToUnsignedMilliSeconds(ctx.lastReceivedNotDisplayedClickQpc, - displayQpc, - ctx.performanceCounterPeriodMs); - val = ctx.pSourceFrameData->present_event.*pStart == 0 ? updatedInputTime : - TimestampDeltaToUnsignedMilliSeconds(ctx.pSourceFrameData->present_event.*pStart, - displayQpc, - ctx.performanceCounterPeriodMs); - ctx.lastReceivedNotDisplayedClickQpc = 0; - } - else { - updatedInputTime = ctx.lastReceivedNotDisplayedAllInputTime == 0 ? 0. : - TimestampDeltaToUnsignedMilliSeconds(ctx.lastReceivedNotDisplayedAllInputTime, - displayQpc, - ctx.performanceCounterPeriodMs); - val = ctx.pSourceFrameData->present_event.*pStart == 0 ? updatedInputTime : - TimestampDeltaToUnsignedMilliSeconds(ctx.pSourceFrameData->present_event.*pStart, - displayQpc, - ctx.performanceCounterPeriodMs); - ctx.lastReceivedNotDisplayedAllInputTime = 0; + const auto gatherTypeSize = ipc::intro::GetDataTypeSize(cmd.gatherType); + if (gatherTypeSize != frameTypeSize) { + pmlog_error("Frame query type mismatch") + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch(gatherTypeSize) + .pmwatch(frameTypeSize).diag(); + throw Except("Frame query type mismatch"); } } - - if (val == 0.) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - } else { - reinterpret_cast(pDestBlob[outputOffset_]) = val; - } - } - uint32_t GetBeginOffset() const override - { - return outputOffset_ - outputPaddingSize_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(double); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - uint16_t outputPaddingSize_; - }; - class PcLatencyGatherCommand_ : public pmon::mid::GatherCommand_ - { - public: - PcLatencyGatherCommand_(size_t nextAvailableByteOffset) - { - outputPaddingSize_ = (uint16_t)util::GetPadding(nextAvailableByteOffset, alignof(double)); - outputOffset_ = uint32_t(nextAvailableByteOffset) + outputPaddingSize_; - } - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - if (ctx.dropped) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - return; - } - - double val = 0.; - auto simStartTime = ctx.pSourceFrameData->present_event.PclSimStartTime != 0 ? - ctx.pSourceFrameData->present_event.PclSimStartTime : - ctx.frameTimingData.lastAppSimStartTime; - if (ctx.avgInput2Fs == 0. || simStartTime == 0) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - return; - } - - // Check to see if the current present is collapsed and if so use the correct display qpc. - uint64_t displayQpc = ctx.pSourceFrameData->present_event.Displayed_ScreenTime[ctx.sourceFrameDisplayIndex]; - auto ii = ctx.frameTimingData.flipDelayDataMap.find(ctx.pSourceFrameData->present_event.FrameId); - if (ii != ctx.frameTimingData.flipDelayDataMap.end()) { - displayQpc = ii->second.displayQpc; + pmlog_error("Invalid device id in frame query") + .pmwatch(q.deviceId).diag(); + throw Except("Invalid device id in frame query"); } - val = ctx.avgInput2Fs + - TimestampDeltaToUnsignedMilliSeconds(simStartTime, - displayQpc, - ctx.performanceCounterPeriodMs); + q.dataOffset = blobCursor; + q.dataSize = frameTypeSize; + blobCursor += frameTypeSize; - if (val == 0.) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - } - else { - reinterpret_cast(pDestBlob[outputOffset_]) = val; - } - } - uint32_t GetBeginOffset() const override - { - return outputOffset_ - outputPaddingSize_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(double); + gatherCommands_.push_back(cmd); } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - uint16_t outputPaddingSize_; - }; - class BetweenSimStartsGatherCommand_ : public pmon::mid::GatherCommand_ - { - public: - BetweenSimStartsGatherCommand_(size_t nextAvailableByteOffset) - { - outputPaddingSize_ = (uint16_t)util::GetPadding(nextAvailableByteOffset, alignof(double)); - outputOffset_ = uint32_t(nextAvailableByteOffset) + outputPaddingSize_; - } - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - double val = 0.; - uint64_t currentSimStartTime = 0; - if (ctx.pSourceFrameData->present_event.PclSimStartTime != 0) { - currentSimStartTime = ctx.pSourceFrameData->present_event.PclSimStartTime; - } else if (ctx.pSourceFrameData->present_event.AppSimStartTime != 0) { - currentSimStartTime = ctx.pSourceFrameData->present_event.AppSimStartTime; - } - - if (ctx.frameTimingData.lastAppSimStartTime == 0 || currentSimStartTime == 0) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - return; - } + // make sure blob size is a multiple of 16 so that blobs in array always start 16-aligned + blobSize_ = util::PadToAlignment(blobCursor, 16u); + } - val = TimestampDeltaToUnsignedMilliSeconds( - ctx.frameTimingData.lastAppSimStartTime, currentSimStartTime, - ctx.performanceCounterPeriodMs); + PM_FRAME_QUERY::~PM_FRAME_QUERY() = default; - if (val == 0.) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - } - else { - reinterpret_cast(pDestBlob[outputOffset_]) = val; - } - } - uint32_t GetBeginOffset() const override - { - return outputOffset_ - outputPaddingSize_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(double); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - uint16_t outputPaddingSize_; - }; - class FlipDelayGatherCommand_ : public pmon::mid::GatherCommand_ + void PM_FRAME_QUERY::GatherToBlob(uint8_t* pBlobBytes, const util::metrics::FrameMetrics& frameMetrics) const { - public: - FlipDelayGatherCommand_(size_t nextAvailableByteOffset) - { - outputPaddingSize_ = (uint16_t)util::GetPadding(nextAvailableByteOffset, alignof(double)); - outputOffset_ = uint32_t(nextAvailableByteOffset) + outputPaddingSize_; - } - void Gather(Context& ctx, uint8_t* pDestBlob) const override - { - - if (ctx.dropped) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); - return; + for (auto& cmd : gatherCommands_) { + if (cmd.deviceId == ipc::kUniversalDeviceId) { + GatherFromFrameMetrics_(cmd, pBlobBytes, frameMetrics); } - - // Calculate the current frame's flip delay - uint64_t flipDelay = ctx.pSourceFrameData->present_event.FlipDelay; - auto ii = ctx.frameTimingData.flipDelayDataMap.find(ctx.pSourceFrameData->present_event.FrameId); - if (ii != ctx.frameTimingData.flipDelayDataMap.end()) { - flipDelay = ii->second.flipDelay; + else if (cmd.deviceId == ipc::kSystemDeviceId) { + GatherFromTelemetry_(cmd, pBlobBytes, frameMetrics.cpuStartQpc, comms_.GetSystemDataStore().telemetryData); } - - double val = 0.; - val = TimestampDeltaToMilliSeconds(flipDelay, ctx.performanceCounterPeriodMs); - - if (val == 0.) { - reinterpret_cast(pDestBlob[outputOffset_]) = - std::numeric_limits::quiet_NaN(); + else if (cmd.deviceId < ipc::kSystemDeviceId) { + GatherFromTelemetry_(cmd, pBlobBytes, frameMetrics.cpuStartQpc, comms_.GetGpuDataStore(cmd.deviceId).telemetryData); } else { - reinterpret_cast(pDestBlob[outputOffset_]) = val; + pmlog_error("Bad device ID").pmwatch(cmd.deviceId); } } - uint32_t GetBeginOffset() const override - { - return outputOffset_ - outputPaddingSize_; - } - uint32_t GetEndOffset() const override - { - return outputOffset_ + alignof(double); - } - uint32_t GetOutputOffset() const override - { - return outputOffset_; - } - private: - uint32_t outputOffset_; - uint16_t outputPaddingSize_; - }; -} - -PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements) -{ - // TODO: validation - // only allow array index zero if not array type in nsm - // fail if array index out of bounds - // fail if any metrics aren't event-compatible - // fail if any stats other than NONE are specified - // fail if intro size doesn't match nsm size - - // we need to keep track of how many non-universal devices are specified - // current release: only 1 gpu device maybe be polled at a time - for (auto& q : queryElements) { - // validate that maximum 1 device (gpu) id is specified throughout the query - // universal (0) and system (kSystemDeviceId) device metrics are exempt from this limit - if (q.deviceId != 0 && q.deviceId != ipc::kSystemDeviceId) { - if (!referencedDevice_) { - referencedDevice_ = q.deviceId; - } - else if (*referencedDevice_ != q.deviceId) { - pmlog_error("Cannot specify 2 different non-universal devices in the same query") - .pmwatch(*referencedDevice_).pmwatch(q.deviceId).diag(); - throw Except("2 different non-universal devices in same query"); - } - } - if (auto pElement = MapQueryElementToGatherCommand_(q, blobSize_)) { - gatherCommands_.push_back(std::move(pElement)); - const auto& cmd = gatherCommands_.back(); - q.dataSize = cmd->GetDataSize(); - q.dataOffset = cmd->GetOutputOffset(); - blobSize_ += cmd->GetTotalSize(); - } } - // make sure blobs are a multiple of 16 so that blobs in array always start 16-aligned - blobSize_ += util::GetPadding(blobSize_, 16); -} - -PM_FRAME_QUERY::~PM_FRAME_QUERY() = default; -void PM_FRAME_QUERY::GatherToBlob(Context& ctx, uint8_t* pDestBlob) const -{ - for (auto& cmd : gatherCommands_) { - cmd->Gather(ctx, pDestBlob); + size_t PM_FRAME_QUERY::GetBlobSize() const + { + return blobSize_; } -} -size_t PM_FRAME_QUERY::GetBlobSize() const -{ - return blobSize_; -} - -std::optional PM_FRAME_QUERY::GetReferencedDevice() const -{ - return referencedDevice_; -} - -std::unique_ptr PM_FRAME_QUERY::MapQueryElementToGatherCommand_(const PM_QUERY_ELEMENT& q, size_t pos) -{ - using Pre = PmNsmPresentEvent; - using Gpu = PresentMonPowerTelemetryInfo; - using Cpu = CpuTelemetryInfo; - - switch (q.metric) { - // temporary static metric lookup via nsm - // only implementing the ones used by appcef right now... others available in the future - // TODO: implement fill for all static OR drop support for filling static - case PM_METRIC_APPLICATION: - return std::make_unique>(pos); - case PM_METRIC_GPU_MEM_SIZE: - return std::make_unique>(pos); - case PM_METRIC_GPU_MEM_MAX_BANDWIDTH: - return std::make_unique>(pos); - - case PM_METRIC_SWAP_CHAIN_ADDRESS: - return std::make_unique>(pos); - case PM_METRIC_GPU_BUSY: - return std::make_unique>(pos); - case PM_METRIC_DROPPED_FRAMES: - return std::make_unique(pos); - case PM_METRIC_PRESENT_MODE: - return std::make_unique>(pos); - case PM_METRIC_PRESENT_RUNTIME: - return std::make_unique>(pos); - case PM_METRIC_CPU_START_QPC: - return std::make_unique(pos); - case PM_METRIC_ALLOWS_TEARING: - return std::make_unique>(pos); - case PM_METRIC_FRAME_TYPE: - return std::make_unique(pos); - case PM_METRIC_SYNC_INTERVAL: - return std::make_unique>(pos); - - case PM_METRIC_GPU_POWER: - return std::make_unique>(pos); - case PM_METRIC_GPU_VOLTAGE: - return std::make_unique>(pos); - case PM_METRIC_GPU_FREQUENCY: - return std::make_unique>(pos); - case PM_METRIC_GPU_TEMPERATURE: - return std::make_unique>(pos); - case PM_METRIC_GPU_FAN_SPEED: - return std::make_unique>(pos, q.arrayIndex); - case PM_METRIC_GPU_UTILIZATION: - return std::make_unique>(pos); - case PM_METRIC_GPU_RENDER_COMPUTE_UTILIZATION: - return std::make_unique>(pos); - case PM_METRIC_GPU_MEDIA_UTILIZATION: - return std::make_unique>(pos); - case PM_METRIC_GPU_MEM_POWER: - return std::make_unique>(pos); - case PM_METRIC_GPU_MEM_VOLTAGE: - return std::make_unique>(pos); - case PM_METRIC_GPU_MEM_FREQUENCY: - return std::make_unique>(pos); - case PM_METRIC_GPU_MEM_EFFECTIVE_FREQUENCY: - return std::make_unique>(pos); - case PM_METRIC_GPU_MEM_TEMPERATURE: - return std::make_unique>(pos); - case PM_METRIC_GPU_MEM_USED: - return std::make_unique>(pos); - case PM_METRIC_GPU_MEM_WRITE_BANDWIDTH: - return std::make_unique>(pos); - case PM_METRIC_GPU_MEM_READ_BANDWIDTH: - return std::make_unique>(pos); - case PM_METRIC_GPU_POWER_LIMITED: - return std::make_unique>(pos); - case PM_METRIC_GPU_TEMPERATURE_LIMITED: - return std::make_unique>(pos); - case PM_METRIC_GPU_CURRENT_LIMITED: - return std::make_unique>(pos); - case PM_METRIC_GPU_VOLTAGE_LIMITED: - return std::make_unique>(pos); - case PM_METRIC_GPU_UTILIZATION_LIMITED: - return std::make_unique>(pos); - case PM_METRIC_GPU_MEM_POWER_LIMITED: - return std::make_unique>(pos); - case PM_METRIC_GPU_MEM_TEMPERATURE_LIMITED: - return std::make_unique>(pos); - case PM_METRIC_GPU_MEM_CURRENT_LIMITED: - return std::make_unique>(pos); - case PM_METRIC_GPU_MEM_VOLTAGE_LIMITED: - return std::make_unique>(pos); - case PM_METRIC_GPU_MEM_UTILIZATION_LIMITED: - return std::make_unique>(pos); - - case PM_METRIC_CPU_UTILIZATION: - return std::make_unique>(pos); - case PM_METRIC_CPU_POWER: - return std::make_unique>(pos); - case PM_METRIC_CPU_TEMPERATURE: - return std::make_unique>(pos); - case PM_METRIC_CPU_FREQUENCY: - return std::make_unique>(pos); - - case PM_METRIC_PRESENT_FLAGS: - return std::make_unique>(pos); - case PM_METRIC_CPU_START_TIME: - return std::make_unique>(pos); - case PM_METRIC_CPU_FRAME_TIME: - case PM_METRIC_BETWEEN_APP_START: - return std::make_unique(pos); - case PM_METRIC_CPU_BUSY: - return std::make_unique>(pos); - case PM_METRIC_CPU_WAIT: - return std::make_unique>(pos); - case PM_METRIC_GPU_TIME: - return std::make_unique(pos); - case PM_METRIC_GPU_WAIT: - return std::make_unique(pos); - case PM_METRIC_DISPLAYED_TIME: - return std::make_unique(pos); - case PM_METRIC_ANIMATION_ERROR: - return std::make_unique>(pos); - case PM_METRIC_ANIMATION_TIME: - return std::make_unique(pos); - case PM_METRIC_GPU_LATENCY: - return std::make_unique>(pos); - case PM_METRIC_DISPLAY_LATENCY: - return std::make_unique>(pos); - case PM_METRIC_CLICK_TO_PHOTON_LATENCY: - return std::make_unique>(pos); - case PM_METRIC_ALL_INPUT_TO_PHOTON_LATENCY: - return std::make_unique>(pos); - case PM_METRIC_INSTRUMENTED_LATENCY: - return std::make_unique>(pos); - case PM_METRIC_PRESENT_START_TIME: - return std::make_unique>(pos); - case PM_METRIC_PRESENT_START_QPC: - return std::make_unique>(pos); - case PM_METRIC_IN_PRESENT_API: - return std::make_unique>(pos); - case PM_METRIC_UNTIL_DISPLAYED: - return std::make_unique>(pos); - case PM_METRIC_BETWEEN_DISPLAY_CHANGE: - return std::make_unique>(pos); - case PM_METRIC_BETWEEN_PRESENTS: - return std::make_unique>(pos); - case PM_METRIC_RENDER_PRESENT_LATENCY: - return std::make_unique>(pos); - case PM_METRIC_BETWEEN_SIMULATION_START: - return std::make_unique(pos); - case PM_METRIC_PC_LATENCY: - return std::make_unique(pos); - case PM_METRIC_FLIP_DELAY: - return std::make_unique(pos); - default: - pmlog_error("unknown metric id").pmwatch((int)q.metric).diag(); - return {}; + PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherCommand_(const PM_QUERY_ELEMENT& q, size_t blobByteCursor) + { + const double qpcToMs = util::GetTimestampPeriodSeconds() * 1000.0; + + GatherCommand_ cmd{}; + cmd.metricId = q.metric; + cmd.gatherType = PM_DATA_TYPE_VOID; + cmd.blobOffset = static_cast(blobByteCursor); + cmd.frameMetricsOffset = 0u; + cmd.deviceId = q.deviceId; + cmd.arrayIdx = q.arrayIndex; + cmd.isOptional = false; + cmd.qpcToMs = 0.0; + + switch (q.metric) { + case PM_METRIC_PRESENT_START_QPC: + cmd.gatherType = PM_DATA_TYPE_UINT64; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, timeInSeconds); + break; + case PM_METRIC_PRESENT_START_TIME: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, timeInSeconds); + cmd.qpcToMs = qpcToMs; + break; + case PM_METRIC_CPU_START_QPC: + cmd.gatherType = PM_DATA_TYPE_UINT64; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, cpuStartQpc); + break; + case PM_METRIC_CPU_START_TIME: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, cpuStartQpc); + cmd.qpcToMs = qpcToMs; + break; + case PM_METRIC_BETWEEN_PRESENTS: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msBetweenPresents); + break; + case PM_METRIC_IN_PRESENT_API: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msInPresentApi); + break; + case PM_METRIC_RENDER_PRESENT_LATENCY: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msUntilRenderComplete); + break; + case PM_METRIC_BETWEEN_DISPLAY_CHANGE: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msBetweenDisplayChange); + break; + case PM_METRIC_UNTIL_DISPLAYED: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msUntilDisplayed); + break; + case PM_METRIC_DISPLAYED_TIME: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msDisplayedTime); + break; + case PM_METRIC_DISPLAY_LATENCY: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msDisplayLatency); + break; + case PM_METRIC_BETWEEN_SIMULATION_START: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msBetweenSimStarts); + cmd.isOptional = true; + break; + case PM_METRIC_PC_LATENCY: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msPcLatency); + cmd.isOptional = true; + break; + case PM_METRIC_CPU_BUSY: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msCPUBusy); + break; + case PM_METRIC_CPU_WAIT: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msCPUWait); + break; + case PM_METRIC_CPU_FRAME_TIME: + case PM_METRIC_BETWEEN_APP_START: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + break; + case PM_METRIC_GPU_LATENCY: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msGPULatency); + break; + case PM_METRIC_GPU_TIME: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + break; + case PM_METRIC_GPU_BUSY: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msGPUBusy); + break; + case PM_METRIC_GPU_WAIT: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msGPUWait); + break; + case PM_METRIC_DROPPED_FRAMES: + cmd.gatherType = PM_DATA_TYPE_BOOL; + break; + case PM_METRIC_ANIMATION_ERROR: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msAnimationError); + cmd.isOptional = true; + break; + case PM_METRIC_ANIMATION_TIME: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msAnimationTime); + cmd.isOptional = true; + break; + case PM_METRIC_CLICK_TO_PHOTON_LATENCY: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msClickToPhotonLatency); + cmd.isOptional = true; + break; + case PM_METRIC_ALL_INPUT_TO_PHOTON_LATENCY: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msAllInputPhotonLatency); + cmd.isOptional = true; + break; + case PM_METRIC_INSTRUMENTED_LATENCY: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msInstrumentedLatency); + cmd.isOptional = true; + break; + case PM_METRIC_FLIP_DELAY: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msFlipDelay); + cmd.isOptional = true; + break; + case PM_METRIC_FRAME_TYPE: + cmd.gatherType = PM_DATA_TYPE_ENUM; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, frameType); + break; + default: + pmlog_error("unknown metric id").pmwatch((int)q.metric).diag(); + return {}; + } + return cmd; } -} -void PM_FRAME_QUERY::Context::UpdateSourceData(const PmNsmFrameData* pSourceFrameData_in, - const PmNsmFrameData* pFrameDataOfNextDisplayed, - const PmNsmFrameData* pFrameDataOfLastPresented, - const PmNsmFrameData* pFrameDataOfLastAppPresented, - const PmNsmFrameData* pFrameDataOfLastDisplayed, - const PmNsmFrameData* pFrameDataOfLastAppDisplayed, - const PmNsmFrameData* pFrameDataOfPreviousAppFrameOfLastAppDisplayed) -{ - pSourceFrameData = pSourceFrameData_in; - sourceFrameDisplayIndex = 0; - dropped = pSourceFrameData->present_event.FinalState != PresentResult::Presented || pSourceFrameData->present_event.DisplayedCount == 0; - if (dropped) { - if (pSourceFrameData->present_event.MouseClickTime != 0) { - lastReceivedNotDisplayedClickQpc = pSourceFrameData->present_event.MouseClickTime; - } - if (pSourceFrameData->present_event.InputTime != 0) { - lastReceivedNotDisplayedAllInputTime = pSourceFrameData->present_event.InputTime; - } - if (pSourceFrameData->present_event.PclSimStartTime != 0) { - if (pSourceFrameData->present_event.PclInputPingTime != 0) { - // This frame was dropped but we have valid pc latency input and simulation start - // times. Calculate the initial input to sim start time. - mAccumulatedInput2FrameStartTime = - TimestampDeltaToUnsignedMilliSeconds( - pSourceFrameData->present_event.PclInputPingTime, - pSourceFrameData->present_event.PclSimStartTime, - performanceCounterPeriodMs); - mLastReceivedNotDisplayedPclInputTime = pSourceFrameData->present_event.PclInputPingTime; - } - else if (mAccumulatedInput2FrameStartTime != 0.f) { - // This frame was also dropped and there is no pc latency input time. However, since we have - // accumulated time this means we have a pending input that has had multiple dropped frames - // and has not yet hit the screen. Calculate the time between the last not displayed sim start and - // this sim start and add it to our accumulated total - mAccumulatedInput2FrameStartTime += - TimestampDeltaToUnsignedMilliSeconds( - mLastReceivedNotDisplayedPclSimStart, - pSourceFrameData->present_event.PclSimStartTime, - performanceCounterPeriodMs); - } - mLastReceivedNotDisplayedPclSimStart = pSourceFrameData->present_event.PclSimStartTime; + void PM_FRAME_QUERY::GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* pBlobBytes, const util::metrics::FrameMetrics& frameMetrics) + { + const auto pFrameMemberBytes = reinterpret_cast(&frameMetrics) + cmd.frameMetricsOffset; + if (cmd.metricId == PM_METRIC_CPU_FRAME_TIME || cmd.metricId == PM_METRIC_BETWEEN_APP_START) { + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + frameMetrics.msCPUBusy + frameMetrics.msCPUWait; + return; } - } - - AnimationErrorSource initAmimationErrorSource = frameTimingData.animationErrorSource; - if (frameTimingData.firstAppSimStartTime == 0) { - // Handle this rare case at the start of consuming frames and the application is - // producing app frame data: - // [0] Frame - Not Dropped - Valid App Data -> Start of raw frame data - // [1] Frame - Dropped | Not Dropped - Valid App Data - // [2] Frame - Dropped | Not Dropped - Valid App Data - // When we start processing the NSM at [0] we will not have a last presented frame data - // and therefore not enter this function to update source the data. However the correct - // app sim start time is at [0] regardless if [1] or [2] are dropped or not - if (pFrameDataOfLastDisplayed) { - if (pFrameDataOfLastDisplayed->present_event.AppSimStartTime != 0) { - frameTimingData.firstAppSimStartTime = pFrameDataOfLastDisplayed->present_event.AppSimStartTime; - frameTimingData.animationErrorSource = AnimationErrorSource::AppProvider; - } else if (pFrameDataOfLastDisplayed->present_event.PclSimStartTime != 0){ - frameTimingData.firstAppSimStartTime = pFrameDataOfLastDisplayed->present_event.PclSimStartTime; - frameTimingData.animationErrorSource = AnimationErrorSource::PCLatency; - } + if (cmd.metricId == PM_METRIC_GPU_TIME) { + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + frameMetrics.msGPUBusy + frameMetrics.msGPUWait; + return; } - // In the case where [0] does not set the firstAppSimStartTime and the current frame is - // either [1] or [2] and is not dropped set the firstAppSimStartTime - if (frameTimingData.firstAppSimStartTime == 0 && !dropped) { - if (pSourceFrameData->present_event.AppSimStartTime != 0) { - frameTimingData.firstAppSimStartTime = pSourceFrameData->present_event.AppSimStartTime; - frameTimingData.animationErrorSource = AnimationErrorSource::AppProvider; - } - else if (pSourceFrameData->present_event.PclSimStartTime != 0) { - frameTimingData.firstAppSimStartTime = pSourceFrameData->present_event.PclSimStartTime; - frameTimingData.animationErrorSource = AnimationErrorSource::PCLatency; - } + if (cmd.metricId == PM_METRIC_DROPPED_FRAMES) { + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + frameMetrics.screenTimeQpc == 0; + return; } - } - - if (pFrameDataOfLastPresented) { - previousPresentStartQpc = pFrameDataOfLastPresented->present_event.PresentStartTime; - } else { - // TODO: log issue or invalidate related columns or drop frame (or some combination) - pmlog_info("null pFrameDataOfLastPresented"); - previousPresentStartQpc = 0; - } - - if (pFrameDataOfLastAppPresented) { - if (pFrameDataOfLastAppPresented->present_event.AppPropagatedPresentStartTime != 0) { - cpuStart = pFrameDataOfLastAppPresented->present_event.AppPropagatedPresentStartTime + - pFrameDataOfLastAppPresented->present_event.AppPropagatedTimeInPresent; - } else { - cpuStart = pFrameDataOfLastAppPresented->present_event.PresentStartTime + - pFrameDataOfLastAppPresented->present_event.TimeInPresent; + if (cmd.metricId == PM_METRIC_PRESENT_START_TIME || cmd.metricId == PM_METRIC_CPU_START_TIME) { + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + static_cast(*reinterpret_cast(pFrameMemberBytes)) * cmd.qpcToMs; + return; } - if (pFrameDataOfLastPresented && pFrameDataOfLastPresented->present_event.PclSimStartTime != 0) { - frameTimingData.lastAppSimStartTime = pFrameDataOfLastAppPresented->present_event.PclSimStartTime; - } else if (pFrameDataOfLastPresented && pFrameDataOfLastPresented->present_event.AppSimStartTime != 0) { - frameTimingData.lastAppSimStartTime = pFrameDataOfLastAppPresented->present_event.AppSimStartTime; + if (frameMetrics.screenTimeQpc == 0 && + (cmd.metricId == PM_METRIC_DISPLAYED_TIME || + cmd.metricId == PM_METRIC_DISPLAY_LATENCY || + cmd.metricId == PM_METRIC_UNTIL_DISPLAYED || + cmd.metricId == PM_METRIC_BETWEEN_DISPLAY_CHANGE)) { + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + std::numeric_limits::quiet_NaN(); + return; } - } - else { - // TODO: log issue or invalidate related columns or drop frame (or some combination) - pmlog_info("null pFrameDataOfLastAppPresented"); - cpuStart = 0; - } - - if (pFrameDataOfNextDisplayed) { - if (!dropped) { - uint64_t flipDelay = 0; - uint64_t currentDisplayQpc = 0; - uint64_t nextDisplayQpc = 0; - // Check to see if the current frame had it's flip delay and display qpc set due to a - // collapsed present. If so use those values to calculate the next displayed qpc. - auto ii = frameTimingData.flipDelayDataMap.find(pSourceFrameData->present_event.FrameId); - if (ii != frameTimingData.flipDelayDataMap.end()) { - flipDelay = ii->second.flipDelay; - currentDisplayQpc = ii->second.displayQpc; - } else { - flipDelay = pSourceFrameData->present_event.FlipDelay; - currentDisplayQpc = pSourceFrameData->present_event.Displayed_ScreenTime[0]; - } - if (flipDelay != 0 && (currentDisplayQpc > pFrameDataOfNextDisplayed->present_event.Displayed_ScreenTime[0])) { - // The next displayed frame is a collapsed present. Update the flip delay data map with the - // current frame's flip delay and display qpc. - FlipDelayData flipDelayData {}; - flipDelayData.flipDelay = pFrameDataOfNextDisplayed->present_event.FlipDelay + - (currentDisplayQpc - pFrameDataOfNextDisplayed->present_event.Displayed_ScreenTime[0]); - flipDelayData.displayQpc = pSourceFrameData->present_event.Displayed_ScreenTime[0]; - nextDisplayedQpc = currentDisplayQpc; - frameTimingData.flipDelayDataMap[pFrameDataOfNextDisplayed->present_event.FrameId] = flipDelayData; - } else { - nextDisplayedQpc = pFrameDataOfNextDisplayed->present_event.Displayed_ScreenTime[0]; + switch (cmd.gatherType) { + case PM_DATA_TYPE_UINT64: + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + *reinterpret_cast(pFrameMemberBytes); + break; + case PM_DATA_TYPE_DOUBLE: + { + auto& blobDouble = *reinterpret_cast(pBlobBytes + cmd.blobOffset); + if (!cmd.isOptional) { + blobDouble = *reinterpret_cast(pFrameMemberBytes); } - } else { - nextDisplayedQpc = pFrameDataOfNextDisplayed->present_event.Displayed_ScreenTime[0]; - } - } - else { - // TODO: log issue or invalidate related columns or drop frame (or some combination) - pmlog_info("null pFrameDataOfNextDisplayed"); - nextDisplayedQpc = 0; - } - - if (pFrameDataOfLastDisplayed && pFrameDataOfLastDisplayed->present_event.DisplayedCount > 0) { - // Check to see if the current frame had it's flip delay and display qpc set due to a - // collapsed present. If so use those values to calculate the next displayed qpc. - auto ii = frameTimingData.flipDelayDataMap.find(pFrameDataOfLastDisplayed->present_event.FrameId); - if (ii != frameTimingData.flipDelayDataMap.end()) { - previousDisplayedQpc = ii->second.displayQpc; - } - else { - previousDisplayedQpc = pFrameDataOfLastDisplayed->present_event.Displayed_ScreenTime[pFrameDataOfLastDisplayed->present_event.DisplayedCount - 1]; - } - // Save off the frame id of the last displayed frame to allow for deletion of old flip delay data - frameTimingData.lastDisplayedFrameId = pFrameDataOfLastDisplayed->present_event.FrameId; - } else { - // TODO: log issue or invalidate related columns or drop frame (or some combination) - pmlog_info("null pFrameDataOfLastDisplayed"); - previousDisplayedQpc = 0; - } - - if (pFrameDataOfLastAppDisplayed && pFrameDataOfLastAppDisplayed->present_event.DisplayedCount > 0) { - // Use the animation error source to set the various last displayed sim start and app screen times - uint64_t displayQpc = 0; - // Check to see if the current frame had it's flip delay and display qpc set due to a - // collapsed present. If so use those values to calculate the next displayed qpc. - auto ii = frameTimingData.flipDelayDataMap.find(pFrameDataOfLastAppDisplayed->present_event.FrameId); - if (ii != frameTimingData.flipDelayDataMap.end()) { - displayQpc = ii->second.displayQpc; - } - else { - displayQpc = pFrameDataOfLastAppDisplayed->present_event.Displayed_ScreenTime[pFrameDataOfLastAppDisplayed->present_event.DisplayedCount - 1]; - } - if (frameTimingData.animationErrorSource == AnimationErrorSource::PCLatency) { - // Special handling is required for application data provided by PCL events. In the PCL events - // case we use a non-zero PclSimStartTime as an indicator for an application generated frame. - if (pFrameDataOfLastAppDisplayed->present_event.PclSimStartTime != 0) { - frameTimingData.lastDisplayedAppSimStartTime = pFrameDataOfLastAppDisplayed->present_event.PclSimStartTime; - - frameTimingData.lastDisplayedAppScreenTime = displayQpc; - } else { - // If we are transistioning from CPU Start time based animation error source to PCL then we need to update the last displayed app screen time - // for the animation error. - if (initAmimationErrorSource != frameTimingData.animationErrorSource) { - frameTimingData.lastDisplayedAppScreenTime = displayQpc; + else { + auto& optDouble = *reinterpret_cast*>(pFrameMemberBytes); + if (optDouble) { + blobDouble = *optDouble; + } + else { + blobDouble = std::numeric_limits::quiet_NaN(); } } - } else if (frameTimingData.animationErrorSource == AnimationErrorSource::AppProvider) { - // The above transition check in the PCL case is not needed for the AppProvider case as with the app provided - // we know which frames are applicatoin generated. - frameTimingData.lastDisplayedAppSimStartTime = pFrameDataOfLastAppDisplayed->present_event.AppSimStartTime; - frameTimingData.lastDisplayedAppScreenTime = displayQpc; - } else { - frameTimingData.lastDisplayedAppScreenTime = displayQpc; + break; } - } - else { - // TODO: log issue or invalidate related columns or drop frame (or some combination) - pmlog_info("null pFrameDataOfLastAppDisplayed"); - } - - if (pFrameDataOfPreviousAppFrameOfLastAppDisplayed) { - if (pFrameDataOfPreviousAppFrameOfLastAppDisplayed->present_event.AppPropagatedPresentStartTime != 0) { - lastDisplayedCpuStart = pFrameDataOfPreviousAppFrameOfLastAppDisplayed->present_event.AppPropagatedPresentStartTime + - pFrameDataOfPreviousAppFrameOfLastAppDisplayed->present_event.AppPropagatedTimeInPresent; - } else { - lastDisplayedCpuStart = pFrameDataOfPreviousAppFrameOfLastAppDisplayed->present_event.PresentStartTime + - pFrameDataOfPreviousAppFrameOfLastAppDisplayed->present_event.TimeInPresent; + case PM_DATA_TYPE_ENUM: + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + *reinterpret_cast(pFrameMemberBytes); + break; } } - else { - pmlog_dbg("null pPreviousFrameDataOfLastDisplayed"); - lastDisplayedCpuStart = 0; - } - appIndex = std::numeric_limits::max(); - if (pSourceFrameData->present_event.DisplayedCount > 0) { - for (size_t i = 0; i < pSourceFrameData->present_event.DisplayedCount; ++i) { - if (pSourceFrameData->present_event.Displayed_FrameType[i] == FrameType::NotSet || - pSourceFrameData->present_event.Displayed_FrameType[i] == FrameType::Application) { - appIndex = i; - break; - } + + void PM_FRAME_QUERY::GatherFromTelemetry_(const GatherCommand_& cmd, uint8_t* pBlobBytes, int64_t searchQpc, + const ipc::TelemetryMap& teleMap) const + { + switch (cmd.gatherType) { + case PM_DATA_TYPE_UINT64: + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; + break; + case PM_DATA_TYPE_DOUBLE: + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; + break; + case PM_DATA_TYPE_ENUM: + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; + break; + case PM_DATA_TYPE_BOOL: + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; + break; } - } else { - appIndex = 0; } - - return; } diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h index 46d0a6af..b37aca13 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h @@ -1,89 +1,62 @@ -#pragma once -#include "../PresentMonAPI2/PresentMonAPI.h" -#include "../PresentMonUtils/StreamFormat.h" +#pragma once #include #include #include -#include "FrameTimingData.h" +#include "../CommonUtilities/mc/MetricsTypes.h" +#include "../PresentMonAPI2/PresentMonAPI.h" namespace pmapi::intro { class Root; } -namespace pmon::mid +namespace pmon::ipc { - class GatherCommand_; + class MiddlewareComms; + class TelemetryMap; } -struct PM_FRAME_QUERY +namespace pmon::mid { -public: - // types - struct Context + struct PM_FRAME_QUERY { + public: // functions - Context(uint64_t qpcStart, long long perfCounterFrequency, FrameTimingData& frameTimingData) : qpcStart{ qpcStart }, - performanceCounterPeriodMs{ perfCounterFrequency != 0.f ? 1000.0 / perfCounterFrequency : 0.f }, - frameTimingData{ frameTimingData } {} - void UpdateSourceData(const PmNsmFrameData* pSourceFrameData_in, - const PmNsmFrameData* pFrameDataOfNextDisplayed, - const PmNsmFrameData* pFrameDataofLastPresented, - const PmNsmFrameData* pFrameDataofLastAppPresented, - const PmNsmFrameData* pFrameDataOfLastDisplayed, - const PmNsmFrameData* pFrameDataOfLastAppDisplayed, - const PmNsmFrameData* pPreviousFrameDataOfLastDisplayed); - // data - const PmNsmFrameData* pSourceFrameData = nullptr; - uint32_t sourceFrameDisplayIndex = 0; - const double performanceCounterPeriodMs{}; - const uint64_t qpcStart{}; - bool dropped{}; - // Start qpc of the previous frame, displayed or not - uint64_t cpuStart = 0; - // Present start qpc of the previous frame, displayed or not - uint64_t previousPresentStartQpc = 0; - // Start cpustart qpc of the previously displayed frame - uint64_t lastDisplayedCpuStart = 0; - // Screen time qpc of the previously displayed frame. - uint64_t previousDisplayedQpc = 0; - // Screen time qpc of the first display in the next displayed PmNsmFrameData - uint64_t nextDisplayedQpc = 0; - // Display index to attribute cpu work, gpu work, animation error and - // input latency - size_t appIndex = 0; - // Click time qpc of non displayed frame - uint64_t lastReceivedNotDisplayedClickQpc = 0; - // All other input time qpc of non displayed frame - uint64_t lastReceivedNotDisplayedAllInputTime = 0; - // QPC of the last PC Latency simulation start - uint64_t mLastReceivedNotDisplayedPclSimStart = 0; - // QPC of the last PC Latency pc input - uint64_t mLastReceivedNotDisplayedPclInputTime = 0; - FrameTimingData frameTimingData{}; + PM_FRAME_QUERY(std::span queryElements, ipc::MiddlewareComms& comms); + ~PM_FRAME_QUERY(); + void GatherToBlob(uint8_t* pBlobBytes, const util::metrics::FrameMetrics& frameMetrics) const; + size_t GetBlobSize() const; - // Accumlated input to frame start time - double mAccumulatedInput2FrameStartTime = 0.f; - // Current input to frame start average - double avgInput2Fs{}; - }; - // functions - PM_FRAME_QUERY(std::span queryElements); - ~PM_FRAME_QUERY(); - void GatherToBlob(Context& ctx, uint8_t* pDestBlob) const; - size_t GetBlobSize() const; - std::optional GetReferencedDevice() const; + PM_FRAME_QUERY(const PM_FRAME_QUERY&) = delete; + PM_FRAME_QUERY& operator=(const PM_FRAME_QUERY&) = delete; + PM_FRAME_QUERY(PM_FRAME_QUERY&&) = delete; + PM_FRAME_QUERY& operator=(PM_FRAME_QUERY&&) = delete; - PM_FRAME_QUERY(const PM_FRAME_QUERY&) = delete; - PM_FRAME_QUERY& operator=(const PM_FRAME_QUERY&) = delete; - PM_FRAME_QUERY(PM_FRAME_QUERY&&) = delete; - PM_FRAME_QUERY& operator=(PM_FRAME_QUERY&&) = delete; - -private: - // functions - std::unique_ptr MapQueryElementToGatherCommand_(const PM_QUERY_ELEMENT& q, size_t pos); - // data - std::vector> gatherCommands_; - size_t blobSize_ = 0; - std::optional referencedDevice_; -}; \ No newline at end of file + private: + // types + struct GatherCommand_ + { + PM_METRIC metricId; + PM_DATA_TYPE gatherType; + uint32_t blobOffset; + // offset into FrameMetrics struct from metric calculator + uint32_t frameMetricsOffset; + uint32_t deviceId; + uint32_t arrayIdx; + // indicates whether the source data is gatherType or optional + bool isOptional; + // for qpc values that need conversion to milliseconds + double qpcToMs; + }; + // functions + static GatherCommand_ MapQueryElementToFrameGatherCommand_(const PM_QUERY_ELEMENT& q, size_t blobCursor); + static void GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* pBlobBytes, const util::metrics::FrameMetrics& frameMetrics); + void GatherFromTelemetry_(const GatherCommand_& cmd, uint8_t* pBlobBytes, int64_t searchQpc, + const ipc::TelemetryMap& teleMap) const; + // data + const ipc::MiddlewareComms& comms_; + std::vector gatherCommands_; + size_t blobSize_ = 0; + size_t nextFrameSerial_ = 0; + }; +} From a51ca2819e8012fba07852b030f0a2d5a352bab6 Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 15 Jan 2026 22:09:55 +0900 Subject: [PATCH 111/205] metric frame source drafted --- .../FrameMetricsSource.cpp | 177 ++++++++++++++++++ .../PresentMonMiddleware/FrameMetricsSource.h | 59 ++++++ .../PresentMonMiddleware.vcxproj | 6 +- .../PresentMonMiddleware.vcxproj.filters | 10 +- vcpkg.json | 1 + 5 files changed, 249 insertions(+), 4 deletions(-) create mode 100644 IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp create mode 100644 IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h diff --git a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp new file mode 100644 index 00000000..3323d2e9 --- /dev/null +++ b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp @@ -0,0 +1,177 @@ +// Copyright (C) 2025 Intel Corporation +#include "FrameMetricsSource.h" + +namespace pmon::mid +{ + SwapChainState::SwapChainState(size_t capacity) + : + metrics_{ capacity } + { + } + + bool SwapChainState::HasPending() const + { + return cursor_ < metrics_.size(); + } + + const util::metrics::FrameMetrics& SwapChainState::Peek() const + { + return metrics_[cursor_]; + } + + void SwapChainState::ConsumeNext() + { + if (cursor_ < metrics_.size()) { + ++cursor_; + } + } + + void SwapChainState::ProcessFrame(const util::metrics::FrameData& frame, util::QpcConverter& qpc) + { + auto ready = unified_.Enqueue(frame, util::metrics::MetricsVersion::V2); + for (auto& item : ready) { + auto& present = item.presentPtr ? *item.presentPtr : item.present; + auto* nextPtr = item.nextDisplayedPtr; + + auto computed = util::metrics::ComputeMetricsForPresent( + qpc, + present, + nextPtr, + unified_.swapChain, + util::metrics::MetricsVersion::V2); + + for (auto& cm : computed) { + PushMetrics_(cm.metrics); + } + } + } + + void SwapChainState::PushMetrics_(const util::metrics::FrameMetrics& metrics) + { + if (metrics_.full() && cursor_ > 0) { + --cursor_; + } + metrics_.push_back(metrics); + ClampCursor_(); + } + + void SwapChainState::ClampCursor_() + { + if (cursor_ > metrics_.size()) { + cursor_ = metrics_.size(); + } + } + + FrameMetricsSource::FrameMetricsSource(ipc::MiddlewareComms& comms, uint32_t processId, size_t perSwapChainCapacity) + : + comms_{ comms }, + processId_{ processId }, + perSwapChainCapacity_{ perSwapChainCapacity == 0 ? size_t{ 1 } : perSwapChainCapacity }, + qpcFrequency_{} + { + const double period = util::GetTimestampPeriodSeconds(); + qpcFrequency_ = period == 0.0 ? 0 : static_cast(1.0 / period + 0.5); + Open_(); + } + + FrameMetricsSource::~FrameMetricsSource() + { + Close_(); + } + + void FrameMetricsSource::Open_() + { + comms_.OpenFrameDataStore(processId_); + pStore_ = &comms_.GetFrameDataStore(processId_); + sessionStartQpc_ = static_cast(pStore_->bookkeeping.startQpc); + const auto range = pStore_->frameData.GetSerialRange(); + nextFrameSerial_ = range.first; + } + + void FrameMetricsSource::Close_() + { + if (pStore_ == nullptr) { + return; + } + comms_.CloseFrameDataStore(processId_); + pStore_ = nullptr; + nextFrameSerial_ = 0; + sessionStartQpc_ = 0; + swapChains_.clear(); + } + + void FrameMetricsSource::ProcessNewFrames_() + { + if (pStore_ == nullptr) { + return; + } + + if (sessionStartQpc_ == 0 && pStore_->bookkeeping.startQpc != 0) { + sessionStartQpc_ = static_cast(pStore_->bookkeeping.startQpc); + } + + const auto& ring = pStore_->frameData; + const auto range = ring.GetSerialRange(); + + if (range.first > nextFrameSerial_) { + nextFrameSerial_ = range.first; + } + if (nextFrameSerial_ >= range.second) { + return; + } + + util::QpcConverter qpc{ qpcFrequency_, sessionStartQpc_ }; + + for (size_t serial = nextFrameSerial_; serial < range.second; ++serial) { + const auto& frame = ring.At(serial); + auto [it, inserted] = swapChains_.try_emplace(frame.swapChainAddress, perSwapChainCapacity_); + auto& state = it->second; + state.ProcessFrame(frame, qpc); + } + + nextFrameSerial_ = range.second; + ring.MarkNextRead(nextFrameSerial_); + } + + std::vector FrameMetricsSource::Consume(size_t maxFrames) + { + std::vector output; + ProcessNewFrames_(); + + if (maxFrames == 0) { + return output; + } + + output.reserve(maxFrames); + + for (size_t i = 0; i < maxFrames; ++i) { + SwapChainState* selectedState = nullptr; + const util::metrics::FrameMetrics* selectedMetrics = nullptr; + uint64_t selectedSwapChain = 0; + + for (auto& [address, state] : swapChains_) { + if (!state.HasPending()) { + continue; + } + const auto& metrics = state.Peek(); + if (selectedMetrics == nullptr || + metrics.timeInSeconds < selectedMetrics->timeInSeconds || + (metrics.timeInSeconds == selectedMetrics->timeInSeconds && address < selectedSwapChain)) { + selectedMetrics = &metrics; + selectedState = &state; + selectedSwapChain = address; + } + } + + if (selectedMetrics == nullptr || selectedState == nullptr) { + break; + } + + output.push_back(*selectedMetrics); + selectedState->ConsumeNext(); + } + + return output; + } + +} diff --git a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h new file mode 100644 index 00000000..444e3801 --- /dev/null +++ b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h @@ -0,0 +1,59 @@ +#pragma once +#include +#include +#include +#include +#include +#include "../CommonUtilities/Qpc.h" +#include "../CommonUtilities/mc/MetricsCalculator.h" +#include "../CommonUtilities/mc/UnifiedSwapChain.h" +#include "../Interprocess/source/Interprocess.h" + +namespace pmon::mid +{ + class SwapChainState + { + public: + explicit SwapChainState(size_t capacity); + bool HasPending() const; + const util::metrics::FrameMetrics& Peek() const; + void ConsumeNext(); + void ProcessFrame(const util::metrics::FrameData& frame, util::QpcConverter& qpc); + + private: + void ClampCursor_(); + void PushMetrics_(const util::metrics::FrameMetrics& metrics); + + boost::circular_buffer metrics_; + util::metrics::UnifiedSwapChain unified_; + size_t cursor_ = 0; + }; + + class FrameMetricsSource + { + public: + FrameMetricsSource(ipc::MiddlewareComms& comms, uint32_t processId, size_t perSwapChainCapacity); + ~FrameMetricsSource(); + + FrameMetricsSource(const FrameMetricsSource&) = delete; + FrameMetricsSource& operator=(const FrameMetricsSource&) = delete; + FrameMetricsSource(FrameMetricsSource&&) = delete; + FrameMetricsSource& operator=(FrameMetricsSource&&) = delete; + + std::vector Consume(size_t maxFrames); + + private: + void Open_(); + void Close_(); + void ProcessNewFrames_(); + + ipc::MiddlewareComms& comms_; + const ipc::FrameDataStore* pStore_ = nullptr; + uint32_t processId_ = 0; + size_t perSwapChainCapacity_ = 0; + size_t nextFrameSerial_ = 0; + uint64_t qpcFrequency_ = 0; + uint64_t sessionStartQpc_ = 0; + std::unordered_map swapChains_; + }; +} diff --git a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj index 663b96aa..47f71b1c 100644 --- a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj +++ b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj @@ -1,4 +1,4 @@ - + @@ -13,6 +13,7 @@ + @@ -24,6 +25,7 @@ /bigobj %(AdditionalOptions) + @@ -134,4 +136,4 @@ - \ No newline at end of file + diff --git a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters index 76c84a6a..3e4931de 100644 --- a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters +++ b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters @@ -1,4 +1,4 @@ - + @@ -39,11 +39,17 @@ Header Files + + Header Files + Source Files + + Source Files + Source Files @@ -54,4 +60,4 @@ Source Files - \ No newline at end of file + diff --git a/vcpkg.json b/vcpkg.json index 1d3f311b..88fd9a89 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -7,6 +7,7 @@ "cli11", "boost-interprocess", "boost-process", + "boost-circular-buffer", "cereal", "concurrentqueue", "boost-asio", From 7caf1fd8151ab5ae0db0eee7406e2402c4ea73f1 Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 15 Jan 2026 22:30:11 +0900 Subject: [PATCH 112/205] frame event query back in global namespace --- .../PresentMonMiddleware/FrameEventQuery.cpp | 730 +++++++++--------- .../PresentMonMiddleware/FrameEventQuery.h | 80 +- 2 files changed, 406 insertions(+), 404 deletions(-) diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index ab8e531b..5da91fbb 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -12,412 +12,412 @@ #include #include -namespace pmon::mid +namespace ipc = pmon::ipc; +namespace util = pmon::util; + +using namespace util; + +PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::MiddlewareComms& comms) + : + comms_{ comms } { - using namespace util; + if (queryElements.empty()) { + pmlog_error("Frame query requires at least one query element").diag(); + throw Except("Empty frame query"); + } - PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::MiddlewareComms& comms) - : - comms_{ comms } - { - if (queryElements.empty()) { - pmlog_error("Frame query requires at least one query element").diag(); - throw Except("Empty frame query"); - } + const auto pIntro = comms.GetIntrospectionRoot(); + if (!pIntro) { + pmlog_error("Failed to acquire introspection root for frame query").diag(); + throw Except("No introspection root for frame query"); + } + // TODO: consider direct use (unwrapped) + pmapi::intro::Root introRoot{ pIntro, [](const PM_INTROSPECTION_ROOT* p) { + free(const_cast(p)); + } }; - const auto pIntro = comms.GetIntrospectionRoot(); - if (!pIntro) { - pmlog_error("Failed to acquire introspection root for frame query").diag(); - throw Except("No introspection root for frame query"); + // TODO: use data type ipc bridger + auto GetDataTypeAlignment = [](PM_DATA_TYPE dataType) { + switch (dataType) { + case PM_DATA_TYPE_DOUBLE: + return alignof(double); + case PM_DATA_TYPE_UINT64: + return alignof(uint64_t); + case PM_DATA_TYPE_INT32: + return alignof(int32_t); + case PM_DATA_TYPE_UINT32: + return alignof(uint32_t); + case PM_DATA_TYPE_ENUM: + return alignof(int); + case PM_DATA_TYPE_BOOL: + return alignof(bool); + case PM_DATA_TYPE_STRING: + return alignof(char); + default: + return size_t{ 1u }; } - // TODO: consider direct use (unwrapped) - pmapi::intro::Root introRoot{ pIntro, [](const PM_INTROSPECTION_ROOT* p) { - free(const_cast(p)); - } }; - - // TODO: use data type ipc bridger - auto GetDataTypeAlignment = [](PM_DATA_TYPE dataType) { - switch (dataType) { - case PM_DATA_TYPE_DOUBLE: - return alignof(double); - case PM_DATA_TYPE_UINT64: - return alignof(uint64_t); - case PM_DATA_TYPE_INT32: - return alignof(int32_t); - case PM_DATA_TYPE_UINT32: - return alignof(uint32_t); - case PM_DATA_TYPE_ENUM: - return alignof(int); - case PM_DATA_TYPE_BOOL: - return alignof(bool); - case PM_DATA_TYPE_STRING: - return alignof(char); - default: - return size_t{ 1u }; - } - }; + }; - size_t blobCursor = 0; - gatherCommands_.reserve(queryElements.size()); + size_t blobCursor = 0; + gatherCommands_.reserve(queryElements.size()); - for (auto& q : queryElements) { - const auto metricView = introRoot.FindMetric(q.metric); - if (!pmapi::intro::MetricTypeIsFrameEvent(metricView.GetType())) { - pmlog_error("Non-frame metric used in frame query") - .pmwatch(metricView.Introspect().GetSymbol()).diag(); - throw Except("Frame query contains non-frame metric"); - } + for (auto& q : queryElements) { + const auto metricView = introRoot.FindMetric(q.metric); + if (!pmapi::intro::MetricTypeIsFrameEvent(metricView.GetType())) { + pmlog_error("Non-frame metric used in frame query") + .pmwatch(metricView.Introspect().GetSymbol()).diag(); + throw Except("Frame query contains non-frame metric"); + } - if (q.stat != PM_STAT_NONE) { - pmlog_warn("Frame query stat should be NONE") - .pmwatch(metricView.Introspect().GetSymbol()) - .pmwatch((int)q.stat).diag(); - } + if (q.stat != PM_STAT_NONE) { + pmlog_warn("Frame query stat should be NONE") + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch((int)q.stat).diag(); + } - const auto frameType = metricView.GetDataTypeInfo().GetFrameType(); - const auto frameTypeSize = ipc::intro::GetDataTypeSize(frameType); - if (frameTypeSize == 0) { - pmlog_error("Unsupported frame query data type") - .pmwatch(metricView.Introspect().GetSymbol()).diag(); - throw Except("Unsupported frame query data type"); - } + const auto frameType = metricView.GetDataTypeInfo().GetFrameType(); + const auto frameTypeSize = ipc::intro::GetDataTypeSize(frameType); + if (frameTypeSize == 0) { + pmlog_error("Unsupported frame query data type") + .pmwatch(metricView.Introspect().GetSymbol()).diag(); + throw Except("Unsupported frame query data type"); + } - if (q.deviceId != ipc::kUniversalDeviceId) { - // TODO: check that device exists in introspection - } + if (q.deviceId != ipc::kUniversalDeviceId) { + // TODO: check that device exists in introspection + } - const auto deviceMetricInfo = [&]() -> std::optional { - for (auto info : metricView.GetDeviceMetricInfo()) { - if (info.GetDevice().GetId() == q.deviceId) { - return info; - } + const auto deviceMetricInfo = [&]() -> std::optional { + for (auto info : metricView.GetDeviceMetricInfo()) { + if (info.GetDevice().GetId() == q.deviceId) { + return info; } - return std::nullopt; - }(); - - if (!deviceMetricInfo.has_value() || !deviceMetricInfo->IsAvailable()) { - pmlog_error("Metric not supported by device in frame query") - .pmwatch(metricView.Introspect().GetSymbol()) - .pmwatch(q.deviceId).diag(); - throw Except("Metric not supported by device in frame query"); } + return std::nullopt; + }(); - const auto arraySize = deviceMetricInfo->GetArraySize(); - if (q.arrayIndex >= arraySize) { - pmlog_error("Frame query array index out of bounds") - .pmwatch(metricView.Introspect().GetSymbol()) - .pmwatch(q.arrayIndex) - .pmwatch(arraySize).diag(); - throw Except("Frame query array index out of bounds"); - } + if (!deviceMetricInfo.has_value() || !deviceMetricInfo->IsAvailable()) { + pmlog_error("Metric not supported by device in frame query") + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch(q.deviceId).diag(); + throw Except("Metric not supported by device in frame query"); + } - const auto alignment = GetDataTypeAlignment(frameType); - blobCursor = util::PadToAlignment(blobCursor, alignment); + const auto arraySize = deviceMetricInfo->GetArraySize(); + if (q.arrayIndex >= arraySize) { + pmlog_error("Frame query array index out of bounds") + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch(q.arrayIndex) + .pmwatch(arraySize).diag(); + throw Except("Frame query array index out of bounds"); + } - GatherCommand_ cmd{}; - if (q.deviceId > 0 && q.deviceId <= ipc::kSystemDeviceId) { - const auto& teleMap = q.deviceId == ipc::kSystemDeviceId ? - comms_.GetSystemDataStore().telemetryData : - comms_.GetGpuDataStore(q.deviceId).telemetryData; + const auto alignment = GetDataTypeAlignment(frameType); + blobCursor = util::PadToAlignment(blobCursor, alignment); - if (teleMap.ArraySize(q.metric) == 0) { - pmlog_error("Telemetry ring missing for metric in frame query") - .pmwatch(metricView.Introspect().GetSymbol()) - .pmwatch(q.deviceId).diag(); - throw Except("Telemetry ring missing for metric in frame query"); - } + GatherCommand_ cmd{}; + if (q.deviceId > 0 && q.deviceId <= ipc::kSystemDeviceId) { + const auto& teleMap = q.deviceId == ipc::kSystemDeviceId ? + comms_.GetSystemDataStore().telemetryData : + comms_.GetGpuDataStore(q.deviceId).telemetryData; - cmd.metricId = q.metric; - cmd.gatherType = frameType; - cmd.blobOffset = static_cast(blobCursor); - cmd.frameMetricsOffset = 0u; - cmd.deviceId = q.deviceId; - cmd.arrayIdx = q.arrayIndex; - cmd.isOptional = false; - cmd.qpcToMs = 0.0; + if (teleMap.ArraySize(q.metric) == 0) { + pmlog_error("Telemetry ring missing for metric in frame query") + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch(q.deviceId).diag(); + throw Except("Telemetry ring missing for metric in frame query"); } - else if (q.deviceId == ipc::kUniversalDeviceId) { - cmd = MapQueryElementToFrameGatherCommand_(q, blobCursor); - if (cmd.gatherType == PM_DATA_TYPE_VOID) { - pmlog_error("Unsupported frame metric in frame query") - .pmwatch(metricView.Introspect().GetSymbol()).diag(); - throw Except("Unsupported frame metric in frame query"); - } + cmd.metricId = q.metric; + cmd.gatherType = frameType; + cmd.blobOffset = static_cast(blobCursor); + cmd.frameMetricsOffset = 0u; + cmd.deviceId = q.deviceId; + cmd.arrayIdx = q.arrayIndex; + cmd.isOptional = false; + cmd.qpcToMs = 0.0; + } + else if (q.deviceId == ipc::kUniversalDeviceId) { + cmd = MapQueryElementToFrameGatherCommand_(q, blobCursor); - const auto gatherTypeSize = ipc::intro::GetDataTypeSize(cmd.gatherType); - if (gatherTypeSize != frameTypeSize) { - pmlog_error("Frame query type mismatch") - .pmwatch(metricView.Introspect().GetSymbol()) - .pmwatch(gatherTypeSize) - .pmwatch(frameTypeSize).diag(); - throw Except("Frame query type mismatch"); - } + if (cmd.gatherType == PM_DATA_TYPE_VOID) { + pmlog_error("Unsupported frame metric in frame query") + .pmwatch(metricView.Introspect().GetSymbol()).diag(); + throw Except("Unsupported frame metric in frame query"); } - else { - pmlog_error("Invalid device id in frame query") - .pmwatch(q.deviceId).diag(); - throw Except("Invalid device id in frame query"); + + const auto gatherTypeSize = ipc::intro::GetDataTypeSize(cmd.gatherType); + if (gatherTypeSize != frameTypeSize) { + pmlog_error("Frame query type mismatch") + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch(gatherTypeSize) + .pmwatch(frameTypeSize).diag(); + throw Except("Frame query type mismatch"); } + } + else { + pmlog_error("Invalid device id in frame query") + .pmwatch(q.deviceId).diag(); + throw Except("Invalid device id in frame query"); + } - q.dataOffset = blobCursor; - q.dataSize = frameTypeSize; - blobCursor += frameTypeSize; + q.dataOffset = blobCursor; + q.dataSize = frameTypeSize; + blobCursor += frameTypeSize; - gatherCommands_.push_back(cmd); - } - // make sure blob size is a multiple of 16 so that blobs in array always start 16-aligned - blobSize_ = util::PadToAlignment(blobCursor, 16u); + gatherCommands_.push_back(cmd); } + // make sure blob size is a multiple of 16 so that blobs in array always start 16-aligned + blobSize_ = util::PadToAlignment(blobCursor, 16u); +} - PM_FRAME_QUERY::~PM_FRAME_QUERY() = default; +PM_FRAME_QUERY::~PM_FRAME_QUERY() = default; - void PM_FRAME_QUERY::GatherToBlob(uint8_t* pBlobBytes, const util::metrics::FrameMetrics& frameMetrics) const - { - for (auto& cmd : gatherCommands_) { - if (cmd.deviceId == ipc::kUniversalDeviceId) { - GatherFromFrameMetrics_(cmd, pBlobBytes, frameMetrics); - } - else if (cmd.deviceId == ipc::kSystemDeviceId) { - GatherFromTelemetry_(cmd, pBlobBytes, frameMetrics.cpuStartQpc, comms_.GetSystemDataStore().telemetryData); - } - else if (cmd.deviceId < ipc::kSystemDeviceId) { - GatherFromTelemetry_(cmd, pBlobBytes, frameMetrics.cpuStartQpc, comms_.GetGpuDataStore(cmd.deviceId).telemetryData); - } - else { - pmlog_error("Bad device ID").pmwatch(cmd.deviceId); - } +void PM_FRAME_QUERY::GatherToBlob(uint8_t* pBlobBytes, const util::metrics::FrameMetrics& frameMetrics) const +{ + for (auto& cmd : gatherCommands_) { + if (cmd.deviceId == ipc::kUniversalDeviceId) { + GatherFromFrameMetrics_(cmd, pBlobBytes, frameMetrics); + } + else if (cmd.deviceId == ipc::kSystemDeviceId) { + GatherFromTelemetry_(cmd, pBlobBytes, frameMetrics.cpuStartQpc, comms_.GetSystemDataStore().telemetryData); + } + else if (cmd.deviceId < ipc::kSystemDeviceId) { + GatherFromTelemetry_(cmd, pBlobBytes, frameMetrics.cpuStartQpc, comms_.GetGpuDataStore(cmd.deviceId).telemetryData); + } + else { + pmlog_error("Bad device ID").pmwatch(cmd.deviceId); } } +} - size_t PM_FRAME_QUERY::GetBlobSize() const - { - return blobSize_; - } +size_t PM_FRAME_QUERY::GetBlobSize() const +{ + return blobSize_; +} - PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherCommand_(const PM_QUERY_ELEMENT& q, size_t blobByteCursor) - { - const double qpcToMs = util::GetTimestampPeriodSeconds() * 1000.0; +PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherCommand_(const PM_QUERY_ELEMENT& q, size_t blobByteCursor) +{ + const double qpcToMs = util::GetTimestampPeriodSeconds() * 1000.0; - GatherCommand_ cmd{}; - cmd.metricId = q.metric; - cmd.gatherType = PM_DATA_TYPE_VOID; - cmd.blobOffset = static_cast(blobByteCursor); - cmd.frameMetricsOffset = 0u; - cmd.deviceId = q.deviceId; - cmd.arrayIdx = q.arrayIndex; - cmd.isOptional = false; - cmd.qpcToMs = 0.0; + GatherCommand_ cmd{}; + cmd.metricId = q.metric; + cmd.gatherType = PM_DATA_TYPE_VOID; + cmd.blobOffset = static_cast(blobByteCursor); + cmd.frameMetricsOffset = 0u; + cmd.deviceId = q.deviceId; + cmd.arrayIdx = q.arrayIndex; + cmd.isOptional = false; + cmd.qpcToMs = 0.0; - switch (q.metric) { - case PM_METRIC_PRESENT_START_QPC: - cmd.gatherType = PM_DATA_TYPE_UINT64; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, timeInSeconds); - break; - case PM_METRIC_PRESENT_START_TIME: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, timeInSeconds); - cmd.qpcToMs = qpcToMs; - break; - case PM_METRIC_CPU_START_QPC: - cmd.gatherType = PM_DATA_TYPE_UINT64; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, cpuStartQpc); - break; - case PM_METRIC_CPU_START_TIME: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, cpuStartQpc); - cmd.qpcToMs = qpcToMs; - break; - case PM_METRIC_BETWEEN_PRESENTS: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msBetweenPresents); - break; - case PM_METRIC_IN_PRESENT_API: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msInPresentApi); - break; - case PM_METRIC_RENDER_PRESENT_LATENCY: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msUntilRenderComplete); - break; - case PM_METRIC_BETWEEN_DISPLAY_CHANGE: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msBetweenDisplayChange); - break; - case PM_METRIC_UNTIL_DISPLAYED: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msUntilDisplayed); - break; - case PM_METRIC_DISPLAYED_TIME: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msDisplayedTime); - break; - case PM_METRIC_DISPLAY_LATENCY: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msDisplayLatency); - break; - case PM_METRIC_BETWEEN_SIMULATION_START: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msBetweenSimStarts); - cmd.isOptional = true; - break; - case PM_METRIC_PC_LATENCY: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msPcLatency); - cmd.isOptional = true; - break; - case PM_METRIC_CPU_BUSY: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msCPUBusy); - break; - case PM_METRIC_CPU_WAIT: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msCPUWait); - break; - case PM_METRIC_CPU_FRAME_TIME: - case PM_METRIC_BETWEEN_APP_START: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - break; - case PM_METRIC_GPU_LATENCY: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msGPULatency); - break; - case PM_METRIC_GPU_TIME: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - break; - case PM_METRIC_GPU_BUSY: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msGPUBusy); - break; - case PM_METRIC_GPU_WAIT: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msGPUWait); - break; - case PM_METRIC_DROPPED_FRAMES: - cmd.gatherType = PM_DATA_TYPE_BOOL; - break; - case PM_METRIC_ANIMATION_ERROR: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msAnimationError); - cmd.isOptional = true; - break; - case PM_METRIC_ANIMATION_TIME: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msAnimationTime); - cmd.isOptional = true; - break; - case PM_METRIC_CLICK_TO_PHOTON_LATENCY: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msClickToPhotonLatency); - cmd.isOptional = true; - break; - case PM_METRIC_ALL_INPUT_TO_PHOTON_LATENCY: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msAllInputPhotonLatency); - cmd.isOptional = true; - break; - case PM_METRIC_INSTRUMENTED_LATENCY: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msInstrumentedLatency); - cmd.isOptional = true; - break; - case PM_METRIC_FLIP_DELAY: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msFlipDelay); - cmd.isOptional = true; - break; - case PM_METRIC_FRAME_TYPE: - cmd.gatherType = PM_DATA_TYPE_ENUM; - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, frameType); - break; - default: - pmlog_error("unknown metric id").pmwatch((int)q.metric).diag(); - return {}; - } - return cmd; + switch (q.metric) { + case PM_METRIC_PRESENT_START_QPC: + cmd.gatherType = PM_DATA_TYPE_UINT64; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, timeInSeconds); + break; + case PM_METRIC_PRESENT_START_TIME: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, timeInSeconds); + cmd.qpcToMs = qpcToMs; + break; + case PM_METRIC_CPU_START_QPC: + cmd.gatherType = PM_DATA_TYPE_UINT64; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, cpuStartQpc); + break; + case PM_METRIC_CPU_START_TIME: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, cpuStartQpc); + cmd.qpcToMs = qpcToMs; + break; + case PM_METRIC_BETWEEN_PRESENTS: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msBetweenPresents); + break; + case PM_METRIC_IN_PRESENT_API: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msInPresentApi); + break; + case PM_METRIC_RENDER_PRESENT_LATENCY: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msUntilRenderComplete); + break; + case PM_METRIC_BETWEEN_DISPLAY_CHANGE: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msBetweenDisplayChange); + break; + case PM_METRIC_UNTIL_DISPLAYED: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msUntilDisplayed); + break; + case PM_METRIC_DISPLAYED_TIME: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msDisplayedTime); + break; + case PM_METRIC_DISPLAY_LATENCY: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msDisplayLatency); + break; + case PM_METRIC_BETWEEN_SIMULATION_START: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msBetweenSimStarts); + cmd.isOptional = true; + break; + case PM_METRIC_PC_LATENCY: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msPcLatency); + cmd.isOptional = true; + break; + case PM_METRIC_CPU_BUSY: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msCPUBusy); + break; + case PM_METRIC_CPU_WAIT: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msCPUWait); + break; + case PM_METRIC_CPU_FRAME_TIME: + case PM_METRIC_BETWEEN_APP_START: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + break; + case PM_METRIC_GPU_LATENCY: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msGPULatency); + break; + case PM_METRIC_GPU_TIME: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + break; + case PM_METRIC_GPU_BUSY: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msGPUBusy); + break; + case PM_METRIC_GPU_WAIT: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msGPUWait); + break; + case PM_METRIC_DROPPED_FRAMES: + cmd.gatherType = PM_DATA_TYPE_BOOL; + break; + case PM_METRIC_ANIMATION_ERROR: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msAnimationError); + cmd.isOptional = true; + break; + case PM_METRIC_ANIMATION_TIME: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msAnimationTime); + cmd.isOptional = true; + break; + case PM_METRIC_CLICK_TO_PHOTON_LATENCY: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msClickToPhotonLatency); + cmd.isOptional = true; + break; + case PM_METRIC_ALL_INPUT_TO_PHOTON_LATENCY: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msAllInputPhotonLatency); + cmd.isOptional = true; + break; + case PM_METRIC_INSTRUMENTED_LATENCY: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msInstrumentedLatency); + cmd.isOptional = true; + break; + case PM_METRIC_FLIP_DELAY: + cmd.gatherType = PM_DATA_TYPE_DOUBLE; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msFlipDelay); + cmd.isOptional = true; + break; + case PM_METRIC_FRAME_TYPE: + cmd.gatherType = PM_DATA_TYPE_ENUM; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, frameType); + break; + default: + pmlog_error("unknown metric id").pmwatch((int)q.metric).diag(); + return {}; } + return cmd; +} - void PM_FRAME_QUERY::GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* pBlobBytes, const util::metrics::FrameMetrics& frameMetrics) +void PM_FRAME_QUERY::GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* pBlobBytes, const util::metrics::FrameMetrics& frameMetrics) +{ + const auto pFrameMemberBytes = reinterpret_cast(&frameMetrics) + cmd.frameMetricsOffset; + if (cmd.metricId == PM_METRIC_CPU_FRAME_TIME || cmd.metricId == PM_METRIC_BETWEEN_APP_START) { + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + frameMetrics.msCPUBusy + frameMetrics.msCPUWait; + return; + } + if (cmd.metricId == PM_METRIC_GPU_TIME) { + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + frameMetrics.msGPUBusy + frameMetrics.msGPUWait; + return; + } + if (cmd.metricId == PM_METRIC_DROPPED_FRAMES) { + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + frameMetrics.screenTimeQpc == 0; + return; + } + if (cmd.metricId == PM_METRIC_PRESENT_START_TIME || cmd.metricId == PM_METRIC_CPU_START_TIME) { + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + static_cast(*reinterpret_cast(pFrameMemberBytes)) * cmd.qpcToMs; + return; + } + if (frameMetrics.screenTimeQpc == 0 && + (cmd.metricId == PM_METRIC_DISPLAYED_TIME || + cmd.metricId == PM_METRIC_DISPLAY_LATENCY || + cmd.metricId == PM_METRIC_UNTIL_DISPLAYED || + cmd.metricId == PM_METRIC_BETWEEN_DISPLAY_CHANGE)) { + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + std::numeric_limits::quiet_NaN(); + return; + } + switch (cmd.gatherType) { + case PM_DATA_TYPE_UINT64: + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + *reinterpret_cast(pFrameMemberBytes); + break; + case PM_DATA_TYPE_DOUBLE: { - const auto pFrameMemberBytes = reinterpret_cast(&frameMetrics) + cmd.frameMetricsOffset; - if (cmd.metricId == PM_METRIC_CPU_FRAME_TIME || cmd.metricId == PM_METRIC_BETWEEN_APP_START) { - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - frameMetrics.msCPUBusy + frameMetrics.msCPUWait; - return; - } - if (cmd.metricId == PM_METRIC_GPU_TIME) { - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - frameMetrics.msGPUBusy + frameMetrics.msGPUWait; - return; - } - if (cmd.metricId == PM_METRIC_DROPPED_FRAMES) { - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - frameMetrics.screenTimeQpc == 0; - return; - } - if (cmd.metricId == PM_METRIC_PRESENT_START_TIME || cmd.metricId == PM_METRIC_CPU_START_TIME) { - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - static_cast(*reinterpret_cast(pFrameMemberBytes)) * cmd.qpcToMs; - return; + auto& blobDouble = *reinterpret_cast(pBlobBytes + cmd.blobOffset); + if (!cmd.isOptional) { + blobDouble = *reinterpret_cast(pFrameMemberBytes); } - if (frameMetrics.screenTimeQpc == 0 && - (cmd.metricId == PM_METRIC_DISPLAYED_TIME || - cmd.metricId == PM_METRIC_DISPLAY_LATENCY || - cmd.metricId == PM_METRIC_UNTIL_DISPLAYED || - cmd.metricId == PM_METRIC_BETWEEN_DISPLAY_CHANGE)) { - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - std::numeric_limits::quiet_NaN(); - return; - } - switch (cmd.gatherType) { - case PM_DATA_TYPE_UINT64: - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - *reinterpret_cast(pFrameMemberBytes); - break; - case PM_DATA_TYPE_DOUBLE: - { - auto& blobDouble = *reinterpret_cast(pBlobBytes + cmd.blobOffset); - if (!cmd.isOptional) { - blobDouble = *reinterpret_cast(pFrameMemberBytes); + else { + auto& optDouble = *reinterpret_cast*>(pFrameMemberBytes); + if (optDouble) { + blobDouble = *optDouble; } else { - auto& optDouble = *reinterpret_cast*>(pFrameMemberBytes); - if (optDouble) { - blobDouble = *optDouble; - } - else { - blobDouble = std::numeric_limits::quiet_NaN(); - } + blobDouble = std::numeric_limits::quiet_NaN(); } - break; - } - case PM_DATA_TYPE_ENUM: - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - *reinterpret_cast(pFrameMemberBytes); - break; } + break; } - - void PM_FRAME_QUERY::GatherFromTelemetry_(const GatherCommand_& cmd, uint8_t* pBlobBytes, int64_t searchQpc, - const ipc::TelemetryMap& teleMap) const - { - switch (cmd.gatherType) { - case PM_DATA_TYPE_UINT64: - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; - break; - case PM_DATA_TYPE_DOUBLE: - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; - break; - case PM_DATA_TYPE_ENUM: - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; - break; - case PM_DATA_TYPE_BOOL: - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; - break; - } + case PM_DATA_TYPE_ENUM: + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + *reinterpret_cast(pFrameMemberBytes); + break; + } +} + +void PM_FRAME_QUERY::GatherFromTelemetry_(const GatherCommand_& cmd, uint8_t* pBlobBytes, int64_t searchQpc, + const ipc::TelemetryMap& teleMap) const +{ + switch (cmd.gatherType) { + case PM_DATA_TYPE_UINT64: + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; + break; + case PM_DATA_TYPE_DOUBLE: + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; + break; + case PM_DATA_TYPE_ENUM: + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; + break; + case PM_DATA_TYPE_BOOL: + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; + break; } } diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h index b37aca13..c956ad04 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h @@ -16,47 +16,49 @@ namespace pmon::ipc class TelemetryMap; } -namespace pmon::mid +struct PM_FRAME_QUERY { - struct PM_FRAME_QUERY - { - public: - // functions - PM_FRAME_QUERY(std::span queryElements, ipc::MiddlewareComms& comms); - ~PM_FRAME_QUERY(); - void GatherToBlob(uint8_t* pBlobBytes, const util::metrics::FrameMetrics& frameMetrics) const; - size_t GetBlobSize() const; +public: + // functions + PM_FRAME_QUERY(std::span queryElements, pmon::ipc::MiddlewareComms& comms); + ~PM_FRAME_QUERY(); + void GatherToBlob(uint8_t* pBlobBytes, const pmon::util::metrics::FrameMetrics& frameMetrics) const; + size_t GetBlobSize() const; - PM_FRAME_QUERY(const PM_FRAME_QUERY&) = delete; - PM_FRAME_QUERY& operator=(const PM_FRAME_QUERY&) = delete; - PM_FRAME_QUERY(PM_FRAME_QUERY&&) = delete; - PM_FRAME_QUERY& operator=(PM_FRAME_QUERY&&) = delete; + PM_FRAME_QUERY(const PM_FRAME_QUERY&) = delete; + PM_FRAME_QUERY& operator=(const PM_FRAME_QUERY&) = delete; + PM_FRAME_QUERY(PM_FRAME_QUERY&&) = delete; + PM_FRAME_QUERY& operator=(PM_FRAME_QUERY&&) = delete; - private: - // types - struct GatherCommand_ - { - PM_METRIC metricId; - PM_DATA_TYPE gatherType; - uint32_t blobOffset; - // offset into FrameMetrics struct from metric calculator - uint32_t frameMetricsOffset; - uint32_t deviceId; - uint32_t arrayIdx; - // indicates whether the source data is gatherType or optional - bool isOptional; - // for qpc values that need conversion to milliseconds - double qpcToMs; - }; - // functions - static GatherCommand_ MapQueryElementToFrameGatherCommand_(const PM_QUERY_ELEMENT& q, size_t blobCursor); - static void GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* pBlobBytes, const util::metrics::FrameMetrics& frameMetrics); - void GatherFromTelemetry_(const GatherCommand_& cmd, uint8_t* pBlobBytes, int64_t searchQpc, - const ipc::TelemetryMap& teleMap) const; - // data - const ipc::MiddlewareComms& comms_; - std::vector gatherCommands_; - size_t blobSize_ = 0; - size_t nextFrameSerial_ = 0; +private: + // types + struct GatherCommand_ + { + PM_METRIC metricId; + PM_DATA_TYPE gatherType; + uint32_t blobOffset; + // offset into FrameMetrics struct from metric calculator + uint32_t frameMetricsOffset; + uint32_t deviceId; + uint32_t arrayIdx; + // indicates whether the source data is gatherType or optional + bool isOptional; + // for qpc values that need conversion to milliseconds + double qpcToMs; }; + // functions + static GatherCommand_ MapQueryElementToFrameGatherCommand_(const PM_QUERY_ELEMENT& q, size_t blobCursor); + static void GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* pBlobBytes, const pmon::util::metrics::FrameMetrics& frameMetrics); + void GatherFromTelemetry_(const GatherCommand_& cmd, uint8_t* pBlobBytes, int64_t searchQpc, + const pmon::ipc::TelemetryMap& teleMap) const; + // data + const pmon::ipc::MiddlewareComms& comms_; + std::vector gatherCommands_; + size_t blobSize_ = 0; + size_t nextFrameSerial_ = 0; +}; + +namespace pmon::mid +{ + using ::PM_FRAME_QUERY; } From cf5293fdac635769ffe0458efe8e03f3ecd4f1cd Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 15 Jan 2026 23:08:32 +0900 Subject: [PATCH 113/205] new frame query integrated and building --- .../ConcreteMiddleware.cpp | 160 +++--------------- .../PresentMonMiddleware/ConcreteMiddleware.h | 8 +- .../PresentMonMiddleware/FrameEventQuery.cpp | 30 ++++ 3 files changed, 58 insertions(+), 140 deletions(-) diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index 378f9928..d1bf30f9 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -25,6 +25,7 @@ #include "../ControlLib/CpuTelemetryInfo.h" #include "../PresentMonService/GlobalIdentifiers.h" #include "FrameEventQuery.h" +#include "FrameMetricsSource.h" #include "../CommonUtilities/mt/Thread.h" #include "../CommonUtilities/log/Log.h" #include "../CommonUtilities/Qpc.h" @@ -42,6 +43,7 @@ namespace pmon::mid static const uint32_t kMaxRespBufferSize = 4096; static const uint64_t kClientFrameDeltaQPCThreshold = 50000000; + static constexpr size_t kFrameMetricsPerSwapChainCapacity = 4096u; ConcreteMiddleware::ConcreteMiddleware(std::optional pipeNameOverride) { const auto pipeName = pipeNameOverride.transform(&std::string::c_str) @@ -110,6 +112,11 @@ namespace pmon::mid presentMonStreamClients.emplace(targetPid, std::make_unique(std::move(res.nsmFileName), false)); } + auto sourceIter = frameMetricsSources.find(targetPid); + if (sourceIter == frameMetricsSources.end()) { + frameMetricsSources.emplace(targetPid, + std::make_unique(*pComms, targetPid, kFrameMetricsPerSwapChainCapacity)); + } } catch (...) { const auto code = util::GeneratePmStatus(); @@ -130,6 +137,10 @@ namespace pmon::mid if (iter != presentMonStreamClients.end()) { presentMonStreamClients.erase(std::move(iter)); } + auto sourceIter = frameMetricsSources.find(targetPid); + if (sourceIter != frameMetricsSources.end()) { + frameMetricsSources.erase(std::move(sourceIter)); + } // Remove the input to frame start data for this process. mPclI2FsManager.RemoveProcess(targetPid); } @@ -1268,155 +1279,26 @@ static void ReportMetrics( void mid::ConcreteMiddleware::ConsumeFrameEvents(const PM_FRAME_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t& numFrames) { - PM_STATUS status = PM_STATUS::PM_STATUS_SUCCESS; - const auto frames_to_copy = numFrames; - // We have saved off the number of frames to copy, now set - // to zero in case we error out along the way BEFORE we - // copy frames into the buffer. If a successful copy occurs - // we'll set to actual number copied. - uint32_t frames_copied = 0; numFrames = 0; - StreamClient* pShmClient = nullptr; - try { - pShmClient = presentMonStreamClients.at(processId).get(); - } - catch (...) { - LOG(INFO) - << "Stream client for process " << processId - << " doesn't exist. Please call pmStartStream to initialize the " - "client."; - pmlog_error("Stream client for process {} doesn't exist. Please call pmStartStream to initialize the client.").diag(); - throw Except(std::format("Failed to find stream for pid {} in ConsumeFrameEvents", processId)); - } - - const auto nsm_view = pShmClient->GetNamedSharedMemView(); - const auto nsm_hdr = nsm_view->GetHeader(); - if (!nsm_hdr->process_active) { - StopStreaming(processId); - pmlog_dbg("Process death detected while consuming frame events").diag(); - throw Except(PM_STATUS_INVALID_PID, "Process died cannot consume frame events"); + auto sourceIter = frameMetricsSources.find(processId); + if (sourceIter == frameMetricsSources.end() || sourceIter->second == nullptr) { + pmlog_error("Frame metrics source for process {} doesn't exist. Please call pmStartStream to initialize the client.").diag(); + throw Except(std::format("Failed to find frame metrics source for pid {} in ConsumeFrameEvents", processId)); } - const auto last_frame_idx = pShmClient->GetLatestFrameIndex(); - if (last_frame_idx == UINT_MAX) { - // There are no frames available, no error frames copied = 0 + if (frames_to_copy == 0) { return; } - // make sure active device is the one referenced in this query - if (auto devId = pQuery->GetReferencedDevice()) { - SetActiveGraphicsAdapter(*devId); - } - - FrameTimingData currentFrameTimingData{}; - auto iter = frameTimingData.find(processId); - if (iter != frameTimingData.end()) { - currentFrameTimingData = iter->second; + auto frames = sourceIter->second->Consume(frames_to_copy); + for (const auto& frameMetrics : frames) { + pQuery->GatherToBlob(pBlob, frameMetrics); + pBlob += pQuery->GetBlobSize(); } - FakePMTraceSession pmSession; - pmSession.mMilliSecondsPerTimestamp = 1000.0 / pShmClient->GetQpcFrequency().QuadPart; - - // context transmits various data that applies to each gather command in the query - PM_FRAME_QUERY::Context ctx{ - nsm_hdr->start_qpc, - pShmClient->GetQpcFrequency().QuadPart, - currentFrameTimingData }; - - while (frames_copied < frames_to_copy) { - const PmNsmFrameData* pCurrentFrameData = nullptr; - const PmNsmFrameData* pNextFrameData = nullptr; - const PmNsmFrameData* pFrameDataOfLastPresented = nullptr; - const PmNsmFrameData* pFrameDataOfLastAppPresented = nullptr; - const PmNsmFrameData* pFrameDataOfNextDisplayed = nullptr; - const PmNsmFrameData* pFrameDataOfLastDisplayed = nullptr; - const PmNsmFrameData* pFrameDataOfLastAppDisplayed = nullptr; - const PmNsmFrameData* pFrameDataOfPreviousAppFrameOfLastAppDisplayed = nullptr; - const auto status = pShmClient->ConsumePtrToNextNsmFrameData(&pCurrentFrameData, &pNextFrameData, - &pFrameDataOfNextDisplayed, &pFrameDataOfLastPresented, &pFrameDataOfLastAppPresented, - &pFrameDataOfLastDisplayed, &pFrameDataOfLastAppDisplayed, &pFrameDataOfPreviousAppFrameOfLastAppDisplayed); - if (status != PM_STATUS::PM_STATUS_SUCCESS) { - pmlog_error("Error while trying to get frame data from shared memory").diag(); - throw Except("Error while trying to get frame data from shared memory"); - } - if (!pCurrentFrameData) { - break; - } - if (pFrameDataOfLastPresented && pFrameDataOfLastAppPresented && pFrameDataOfNextDisplayed) { - ctx.UpdateSourceData(pCurrentFrameData, - pFrameDataOfNextDisplayed, - pFrameDataOfLastPresented, - pFrameDataOfLastAppPresented, - pFrameDataOfLastDisplayed, - pFrameDataOfLastAppDisplayed, - pFrameDataOfPreviousAppFrameOfLastAppDisplayed); - - if (ctx.dropped && ctx.pSourceFrameData->present_event.DisplayedCount == 0) { - pQuery->GatherToBlob(ctx, pBlob); - pBlob += pQuery->GetBlobSize(); - frames_copied++; - } else { - while (ctx.sourceFrameDisplayIndex < ctx.pSourceFrameData->present_event.DisplayedCount) { - if (ctx.pSourceFrameData->present_event.PclSimStartTime != 0) { - // If we are calculating PC Latency then we need to update the input to frame start - // time. - if (ctx.pSourceFrameData->present_event.PclInputPingTime == 0) { - if (ctx.mAccumulatedInput2FrameStartTime != 0) { - // This frame was displayed but we don't have a pc latency input time. However, there is accumulated time - // so there is a pending input that will now hit the screen. Add in the time from the last not - // displayed pc simulation start to this frame's pc simulation start. - ctx.mAccumulatedInput2FrameStartTime += - pmSession.TimestampDeltaToUnsignedMilliSeconds( - ctx.mLastReceivedNotDisplayedPclSimStart, - ctx.pSourceFrameData->present_event.PclSimStartTime); - // Add all of the accumlated time to the average input to frame start time. - mPclI2FsManager.AddI2FsValueForProcess( - ctx.pSourceFrameData->present_event.ProcessId, - ctx.mLastReceivedNotDisplayedPclInputTime, - ctx.mAccumulatedInput2FrameStartTime); - // Reset the tracking variables for when we have a dropped frame with a pc latency input - ctx.mAccumulatedInput2FrameStartTime = 0.f; - ctx.mLastReceivedNotDisplayedPclSimStart = 0; - ctx.mLastReceivedNotDisplayedPclInputTime = 0; - } - } else { - mPclI2FsManager.AddI2FsValueForProcess( - ctx.pSourceFrameData->present_event.ProcessId, - ctx.pSourceFrameData->present_event.PclInputPingTime, - pmSession.TimestampDeltaToUnsignedMilliSeconds( - ctx.pSourceFrameData->present_event.PclInputPingTime, - ctx.pSourceFrameData->present_event.PclSimStartTime)); - } - } - ctx.avgInput2Fs = mPclI2FsManager.GetI2FsForProcess(ctx.pSourceFrameData->present_event.ProcessId); - pQuery->GatherToBlob(ctx, pBlob); - pBlob += pQuery->GetBlobSize(); - frames_copied++; - ctx.sourceFrameDisplayIndex++; - } - } - - } - // Check to see if the next frame produces more frames than we can store in the - // the blob. - if (frames_copied + pNextFrameData->present_event.DisplayedCount >= frames_to_copy) { - break; - } - } - // Set to the actual number of frames copied - numFrames = frames_copied; - // Trim off any old flip delay data that resides in the FrameTimingData::flipDelayDataMap map - // that is older than the last displayed frame id. - for (auto it = ctx.frameTimingData.flipDelayDataMap.begin(); it != ctx.frameTimingData.flipDelayDataMap.end();) { - if (it->first < ctx.frameTimingData.lastDisplayedFrameId) { - it = ctx.frameTimingData.flipDelayDataMap.erase(it); // Erase and move to the next element - } else { - ++it; // Move to the next element - } - } - frameTimingData[processId] = ctx.frameTimingData; + numFrames = uint32_t(frames.size()); } void ConcreteMiddleware::StopPlayback() diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h index 15f7ecf5..cb42fb9f 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "../CommonUtilities/win/WinAPI.h" #include "Middleware.h" #include "../Interprocess/source/Interprocess.h" @@ -18,6 +18,8 @@ namespace pmapi::intro namespace pmon::mid { + class FrameMetricsSource; + // Used to calculate correct start frame based on metric offset struct MetricOffsetData { uint64_t queryToFrameDataDelta = 0; @@ -229,5 +231,9 @@ namespace pmon::mid std::optional activeDevice; std::unique_ptr pIntroRoot; InputToFsManager mPclI2FsManager; + + // new members + // Frame metrics sources mapped to process id + std::map> frameMetricsSources; }; } diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index 5da91fbb..5fcc18b0 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -26,6 +26,7 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M throw Except("Empty frame query"); } + // TODO: pass in from middleware cache rather than marshalling over anew from comms const auto pIntro = comms.GetIntrospectionRoot(); if (!pIntro) { pmlog_error("Failed to acquire introspection root for frame query").diag(); @@ -375,6 +376,14 @@ void PM_FRAME_QUERY::GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* *reinterpret_cast(pBlobBytes + cmd.blobOffset) = *reinterpret_cast(pFrameMemberBytes); break; + case PM_DATA_TYPE_INT32: + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + *reinterpret_cast(pFrameMemberBytes); + break; + case PM_DATA_TYPE_UINT32: + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + *reinterpret_cast(pFrameMemberBytes); + break; case PM_DATA_TYPE_DOUBLE: { auto& blobDouble = *reinterpret_cast(pBlobBytes + cmd.blobOffset); @@ -396,6 +405,16 @@ void PM_FRAME_QUERY::GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* *reinterpret_cast(pBlobBytes + cmd.blobOffset) = *reinterpret_cast(pFrameMemberBytes); break; + case PM_DATA_TYPE_BOOL: + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = + *reinterpret_cast(pFrameMemberBytes); + break; + case PM_DATA_TYPE_STRING: + std::copy_n(reinterpret_cast(pFrameMemberBytes), PM_MAX_PATH, + reinterpret_cast(pBlobBytes + cmd.blobOffset)); + break; + case PM_DATA_TYPE_VOID: + break; } } @@ -407,6 +426,12 @@ void PM_FRAME_QUERY::GatherFromTelemetry_(const GatherCommand_& cmd, uint8_t* pB *reinterpret_cast(pBlobBytes + cmd.blobOffset) = teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; break; + case PM_DATA_TYPE_INT32: + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = 0; + break; + case PM_DATA_TYPE_UINT32: + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = 0u; + break; case PM_DATA_TYPE_DOUBLE: *reinterpret_cast(pBlobBytes + cmd.blobOffset) = teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; @@ -419,5 +444,10 @@ void PM_FRAME_QUERY::GatherFromTelemetry_(const GatherCommand_& cmd, uint8_t* pB *reinterpret_cast(pBlobBytes + cmd.blobOffset) = teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; break; + case PM_DATA_TYPE_STRING: + std::fill_n(pBlobBytes + cmd.blobOffset, PM_MAX_PATH, 0); + break; + case PM_DATA_TYPE_VOID: + break; } } From bee6fa5c6fb7b6178f25cc180b72552ead742f6c Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 15 Jan 2026 23:15:54 +0900 Subject: [PATCH 114/205] disallow multiple fq --- .../PresentMonMiddleware/ConcreteMiddleware.cpp | 8 ++++++++ IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h | 1 + 2 files changed, 9 insertions(+) diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index d1bf30f9..f9dbd9f4 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -1267,13 +1267,21 @@ static void ReportMetrics( PM_FRAME_QUERY* mid::ConcreteMiddleware::RegisterFrameEventQuery(std::span queryElements, uint32_t& blobSize) { + if (activeFrameEventQuery != nullptr) { + pmlog_error("Frame event query already registered").diag(); + throw Except("Frame event query already registered"); + } const auto pQuery = new PM_FRAME_QUERY{ queryElements, *pComms }; + activeFrameEventQuery = pQuery; blobSize = (uint32_t)pQuery->GetBlobSize(); return pQuery; } void mid::ConcreteMiddleware::FreeFrameEventQuery(const PM_FRAME_QUERY* pQuery) { + if (pQuery == activeFrameEventQuery) { + activeFrameEventQuery = nullptr; + } delete const_cast(pQuery); } diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h index cb42fb9f..717448ad 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h @@ -225,6 +225,7 @@ namespace pmon::mid std::unordered_map, uint64_t> queryFrameDataDeltas; // Dynamic query handle to cache data std::unordered_map, std::unique_ptr> cachedMetricDatas; + PM_FRAME_QUERY* activeFrameEventQuery = nullptr; std::vector cachedGpuInfo; std::vector cachedCpuInfo; uint32_t currentGpuInfoIndex = UINT32_MAX; From cd420c709a7cffaf80488d1f8a29e49ca2045737 Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 16 Jan 2026 14:58:58 +0900 Subject: [PATCH 115/205] integration test for fq+adding in placeholders for missing FrameMetrics --- IntelPresentMon/PresentMonAPI2Tests/Folders.h | 5 + .../IpcMcIntegrationTests.cpp | 137 ++++++++++++++++++ .../PresentMonAPI2Tests/ModuleInit.cpp | 1 + .../PresentMonAPI2Tests.vcxproj | 4 +- .../PresentMonAPI2Tests.vcxproj.filters | 11 +- .../PresentMonMiddleware/FrameEventQuery.cpp | 84 +++++------ .../PresentMonMiddleware/FrameEventQuery.h | 3 +- 7 files changed, 198 insertions(+), 47 deletions(-) create mode 100644 IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp diff --git a/IntelPresentMon/PresentMonAPI2Tests/Folders.h b/IntelPresentMon/PresentMonAPI2Tests/Folders.h index 00b97c6f..619abefb 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/Folders.h +++ b/IntelPresentMon/PresentMonAPI2Tests/Folders.h @@ -34,3 +34,8 @@ namespace IpcComponentTests static constexpr const char* logFolder_ = "TestLogs\\IpcComponent"; static constexpr const char* outFolder_ = "TestOutput\\IpcComponent"; } + +namespace IpcMcIntegrationTests +{ + static constexpr const char* logFolder_ = "TestLogs\\IpcMcIntegration"; +} diff --git a/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp new file mode 100644 index 00000000..21f54b10 --- /dev/null +++ b/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp @@ -0,0 +1,137 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: MIT +#include "../CommonUtilities/win/WinAPI.h" +#include "CppUnitTest.h" +#include "TestProcess.h" +#include "Folders.h" + +#include "../PresentMonAPIWrapper/PresentMonAPIWrapper.h" +#include "../Interprocess/source/SystemDeviceId.h" + +#include +#include +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace std::chrono_literals; + +namespace IpcMcIntegrationTests +{ + namespace ipc = pmon::ipc; + + class TestFixture : public CommonTestFixture + { + public: + const CommonProcessArgs& GetCommonArgs() const override + { + static CommonProcessArgs args{ + .ctrlPipe = R"(\\.\pipe\pm-ipcmc-int-ctrl)", + .shmNamePrefix = "pm_ipcmc_int", + .logLevel = "verbose", + .logVerboseModules = "ipc_sto met_use", + .logFolder = logFolder_, + .sampleClientMode = "NONE", + }; + return args; + } + }; + + static std::vector BuildUniversalFrameQueryElements_(const pmapi::intro::Root& intro) + { + std::vector elements; + for (auto metric : intro.GetMetrics()) { + if (!pmapi::intro::MetricTypeIsFrameEvent(metric.GetType())) { + continue; + } + for (auto info : metric.GetDeviceMetricInfo()) { + if (info.GetDevice().GetId() != ipc::kUniversalDeviceId) { + continue; + } + if (!info.IsAvailable()) { + break; + } + const uint32_t arraySize = info.GetArraySize(); + for (uint32_t index = 0; index < arraySize; ++index) { + elements.push_back(PM_QUERY_ELEMENT{ + .metric = metric.GetId(), + .stat = PM_STAT_NONE, + .deviceId = ipc::kUniversalDeviceId, + .arrayIndex = index, + .dataOffset = 0, + .dataSize = 0, + }); + } + break; + } + } + return elements; + } + + TEST_CLASS(IpcMcIntegrationTests) + { + TestFixture fixture_; + + public: + TEST_METHOD_INITIALIZE(Setup) + { + fixture_.Setup(); + } + + TEST_METHOD_CLEANUP(Cleanup) + { + fixture_.Cleanup(); + } + + TEST_METHOD(UniversalFrameQueryConsumesPresenterFrames) + { + pmapi::Session session{ fixture_.GetCommonArgs().ctrlPipe }; + auto intro = session.GetIntrospectionRoot(); + Assert::IsTrue((bool)intro); + + auto elements = BuildUniversalFrameQueryElements_(*intro); + Logger::WriteMessage(std::format("Universal frame metrics: {}\n", elements.size()).c_str()); + Assert::IsTrue(!elements.empty(), L"No universal frame metrics found"); + + auto query = session.RegisterFrameQuery(elements); + + auto presenter = fixture_.LaunchPresenter(); + session.SetEtwFlushPeriod(8); + std::this_thread::sleep_for(1ms); + auto tracker = session.TrackProcess(presenter.GetId()); + + auto blobs = query.MakeBlobContainer(16); + bool gotFrames = false; + const auto deadline = std::chrono::steady_clock::now() + 2s; + while (std::chrono::steady_clock::now() < deadline) { + query.Consume(tracker, blobs); + if (blobs.GetNumBlobsPopulated() > 0) { + gotFrames = true; + break; + } + std::this_thread::sleep_for(25ms); + } + Assert::IsTrue(gotFrames, L"Expected frame query to consume frames"); + } + + TEST_METHOD(SecondFrameQueryRegistrationFails) + { + pmapi::Session session{ fixture_.GetCommonArgs().ctrlPipe }; + auto intro = session.GetIntrospectionRoot(); + Assert::IsTrue((bool)intro); + + auto elements = BuildUniversalFrameQueryElements_(*intro); + Assert::IsTrue(!elements.empty(), L"No universal frame metrics found"); + + // first registration succeeds + std::vector single{ elements.front() }; + auto query = session.RegisterFrameQuery(single); + Assert::IsTrue((bool)query); + + // second registration fails + Assert::ExpectException([&] { + session.RegisterFrameQuery(single); + }); + } + }; +} diff --git a/IntelPresentMon/PresentMonAPI2Tests/ModuleInit.cpp b/IntelPresentMon/PresentMonAPI2Tests/ModuleInit.cpp index c90a09f4..4999900b 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/ModuleInit.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/ModuleInit.cpp @@ -38,4 +38,5 @@ TEST_MODULE_INITIALIZE(Api2TestModuleInit) WipeAndRecreate(PacedFrame::outFolder_); WipeAndRecreate(InterimBroadcasterTests::logFolder_); WipeAndRecreate(InterimBroadcasterTests::outFolder_); + WipeAndRecreate(IpcMcIntegrationTests::logFolder_); } diff --git a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj index 7ebfef85..9754fb5f 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj +++ b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj @@ -99,6 +99,7 @@ + @@ -138,6 +139,7 @@ + @@ -146,4 +148,4 @@ - + \ No newline at end of file diff --git a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj.filters b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj.filters index 4087f71c..18d0d13c 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj.filters +++ b/IntelPresentMon/PresentMonAPI2Tests/PresentMonAPI2Tests.vcxproj.filters @@ -1,4 +1,4 @@ - + @@ -45,6 +45,12 @@ Source Files + + Source Files + + + Source Files + @@ -68,5 +74,8 @@ Header Files + + Header Files + \ No newline at end of file diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index 5fcc18b0..1587957a 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -139,7 +139,7 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M cmd.qpcToMs = 0.0; } else if (q.deviceId == ipc::kUniversalDeviceId) { - cmd = MapQueryElementToFrameGatherCommand_(q, blobCursor); + cmd = MapQueryElementToFrameGatherCommand_(q, blobCursor, metricView); if (cmd.gatherType == PM_DATA_TYPE_VOID) { pmlog_error("Unsupported frame metric in frame query") @@ -197,13 +197,13 @@ size_t PM_FRAME_QUERY::GetBlobSize() const return blobSize_; } -PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherCommand_(const PM_QUERY_ELEMENT& q, size_t blobByteCursor) +PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherCommand_(const PM_QUERY_ELEMENT& q, size_t blobByteCursor, const pmapi::intro::MetricView& metricView) { const double qpcToMs = util::GetTimestampPeriodSeconds() * 1000.0; GatherCommand_ cmd{}; cmd.metricId = q.metric; - cmd.gatherType = PM_DATA_TYPE_VOID; + cmd.gatherType = metricView.GetDataTypeInfo().GetFrameType(); cmd.blobOffset = static_cast(blobByteCursor); cmd.frameMetricsOffset = 0u; cmd.deviceId = q.deviceId; @@ -212,129 +212,132 @@ PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherComma cmd.qpcToMs = 0.0; switch (q.metric) { + case PM_METRIC_ALLOWS_TEARING: + // TODO: fix this placeholder + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, util::metrics::FrameMetrics::cpuStartQpc); + break; + case PM_METRIC_PRESENT_RUNTIME: + // TODO: fix this placeholder + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, util::metrics::FrameMetrics::cpuStartQpc); + break; + case PM_METRIC_PRESENT_MODE: + // TODO: fix this placeholder + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, util::metrics::FrameMetrics::cpuStartQpc); + break; + case PM_METRIC_PRESENT_FLAGS: + // TODO: fix this placeholder + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, util::metrics::FrameMetrics::cpuStartQpc); + break; + case PM_METRIC_SYNC_INTERVAL: + // TODO: fix this placeholder + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, util::metrics::FrameMetrics::cpuStartQpc); + break; + case PM_METRIC_SWAP_CHAIN_ADDRESS: + // TODO: fix this placeholder + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, util::metrics::FrameMetrics::cpuStartQpc); + break; case PM_METRIC_PRESENT_START_QPC: - cmd.gatherType = PM_DATA_TYPE_UINT64; + // TODO: fix cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, timeInSeconds); break; case PM_METRIC_PRESENT_START_TIME: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; + // TODO: fix cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, timeInSeconds); cmd.qpcToMs = qpcToMs; break; case PM_METRIC_CPU_START_QPC: - cmd.gatherType = PM_DATA_TYPE_UINT64; + // TODO: fix cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, cpuStartQpc); break; case PM_METRIC_CPU_START_TIME: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; + // TODO: fix cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, cpuStartQpc); cmd.qpcToMs = qpcToMs; break; case PM_METRIC_BETWEEN_PRESENTS: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msBetweenPresents); break; case PM_METRIC_IN_PRESENT_API: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msInPresentApi); break; case PM_METRIC_RENDER_PRESENT_LATENCY: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msUntilRenderComplete); break; case PM_METRIC_BETWEEN_DISPLAY_CHANGE: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msBetweenDisplayChange); break; case PM_METRIC_UNTIL_DISPLAYED: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msUntilDisplayed); break; case PM_METRIC_DISPLAYED_TIME: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msDisplayedTime); break; case PM_METRIC_DISPLAY_LATENCY: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msDisplayLatency); break; case PM_METRIC_BETWEEN_SIMULATION_START: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msBetweenSimStarts); cmd.isOptional = true; break; case PM_METRIC_PC_LATENCY: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msPcLatency); cmd.isOptional = true; break; case PM_METRIC_CPU_BUSY: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msCPUBusy); break; case PM_METRIC_CPU_WAIT: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msCPUWait); break; case PM_METRIC_CPU_FRAME_TIME: case PM_METRIC_BETWEEN_APP_START: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; break; case PM_METRIC_GPU_LATENCY: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msGPULatency); break; case PM_METRIC_GPU_TIME: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; break; case PM_METRIC_GPU_BUSY: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msGPUBusy); break; case PM_METRIC_GPU_WAIT: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msGPUWait); break; case PM_METRIC_DROPPED_FRAMES: - cmd.gatherType = PM_DATA_TYPE_BOOL; break; case PM_METRIC_ANIMATION_ERROR: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msAnimationError); cmd.isOptional = true; break; case PM_METRIC_ANIMATION_TIME: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msAnimationTime); cmd.isOptional = true; break; case PM_METRIC_CLICK_TO_PHOTON_LATENCY: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msClickToPhotonLatency); cmd.isOptional = true; break; case PM_METRIC_ALL_INPUT_TO_PHOTON_LATENCY: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msAllInputPhotonLatency); cmd.isOptional = true; break; case PM_METRIC_INSTRUMENTED_LATENCY: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msInstrumentedLatency); cmd.isOptional = true; break; case PM_METRIC_FLIP_DELAY: - cmd.gatherType = PM_DATA_TYPE_DOUBLE; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msFlipDelay); cmd.isOptional = true; break; case PM_METRIC_FRAME_TYPE: - cmd.gatherType = PM_DATA_TYPE_ENUM; cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, frameType); break; default: - pmlog_error("unknown metric id").pmwatch((int)q.metric).diag(); - return {}; + pmlog_error("Unexpected frame metric in frame query") + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch(metricView.IntrospectType().GetSymbol()) + .pmwatch((int)q.metric).diag(); + throw Except("Unexpected frame metric in frame query"); } return cmd; } @@ -410,10 +413,8 @@ void PM_FRAME_QUERY::GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* *reinterpret_cast(pFrameMemberBytes); break; case PM_DATA_TYPE_STRING: - std::copy_n(reinterpret_cast(pFrameMemberBytes), PM_MAX_PATH, - reinterpret_cast(pBlobBytes + cmd.blobOffset)); - break; case PM_DATA_TYPE_VOID: + pmlog_error("Unsupported frame data type"); break; } } @@ -426,12 +427,6 @@ void PM_FRAME_QUERY::GatherFromTelemetry_(const GatherCommand_& cmd, uint8_t* pB *reinterpret_cast(pBlobBytes + cmd.blobOffset) = teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; break; - case PM_DATA_TYPE_INT32: - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = 0; - break; - case PM_DATA_TYPE_UINT32: - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = 0u; - break; case PM_DATA_TYPE_DOUBLE: *reinterpret_cast(pBlobBytes + cmd.blobOffset) = teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; @@ -444,10 +439,11 @@ void PM_FRAME_QUERY::GatherFromTelemetry_(const GatherCommand_& cmd, uint8_t* pB *reinterpret_cast(pBlobBytes + cmd.blobOffset) = teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; break; + case PM_DATA_TYPE_INT32: + case PM_DATA_TYPE_UINT32: case PM_DATA_TYPE_STRING: - std::fill_n(pBlobBytes + cmd.blobOffset, PM_MAX_PATH, 0); - break; case PM_DATA_TYPE_VOID: + pmlog_error("Unsupported telemetry data type"); break; } } diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h index c956ad04..e3fcc156 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h @@ -8,6 +8,7 @@ namespace pmapi::intro { class Root; + class MetricView; } namespace pmon::ipc @@ -47,7 +48,7 @@ struct PM_FRAME_QUERY double qpcToMs; }; // functions - static GatherCommand_ MapQueryElementToFrameGatherCommand_(const PM_QUERY_ELEMENT& q, size_t blobCursor); + static GatherCommand_ MapQueryElementToFrameGatherCommand_(const PM_QUERY_ELEMENT& q, size_t blobCursor, const pmapi::intro::MetricView& metricView); static void GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* pBlobBytes, const pmon::util::metrics::FrameMetrics& frameMetrics); void GatherFromTelemetry_(const GatherCommand_& cmd, uint8_t* pBlobBytes, int64_t searchQpc, const pmon::ipc::TelemetryMap& teleMap) const; From 8447bebbe208b0ccb0f384470b30ab8bf516c297 Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 16 Jan 2026 15:39:02 +0900 Subject: [PATCH 116/205] better status codes for frame query errors --- .../Interprocess/source/metadata/EnumStatus.h | 4 +++- .../PresentMonAPI2/PresentMonAPI.h | 2 ++ .../ConcreteMiddleware.cpp | 2 +- .../PresentMonMiddleware/FrameEventQuery.cpp | 23 ++++++++++--------- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/metadata/EnumStatus.h b/IntelPresentMon/Interprocess/source/metadata/EnumStatus.h index bd3f23f1..4103b9de 100644 --- a/IntelPresentMon/Interprocess/source/metadata/EnumStatus.h +++ b/IntelPresentMon/Interprocess/source/metadata/EnumStatus.h @@ -23,4 +23,6 @@ X_(STATUS, MIDDLEWARE_MISSING_ENDPOINT, "Middleware Missing Endpoint", "", "A required endpoint function was not found in the Middleware DLL") \ X_(STATUS, MIDDLEWARE_VERSION_LOW, "Middleware Version Low", "", "Middleware DLL version was found to be too low for compatibility") \ X_(STATUS, MIDDLEWARE_VERSION_HIGH, "Middleware Version High", "", "Middleware DLL version was found to be too high for compatibility") \ - X_(STATUS, MIDDLEWARE_SERVICE_MISMATCH, "Middleware Service Mismatch", "", "Middleware DLL build ID does not match that of the service") \ No newline at end of file + X_(STATUS, MIDDLEWARE_SERVICE_MISMATCH, "Middleware Service Mismatch", "", "Middleware DLL build ID does not match that of the service") \ + X_(STATUS, QUERY_MALFORMED, "Query Malformed", "", "There are errors or inconsistencies in the elements of the query being registered") \ + X_(STATUS, QUERY_QUOTA_EXCEEDED, "Query Quota Exceeded", "", "Limit on the number of concurrently registered queries is exceeded (1 max for frame queries)") \ No newline at end of file diff --git a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h index 075e0195..44142c3c 100644 --- a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h +++ b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h @@ -42,6 +42,8 @@ extern "C" { PM_STATUS_MIDDLEWARE_VERSION_LOW, PM_STATUS_MIDDLEWARE_VERSION_HIGH, PM_STATUS_MIDDLEWARE_SERVICE_MISMATCH, + PM_STATUS_QUERY_MALFORMED, + PM_STATUS_QUERY_QUOTA_EXCEEDED, }; enum PM_METRIC diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index f9dbd9f4..9087bec0 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -1269,7 +1269,7 @@ static void ReportMetrics( { if (activeFrameEventQuery != nullptr) { pmlog_error("Frame event query already registered").diag(); - throw Except("Frame event query already registered"); + throw Except(PM_STATUS_QUERY_QUOTA_EXCEEDED, "Frame event query already registered"); } const auto pQuery = new PM_FRAME_QUERY{ queryElements, *pComms }; activeFrameEventQuery = pQuery; diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index 1587957a..4698a1d1 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -4,6 +4,7 @@ #include "../Interprocess/source/Interprocess.h" #include "../Interprocess/source/SystemDeviceId.h" #include "../Interprocess/source/IntrospectionHelpers.h" +#include "../Interprocess/source/PmStatusError.h" #include "../CommonUtilities/log/Log.h" #include "../CommonUtilities/Exception.h" #include "../CommonUtilities/Memory.h" @@ -23,14 +24,14 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M { if (queryElements.empty()) { pmlog_error("Frame query requires at least one query element").diag(); - throw Except("Empty frame query"); + throw Except(PM_STATUS_QUERY_MALFORMED, "Empty frame query"); } // TODO: pass in from middleware cache rather than marshalling over anew from comms const auto pIntro = comms.GetIntrospectionRoot(); if (!pIntro) { pmlog_error("Failed to acquire introspection root for frame query").diag(); - throw Except("No introspection root for frame query"); + throw Except(PM_STATUS_QUERY_MALFORMED, "No introspection root for frame query"); } // TODO: consider direct use (unwrapped) pmapi::intro::Root introRoot{ pIntro, [](const PM_INTROSPECTION_ROOT* p) { @@ -67,7 +68,7 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M if (!pmapi::intro::MetricTypeIsFrameEvent(metricView.GetType())) { pmlog_error("Non-frame metric used in frame query") .pmwatch(metricView.Introspect().GetSymbol()).diag(); - throw Except("Frame query contains non-frame metric"); + throw Except(PM_STATUS_QUERY_MALFORMED, "Frame query contains non-frame metric"); } if (q.stat != PM_STAT_NONE) { @@ -81,7 +82,7 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M if (frameTypeSize == 0) { pmlog_error("Unsupported frame query data type") .pmwatch(metricView.Introspect().GetSymbol()).diag(); - throw Except("Unsupported frame query data type"); + throw Except(PM_STATUS_QUERY_MALFORMED, "Unsupported frame query data type"); } if (q.deviceId != ipc::kUniversalDeviceId) { @@ -101,7 +102,7 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M pmlog_error("Metric not supported by device in frame query") .pmwatch(metricView.Introspect().GetSymbol()) .pmwatch(q.deviceId).diag(); - throw Except("Metric not supported by device in frame query"); + throw Except(PM_STATUS_QUERY_MALFORMED, "Metric not supported by device in frame query"); } const auto arraySize = deviceMetricInfo->GetArraySize(); @@ -110,7 +111,7 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M .pmwatch(metricView.Introspect().GetSymbol()) .pmwatch(q.arrayIndex) .pmwatch(arraySize).diag(); - throw Except("Frame query array index out of bounds"); + throw Except(PM_STATUS_QUERY_MALFORMED, "Frame query array index out of bounds"); } const auto alignment = GetDataTypeAlignment(frameType); @@ -126,7 +127,7 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M pmlog_error("Telemetry ring missing for metric in frame query") .pmwatch(metricView.Introspect().GetSymbol()) .pmwatch(q.deviceId).diag(); - throw Except("Telemetry ring missing for metric in frame query"); + throw Except(PM_STATUS_QUERY_MALFORMED, "Telemetry ring missing for metric in frame query"); } cmd.metricId = q.metric; @@ -144,7 +145,7 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M if (cmd.gatherType == PM_DATA_TYPE_VOID) { pmlog_error("Unsupported frame metric in frame query") .pmwatch(metricView.Introspect().GetSymbol()).diag(); - throw Except("Unsupported frame metric in frame query"); + throw Except(PM_STATUS_QUERY_MALFORMED, "Unsupported frame metric in frame query"); } const auto gatherTypeSize = ipc::intro::GetDataTypeSize(cmd.gatherType); @@ -153,13 +154,13 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M .pmwatch(metricView.Introspect().GetSymbol()) .pmwatch(gatherTypeSize) .pmwatch(frameTypeSize).diag(); - throw Except("Frame query type mismatch"); + throw Except(PM_STATUS_QUERY_MALFORMED, "Frame query type mismatch"); } } else { pmlog_error("Invalid device id in frame query") .pmwatch(q.deviceId).diag(); - throw Except("Invalid device id in frame query"); + throw Except(PM_STATUS_QUERY_MALFORMED, "Invalid device id in frame query"); } q.dataOffset = blobCursor; @@ -337,7 +338,7 @@ PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherComma .pmwatch(metricView.Introspect().GetSymbol()) .pmwatch(metricView.IntrospectType().GetSymbol()) .pmwatch((int)q.metric).diag(); - throw Except("Unexpected frame metric in frame query"); + throw Except(PM_STATUS_QUERY_MALFORMED, "Unexpected frame metric in frame query"); } return cmd; } From 27f46f8e30ae7a035e0aa1fe3aba7d060395c579 Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 16 Jan 2026 16:15:24 +0900 Subject: [PATCH 117/205] improve introspection injection in fq --- .../PresentMonMiddleware/ConcreteMiddleware.cpp | 7 +++---- .../PresentMonMiddleware/FrameEventQuery.cpp | 15 +++------------ .../PresentMonMiddleware/FrameEventQuery.h | 3 ++- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index 9087bec0..614ee880 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -1271,10 +1271,9 @@ static void ReportMetrics( pmlog_error("Frame event query already registered").diag(); throw Except(PM_STATUS_QUERY_QUOTA_EXCEEDED, "Frame event query already registered"); } - const auto pQuery = new PM_FRAME_QUERY{ queryElements, *pComms }; - activeFrameEventQuery = pQuery; - blobSize = (uint32_t)pQuery->GetBlobSize(); - return pQuery; + activeFrameEventQuery = new PM_FRAME_QUERY{ queryElements, *pComms, GetIntrospectionRoot() }; + blobSize = (uint32_t)activeFrameEventQuery->GetBlobSize(); + return activeFrameEventQuery; } void mid::ConcreteMiddleware::FreeFrameEventQuery(const PM_FRAME_QUERY* pQuery) diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index 4698a1d1..0328c6c8 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -18,7 +18,8 @@ namespace util = pmon::util; using namespace util; -PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::MiddlewareComms& comms) +PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::MiddlewareComms& comms, + const pmapi::intro::Root& introRoot) : comms_{ comms } { @@ -27,17 +28,6 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M throw Except(PM_STATUS_QUERY_MALFORMED, "Empty frame query"); } - // TODO: pass in from middleware cache rather than marshalling over anew from comms - const auto pIntro = comms.GetIntrospectionRoot(); - if (!pIntro) { - pmlog_error("Failed to acquire introspection root for frame query").diag(); - throw Except(PM_STATUS_QUERY_MALFORMED, "No introspection root for frame query"); - } - // TODO: consider direct use (unwrapped) - pmapi::intro::Root introRoot{ pIntro, [](const PM_INTROSPECTION_ROOT* p) { - free(const_cast(p)); - } }; - // TODO: use data type ipc bridger auto GetDataTypeAlignment = [](PM_DATA_TYPE dataType) { switch (dataType) { @@ -56,6 +46,7 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M case PM_DATA_TYPE_STRING: return alignof(char); default: + pmlog_error("Bad data type in alignment calculation"); return size_t{ 1u }; } }; diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h index e3fcc156..cf82dcb1 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h @@ -21,7 +21,8 @@ struct PM_FRAME_QUERY { public: // functions - PM_FRAME_QUERY(std::span queryElements, pmon::ipc::MiddlewareComms& comms); + PM_FRAME_QUERY(std::span queryElements, pmon::ipc::MiddlewareComms& comms, + const pmapi::intro::Root& introRoot); ~PM_FRAME_QUERY(); void GatherToBlob(uint8_t* pBlobBytes, const pmon::util::metrics::FrameMetrics& frameMetrics) const; size_t GetBlobSize() const; From a5869ca0d855013b7ecb74d080e956ce7371ceef Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 16 Jan 2026 16:18:17 +0900 Subject: [PATCH 118/205] validate device id in fq reg --- IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index 0328c6c8..8bb6bc41 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -1,6 +1,7 @@ // Copyright (C) 2017-2024 Intel Corporation #include "FrameEventQuery.h" #include "../PresentMonAPIWrapperCommon/Introspection.h" +#include "../PresentMonAPIWrapperCommon/Exception.h" #include "../Interprocess/source/Interprocess.h" #include "../Interprocess/source/SystemDeviceId.h" #include "../Interprocess/source/IntrospectionHelpers.h" @@ -77,7 +78,13 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M } if (q.deviceId != ipc::kUniversalDeviceId) { - // TODO: check that device exists in introspection + try { + introRoot.FindDevice(q.deviceId); + } + catch (const pmapi::LookupException&) { + pmlog_error(util::ReportException("Registering frame query")); + throw Except(PM_STATUS_QUERY_MALFORMED, "Invalid device ID"); + } } const auto deviceMetricInfo = [&]() -> std::optional { From 0c2634ce64fc8b5b3321ac589806dc8d7fe0945c Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 16 Jan 2026 16:42:40 +0900 Subject: [PATCH 119/205] PM_DATA_TYPE alignment helper using static tmp bridge --- .../source/IntrospectionDataTypeMapping.h | 9 ++++-- .../source/IntrospectionHelpers.cpp | 14 +++++++-- .../source/IntrospectionHelpers.h | 5 +-- .../PresentMonMiddleware/FrameEventQuery.cpp | 31 +++---------------- .../FrameMetricsSource.cpp | 6 ++-- 5 files changed, 29 insertions(+), 36 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/IntrospectionDataTypeMapping.h b/IntelPresentMon/Interprocess/source/IntrospectionDataTypeMapping.h index d7cca97e..779bba89 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionDataTypeMapping.h +++ b/IntelPresentMon/Interprocess/source/IntrospectionDataTypeMapping.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "../../PresentMonAPI2/PresentMonAPI.h" #include "metadata/EnumDataType.h" #include "metadata/MasterEnumList.h" @@ -38,6 +38,11 @@ namespace pmon::ipc::intro { template<> constexpr size_t DataTypeToStaticType_sz = 0ull; + template + constexpr size_t DataTypeToStaticType_align = alignof(DataTypeToStaticType_t); + template<> + constexpr size_t DataTypeToStaticType_align = 1ull; + template class F, typename...P> auto BridgeDataType(PM_DATA_TYPE dataType, P&&...args) { @@ -87,4 +92,4 @@ namespace pmon::ipc::intro { } return F::Default(std::forward

(args)...); } -} \ No newline at end of file +} diff --git a/IntelPresentMon/Interprocess/source/IntrospectionHelpers.cpp b/IntelPresentMon/Interprocess/source/IntrospectionHelpers.cpp index 69fd5f43..7c074706 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionHelpers.cpp +++ b/IntelPresentMon/Interprocess/source/IntrospectionHelpers.cpp @@ -1,4 +1,4 @@ -#include "IntrospectionHelpers.h" +#include "IntrospectionHelpers.h" #include "metadata/EnumDataType.h" #include "IntrospectionMacroHelpers.h" #include "IntrospectionDataTypeMapping.h" @@ -11,8 +11,18 @@ namespace pmon::ipc::intro static size_t Default() { return 0ull; } }; + template struct DataTypeAlignmentBridger { + static size_t Invoke() { return DataTypeToStaticType_align

; } + static size_t Default() { return 1ull; } + }; + size_t GetDataTypeSize(PM_DATA_TYPE dt) { return BridgeDataType(dt); } -} \ No newline at end of file + + size_t GetDataTypeAlignment(PM_DATA_TYPE dt) + { + return BridgeDataType(dt); + } +} diff --git a/IntelPresentMon/Interprocess/source/IntrospectionHelpers.h b/IntelPresentMon/Interprocess/source/IntrospectionHelpers.h index 74db25c9..47d9078d 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionHelpers.h +++ b/IntelPresentMon/Interprocess/source/IntrospectionHelpers.h @@ -1,7 +1,8 @@ -#pragma once +#pragma once #include "../../PresentMonAPI2/PresentMonAPI.h" namespace pmon::ipc::intro { size_t GetDataTypeSize(PM_DATA_TYPE v); -} \ No newline at end of file + size_t GetDataTypeAlignment(PM_DATA_TYPE v); +} diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index 8bb6bc41..aa298b2f 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -29,29 +29,6 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M throw Except(PM_STATUS_QUERY_MALFORMED, "Empty frame query"); } - // TODO: use data type ipc bridger - auto GetDataTypeAlignment = [](PM_DATA_TYPE dataType) { - switch (dataType) { - case PM_DATA_TYPE_DOUBLE: - return alignof(double); - case PM_DATA_TYPE_UINT64: - return alignof(uint64_t); - case PM_DATA_TYPE_INT32: - return alignof(int32_t); - case PM_DATA_TYPE_UINT32: - return alignof(uint32_t); - case PM_DATA_TYPE_ENUM: - return alignof(int); - case PM_DATA_TYPE_BOOL: - return alignof(bool); - case PM_DATA_TYPE_STRING: - return alignof(char); - default: - pmlog_error("Bad data type in alignment calculation"); - return size_t{ 1u }; - } - }; - size_t blobCursor = 0; gatherCommands_.reserve(queryElements.size()); @@ -112,7 +89,7 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M throw Except(PM_STATUS_QUERY_MALFORMED, "Frame query array index out of bounds"); } - const auto alignment = GetDataTypeAlignment(frameType); + const auto alignment = ipc::intro::GetDataTypeAlignment(frameType); blobCursor = util::PadToAlignment(blobCursor, alignment); GatherCommand_ cmd{}; @@ -130,7 +107,7 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M cmd.metricId = q.metric; cmd.gatherType = frameType; - cmd.blobOffset = static_cast(blobCursor); + cmd.blobOffset = uint32_t(blobCursor); cmd.frameMetricsOffset = 0u; cmd.deviceId = q.deviceId; cmd.arrayIdx = q.arrayIndex; @@ -203,7 +180,7 @@ PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherComma GatherCommand_ cmd{}; cmd.metricId = q.metric; cmd.gatherType = metricView.GetDataTypeInfo().GetFrameType(); - cmd.blobOffset = static_cast(blobByteCursor); + cmd.blobOffset = uint32_t(blobByteCursor); cmd.frameMetricsOffset = 0u; cmd.deviceId = q.deviceId; cmd.arrayIdx = q.arrayIndex; @@ -361,7 +338,7 @@ void PM_FRAME_QUERY::GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* } if (cmd.metricId == PM_METRIC_PRESENT_START_TIME || cmd.metricId == PM_METRIC_CPU_START_TIME) { *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - static_cast(*reinterpret_cast(pFrameMemberBytes)) * cmd.qpcToMs; + double(*reinterpret_cast(pFrameMemberBytes)) * cmd.qpcToMs; return; } if (frameMetrics.screenTimeQpc == 0 && diff --git a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp index 3323d2e9..bbdfca45 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp @@ -70,7 +70,7 @@ namespace pmon::mid qpcFrequency_{} { const double period = util::GetTimestampPeriodSeconds(); - qpcFrequency_ = period == 0.0 ? 0 : static_cast(1.0 / period + 0.5); + qpcFrequency_ = period == 0.0 ? 0 : uint64_t(1.0 / period + 0.5); Open_(); } @@ -83,7 +83,7 @@ namespace pmon::mid { comms_.OpenFrameDataStore(processId_); pStore_ = &comms_.GetFrameDataStore(processId_); - sessionStartQpc_ = static_cast(pStore_->bookkeeping.startQpc); + sessionStartQpc_ = uint64_t(pStore_->bookkeeping.startQpc); const auto range = pStore_->frameData.GetSerialRange(); nextFrameSerial_ = range.first; } @@ -107,7 +107,7 @@ namespace pmon::mid } if (sessionStartQpc_ == 0 && pStore_->bookkeeping.startQpc != 0) { - sessionStartQpc_ = static_cast(pStore_->bookkeeping.startQpc); + sessionStartQpc_ = uint64_t(pStore_->bookkeeping.startQpc); } const auto& ring = pStore_->frameData; From 768259231fe7266ffeca4f60a0b917b6924fc146 Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 16 Jan 2026 16:58:34 +0900 Subject: [PATCH 120/205] folding in helper functions --- .../FrameMetricsSource.cpp | 33 +++++++++---------- .../PresentMonMiddleware/FrameMetricsSource.h | 2 -- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp index bbdfca45..925d101a 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp @@ -71,16 +71,7 @@ namespace pmon::mid { const double period = util::GetTimestampPeriodSeconds(); qpcFrequency_ = period == 0.0 ? 0 : uint64_t(1.0 / period + 0.5); - Open_(); - } - - FrameMetricsSource::~FrameMetricsSource() - { - Close_(); - } - - void FrameMetricsSource::Open_() - { + // open the data store from ipc comms_.OpenFrameDataStore(processId_); pStore_ = &comms_.GetFrameDataStore(processId_); sessionStartQpc_ = uint64_t(pStore_->bookkeeping.startQpc); @@ -88,16 +79,22 @@ namespace pmon::mid nextFrameSerial_ = range.first; } - void FrameMetricsSource::Close_() + FrameMetricsSource::~FrameMetricsSource() { - if (pStore_ == nullptr) { - return; + // close the data store + try { + if (pStore_ == nullptr) { + return; + } + comms_.CloseFrameDataStore(processId_); + pStore_ = nullptr; + nextFrameSerial_ = 0; + sessionStartQpc_ = 0; + swapChains_.clear(); + } + catch (...) { + pmlog_error(util::ReportException("Error closing frame data store")); } - comms_.CloseFrameDataStore(processId_); - pStore_ = nullptr; - nextFrameSerial_ = 0; - sessionStartQpc_ = 0; - swapChains_.clear(); } void FrameMetricsSource::ProcessNewFrames_() diff --git a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h index 444e3801..714afbd0 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h +++ b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h @@ -43,8 +43,6 @@ namespace pmon::mid std::vector Consume(size_t maxFrames); private: - void Open_(); - void Close_(); void ProcessNewFrames_(); ipc::MiddlewareComms& comms_; From 6ac2b223dd51ced360650dee1ebad7e01a91b41f Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 16 Jan 2026 17:33:36 +0900 Subject: [PATCH 121/205] qpc converter improvement --- IntelPresentMon/CommonUtilities/Qpc.cpp | 18 ++++++++++++++---- IntelPresentMon/CommonUtilities/Qpc.h | 6 ++++-- .../FrameMetricsSource.cpp | 18 ++++-------------- .../PresentMonMiddleware/FrameMetricsSource.h | 3 +-- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/Qpc.cpp b/IntelPresentMon/CommonUtilities/Qpc.cpp index 90bfda37..b7e6832a 100644 --- a/IntelPresentMon/CommonUtilities/Qpc.cpp +++ b/IntelPresentMon/CommonUtilities/Qpc.cpp @@ -1,4 +1,4 @@ -#include "Qpc.h" +#include "Qpc.h" #include "win/WinAPI.h" #include "log/Log.h" #include @@ -14,13 +14,23 @@ namespace pmon::util } return (int64_t)timestamp.QuadPart; } - double GetTimestampPeriodSeconds() noexcept + double GetTimestampFrequencyDouble() noexcept + { + return double(GetTimestampFrequencyUint64()); + } + uint64_t GetTimestampFrequencyUint64() noexcept { LARGE_INTEGER freq; if (!QueryPerformanceFrequency(&freq)) { pmlog_error("qpc frequency failed").hr().every(5); + return 0; } - return 1.0 / double(freq.QuadPart); + return (uint64_t)freq.QuadPart; + } + double GetTimestampPeriodSeconds() noexcept + { + const auto frequency = GetTimestampFrequencyDouble(); + return frequency == 0.0 ? 0.0 : 1.0 / frequency; } void SpinWaitUntilTimestamp(int64_t timestamp) noexcept { @@ -109,4 +119,4 @@ namespace pmon::util { return TicksToMilliSeconds(tickCount); } -} \ No newline at end of file +} diff --git a/IntelPresentMon/CommonUtilities/Qpc.h b/IntelPresentMon/CommonUtilities/Qpc.h index 40e8b3e3..0f91a951 100644 --- a/IntelPresentMon/CommonUtilities/Qpc.h +++ b/IntelPresentMon/CommonUtilities/Qpc.h @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #include @@ -6,6 +6,8 @@ namespace pmon::util { int64_t GetCurrentTimestamp() noexcept; + double GetTimestampFrequencyDouble() noexcept; + uint64_t GetTimestampFrequencyUint64() noexcept; double GetTimestampPeriodSeconds() noexcept; void SpinWaitUntilTimestamp(int64_t timestamp) noexcept; double TimestampDeltaToSeconds(int64_t start, int64_t end, double period) noexcept; @@ -57,4 +59,4 @@ namespace pmon::util double msPerTick_{ 0.0 }; uint64_t sessionStartTimestamp_{ 0 }; }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp index 925d101a..a85e8ef0 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp @@ -66,15 +66,13 @@ namespace pmon::mid : comms_{ comms }, processId_{ processId }, - perSwapChainCapacity_{ perSwapChainCapacity == 0 ? size_t{ 1 } : perSwapChainCapacity }, - qpcFrequency_{} + perSwapChainCapacity_{ perSwapChainCapacity == 0 ? size_t{ 1 } : perSwapChainCapacity } { - const double period = util::GetTimestampPeriodSeconds(); - qpcFrequency_ = period == 0.0 ? 0 : uint64_t(1.0 / period + 0.5); // open the data store from ipc comms_.OpenFrameDataStore(processId_); pStore_ = &comms_.GetFrameDataStore(processId_); - sessionStartQpc_ = uint64_t(pStore_->bookkeeping.startQpc); + // TODO: potentially source qpc frequency from store's bookkeeping + qpcConverter_ = util::QpcConverter{ util::GetTimestampFrequencyUint64(), (uint64_t)pStore_->bookkeeping.startQpc}; const auto range = pStore_->frameData.GetSerialRange(); nextFrameSerial_ = range.first; } @@ -88,8 +86,6 @@ namespace pmon::mid } comms_.CloseFrameDataStore(processId_); pStore_ = nullptr; - nextFrameSerial_ = 0; - sessionStartQpc_ = 0; swapChains_.clear(); } catch (...) { @@ -103,10 +99,6 @@ namespace pmon::mid return; } - if (sessionStartQpc_ == 0 && pStore_->bookkeeping.startQpc != 0) { - sessionStartQpc_ = uint64_t(pStore_->bookkeeping.startQpc); - } - const auto& ring = pStore_->frameData; const auto range = ring.GetSerialRange(); @@ -117,13 +109,11 @@ namespace pmon::mid return; } - util::QpcConverter qpc{ qpcFrequency_, sessionStartQpc_ }; - for (size_t serial = nextFrameSerial_; serial < range.second; ++serial) { const auto& frame = ring.At(serial); auto [it, inserted] = swapChains_.try_emplace(frame.swapChainAddress, perSwapChainCapacity_); auto& state = it->second; - state.ProcessFrame(frame, qpc); + state.ProcessFrame(frame, *qpcConverter_); } nextFrameSerial_ = range.second; diff --git a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h index 714afbd0..52ac3e75 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h +++ b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h @@ -50,8 +50,7 @@ namespace pmon::mid uint32_t processId_ = 0; size_t perSwapChainCapacity_ = 0; size_t nextFrameSerial_ = 0; - uint64_t qpcFrequency_ = 0; - uint64_t sessionStartQpc_ = 0; + std::optional qpcConverter_; std::unordered_map swapChains_; }; } From 40fa50f41b725e7814b121d5b43d4c5f2a793a31 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Fri, 16 Jan 2026 10:37:20 -0800 Subject: [PATCH 122/205] Adding in additional metrics to calculator --- .../mc/MetricsCalculatorAnimation.cpp | 8 ++++++ .../mc/MetricsCalculatorCpuGpu.cpp | 2 ++ .../mc/MetricsCalculatorDisplay.cpp | 2 +- .../CommonUtilities/mc/MetricsTypes.h | 13 ++++++++- .../PresentMonMiddleware/FrameEventQuery.cpp | 27 +++++++------------ 5 files changed, 32 insertions(+), 20 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorAnimation.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorAnimation.cpp index bf828064..87d8dc0e 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorAnimation.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorAnimation.cpp @@ -105,6 +105,14 @@ namespace pmon::util::metrics out.msSinceInput = (present.inputTime == 0) ? 0.0 : qpc.DurationMilliSeconds(present.presentStartTime - present.inputTime); + + // Copy metadata + out.swapChainAddress = present.swapChainAddress; + out.runtime = present.runtime; + out.syncInterval = present.syncInterval; + out.presentFlags = present.presentFlags; + out.allowsTearing = present.supportsTearing; + out.presentMode = present.presentMode; } void CalculateAnimationMetrics( diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorCpuGpu.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorCpuGpu.cpp index 559886a1..6cc08a28 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorCpuGpu.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorCpuGpu.cpp @@ -155,10 +155,12 @@ namespace pmon::util::metrics { metrics.msCPUBusy = ComputeMsCpuBusy(qpc, chainState, present, isAppFrame); metrics.msCPUWait = ComputeMsCpuWait(qpc, present, isAppFrame); + metrics.msCPUTime = metrics.msCPUBusy + metrics.msCPUWait; metrics.msGPULatency = ComputeMsGpuLatency(qpc, chainState, present, isAppFrame); metrics.msGPUBusy = ComputeMsGpuBusy(qpc, present, isAppFrame); metrics.msVideoBusy = ComputeMsVideoBusy(qpc, present, isAppFrame); metrics.msGPUWait = ComputeMsGpuWait(qpc, present, isAppFrame); + metrics.msGPUTime = metrics.msGPUBusy + metrics.msGPUWait; } } \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorDisplay.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorDisplay.cpp index d67f9c5e..594d4034 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorDisplay.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorDisplay.cpp @@ -201,7 +201,7 @@ namespace pmon::util::metrics metrics.msFlipDelay = ComputeMsFlipDelay(qpc, present, isDisplayed); metrics.msDisplayLatency = ComputeMsDisplayLatency(qpc, swapChain, present, isDisplayed, screenTime); metrics.msReadyTimeToDisplayLatency = ComputeMsReadyTimeToDisplayLatency(qpc, present, isDisplayed, screenTime); - + metrics.isDroppedFrame = !isDisplayed; metrics.screenTimeQpc = screenTime; } } diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h index 90b8de8b..85bb1cce 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h @@ -101,6 +101,7 @@ namespace pmon::util::metrics { struct FrameMetrics { // Core Timing (always computed) uint64_t timeInSeconds = 0; + uint64_t cpuStartQpc = 0; double msBetweenPresents = 0; double msInPresentApi = 0; double msUntilRenderStart = 0; @@ -116,17 +117,19 @@ namespace pmon::util::metrics { double msBetweenDisplayChange = 0; uint64_t screenTimeQpc = 0; std::optional msReadyTimeToDisplayLatency; + bool isDroppedFrame = false; // CPU Metrics (app frames only) - uint64_t cpuStartQpc = 0; double msCPUBusy = 0; double msCPUWait = 0; + double msCPUTime = 0; // GPU Metrics (app frames only) double msGPULatency = 0; double msGPUBusy = 0; double msVideoBusy = 0; double msGPUWait = 0; + double msGPUTime = 0; // Input Latency (optional, app+displayed only) std::optional msClickToPhotonLatency = {}; @@ -150,5 +153,13 @@ namespace pmon::util::metrics { // Frame Classification FrameType frameType = {}; + + // Present Metadata + uint64_t swapChainAddress = 0; + Runtime runtime = {}; + int32_t syncInterval = 0; + uint32_t presentFlags = 0; + bool allowsTearing = false; + PresentMode presentMode = {}; }; } diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index aa298b2f..4eb4aa5f 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -189,28 +189,22 @@ PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherComma switch (q.metric) { case PM_METRIC_ALLOWS_TEARING: - // TODO: fix this placeholder - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, util::metrics::FrameMetrics::cpuStartQpc); + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, allowsTearing); break; case PM_METRIC_PRESENT_RUNTIME: - // TODO: fix this placeholder - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, util::metrics::FrameMetrics::cpuStartQpc); + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, runtime); break; case PM_METRIC_PRESENT_MODE: - // TODO: fix this placeholder - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, util::metrics::FrameMetrics::cpuStartQpc); + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, presentMode); break; case PM_METRIC_PRESENT_FLAGS: - // TODO: fix this placeholder - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, util::metrics::FrameMetrics::cpuStartQpc); + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, presentFlags); break; case PM_METRIC_SYNC_INTERVAL: - // TODO: fix this placeholder - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, util::metrics::FrameMetrics::cpuStartQpc); + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, syncInterval); break; case PM_METRIC_SWAP_CHAIN_ADDRESS: - // TODO: fix this placeholder - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, util::metrics::FrameMetrics::cpuStartQpc); + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, swapChainAddress); break; case PM_METRIC_PRESENT_START_QPC: // TODO: fix @@ -322,18 +316,15 @@ void PM_FRAME_QUERY::GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* { const auto pFrameMemberBytes = reinterpret_cast(&frameMetrics) + cmd.frameMetricsOffset; if (cmd.metricId == PM_METRIC_CPU_FRAME_TIME || cmd.metricId == PM_METRIC_BETWEEN_APP_START) { - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - frameMetrics.msCPUBusy + frameMetrics.msCPUWait; + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = frameMetrics.msCPUTime; return; } if (cmd.metricId == PM_METRIC_GPU_TIME) { - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - frameMetrics.msGPUBusy + frameMetrics.msGPUWait; + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = frameMetrics.msGPUTime; return; } if (cmd.metricId == PM_METRIC_DROPPED_FRAMES) { - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - frameMetrics.screenTimeQpc == 0; + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = frameMetrics.isDroppedFrame; return; } if (cmd.metricId == PM_METRIC_PRESENT_START_TIME || cmd.metricId == PM_METRIC_CPU_START_TIME) { From 9db147832b3d62c45f72ee28a519e6d6416ac067 Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Fri, 16 Jan 2026 15:05:14 -0800 Subject: [PATCH 123/205] Added support for presentStart* and cpuStart metrics --- .../CommonUtilities/mc/MetricsCalculator.cpp | 12 ++++++++++- .../mc/MetricsCalculatorAnimation.cpp | 11 ++++++++++ .../CommonUtilities/mc/MetricsTypes.cpp | 4 ++-- .../CommonUtilities/mc/MetricsTypes.h | 3 +++ .../PresentMonMiddleware/FrameEventQuery.cpp | 21 ++++++++----------- 5 files changed, 36 insertions(+), 15 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 998e64fe..80bfdbd8 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -74,9 +74,18 @@ namespace pmon::util::metrics *d.newInput2FrameStartEma; } } + + double ComputeCPUStartTimeMs( + const QpcConverter& qpc, + const uint64_t& CPUStartTimeQpc) + { + const auto startQpc = qpc.GetSessionStartTimestamp(); + return (startQpc != 0 && CPUStartTimeQpc != 0) + ? qpc.DeltaSignedMilliSeconds(startQpc, CPUStartTimeQpc) + : 0.0; + } } - // 2) Public entry points // ============================================================================ std::vector ComputeMetricsForPresent( @@ -297,6 +306,7 @@ namespace pmon::util::metrics metrics); metrics.cpuStartQpc = CalculateCPUStart(chain, present); + metrics.cpuStartMs = ComputeCPUStartTimeMs(qpc, metrics.cpuStartQpc); return result; } diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorAnimation.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorAnimation.cpp index 87d8dc0e..d3ab9a24 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorAnimation.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorAnimation.cpp @@ -72,6 +72,15 @@ namespace pmon::util::metrics return CalculateAnimationTime(qpc, chain.firstAppSimStartTime, currentSimStart); } + double ComputePresentStartTimeMs( + const QpcConverter& qpc, + const FrameData& present) + { + const auto startQpc = qpc.GetSessionStartTimestamp(); + return startQpc != 0 && present.presentStartTime != 0 + ? qpc.DeltaSignedMilliSeconds(startQpc, present.presentStartTime) + : 0.0; + } } @@ -82,6 +91,8 @@ namespace pmon::util::metrics FrameMetrics& out) { out.timeInSeconds = present.presentStartTime; + out.presentStartQpc = present.presentStartTime; + out.presentStartMs = ComputePresentStartTimeMs(qpc, present); // Calculate the delta from the previous present (if one exists) // to the current present diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp index 1bb1264f..5d755ac5 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp @@ -90,7 +90,7 @@ namespace pmon::util::metrics { frame.appSleepStartTime = p->AppSleepStartTime; frame.appSleepEndTime = p->AppSleepEndTime; frame.appSimStartTime = p->AppSimStartTime; - frame.appSleepEndTime = p->AppSleepEndTime; + frame.appSimEndTime = p->AppSimEndTime; frame.appRenderSubmitStartTime = p->AppRenderSubmitStartTime; frame.appRenderSubmitEndTime = p->AppRenderSubmitEndTime; frame.appPresentStartTime = p->AppPresentStartTime; @@ -144,7 +144,7 @@ namespace pmon::util::metrics { frame.appSleepStartTime = p.AppSleepStartTime; frame.appSleepEndTime = p.AppSleepEndTime; frame.appSimStartTime = p.AppSimStartTime; - frame.appSleepEndTime = p.AppSleepEndTime; + frame.appSimEndTime = p.AppSimEndTime; frame.appRenderSubmitStartTime = p.AppRenderSubmitStartTime; frame.appRenderSubmitEndTime = p.AppRenderSubmitEndTime; frame.appPresentStartTime = p.AppPresentStartTime; diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h index 85bb1cce..946bad29 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h @@ -101,7 +101,10 @@ namespace pmon::util::metrics { struct FrameMetrics { // Core Timing (always computed) uint64_t timeInSeconds = 0; + uint64_t presentStartQpc = 0; + double presentStartMs = 0; uint64_t cpuStartQpc = 0; + double cpuStartMs = 0; double msBetweenPresents = 0; double msInPresentApi = 0; double msUntilRenderStart = 0; diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index 4eb4aa5f..e6ef2043 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -207,22 +207,16 @@ PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherComma cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, swapChainAddress); break; case PM_METRIC_PRESENT_START_QPC: - // TODO: fix - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, timeInSeconds); + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, presentStartQpc); break; case PM_METRIC_PRESENT_START_TIME: - // TODO: fix - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, timeInSeconds); - cmd.qpcToMs = qpcToMs; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, presentStartMs); break; case PM_METRIC_CPU_START_QPC: - // TODO: fix cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, cpuStartQpc); break; case PM_METRIC_CPU_START_TIME: - // TODO: fix - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, cpuStartQpc); - cmd.qpcToMs = qpcToMs; + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, cpuStartMs); break; case PM_METRIC_BETWEEN_PRESENTS: cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msBetweenPresents); @@ -327,9 +321,12 @@ void PM_FRAME_QUERY::GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* *reinterpret_cast(pBlobBytes + cmd.blobOffset) = frameMetrics.isDroppedFrame; return; } - if (cmd.metricId == PM_METRIC_PRESENT_START_TIME || cmd.metricId == PM_METRIC_CPU_START_TIME) { - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - double(*reinterpret_cast(pFrameMemberBytes)) * cmd.qpcToMs; + if (cmd.metricId == PM_METRIC_PRESENT_START_TIME) { + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = frameMetrics.presentStartMs; + return; + } + if (cmd.metricId == PM_METRIC_CPU_START_TIME) { + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = frameMetrics.cpuStartMs; return; } if (frameMetrics.screenTimeQpc == 0 && From bb9053febea4870e69425f95cfe0ef364c22e267 Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 19 Jan 2026 04:59:28 +0900 Subject: [PATCH 124/205] cleanup some unnecessary branches and validations --- .../PresentMonMiddleware/FrameEventQuery.cpp | 46 +++---------------- 1 file changed, 6 insertions(+), 40 deletions(-) diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index e6ef2043..9709d01f 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -116,21 +116,6 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M } else if (q.deviceId == ipc::kUniversalDeviceId) { cmd = MapQueryElementToFrameGatherCommand_(q, blobCursor, metricView); - - if (cmd.gatherType == PM_DATA_TYPE_VOID) { - pmlog_error("Unsupported frame metric in frame query") - .pmwatch(metricView.Introspect().GetSymbol()).diag(); - throw Except(PM_STATUS_QUERY_MALFORMED, "Unsupported frame metric in frame query"); - } - - const auto gatherTypeSize = ipc::intro::GetDataTypeSize(cmd.gatherType); - if (gatherTypeSize != frameTypeSize) { - pmlog_error("Frame query type mismatch") - .pmwatch(metricView.Introspect().GetSymbol()) - .pmwatch(gatherTypeSize) - .pmwatch(frameTypeSize).diag(); - throw Except(PM_STATUS_QUERY_MALFORMED, "Frame query type mismatch"); - } } else { pmlog_error("Invalid device id in frame query") @@ -309,31 +294,12 @@ PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherComma void PM_FRAME_QUERY::GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* pBlobBytes, const util::metrics::FrameMetrics& frameMetrics) { const auto pFrameMemberBytes = reinterpret_cast(&frameMetrics) + cmd.frameMetricsOffset; - if (cmd.metricId == PM_METRIC_CPU_FRAME_TIME || cmd.metricId == PM_METRIC_BETWEEN_APP_START) { - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = frameMetrics.msCPUTime; - return; - } - if (cmd.metricId == PM_METRIC_GPU_TIME) { - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = frameMetrics.msGPUTime; - return; - } - if (cmd.metricId == PM_METRIC_DROPPED_FRAMES) { - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = frameMetrics.isDroppedFrame; - return; - } - if (cmd.metricId == PM_METRIC_PRESENT_START_TIME) { - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = frameMetrics.presentStartMs; - return; - } - if (cmd.metricId == PM_METRIC_CPU_START_TIME) { - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = frameMetrics.cpuStartMs; - return; - } - if (frameMetrics.screenTimeQpc == 0 && - (cmd.metricId == PM_METRIC_DISPLAYED_TIME || - cmd.metricId == PM_METRIC_DISPLAY_LATENCY || - cmd.metricId == PM_METRIC_UNTIL_DISPLAYED || - cmd.metricId == PM_METRIC_BETWEEN_DISPLAY_CHANGE)) { + // metrics relating to display decay to NaN when frame was dropped/not displayed at all + if (frameMetrics.isDroppedFrame && ( + cmd.metricId == PM_METRIC_DISPLAYED_TIME || + cmd.metricId == PM_METRIC_DISPLAY_LATENCY || + cmd.metricId == PM_METRIC_UNTIL_DISPLAYED || + cmd.metricId == PM_METRIC_BETWEEN_DISPLAY_CHANGE)) { *reinterpret_cast(pBlobBytes + cmd.blobOffset) = std::numeric_limits::quiet_NaN(); return; From d5ccf2d2fd8c1896837f4efb1db09cb330ac2184 Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 19 Jan 2026 05:42:41 +0900 Subject: [PATCH 125/205] cleanup/fix qpc behavior --- .../ConcreteMiddleware.cpp | 9 ++-- .../PresentMonMiddleware/FrameEventQuery.cpp | 41 +++++++++++-------- .../PresentMonMiddleware/FrameEventQuery.h | 15 ++++--- .../FrameMetricsSource.cpp | 6 +++ .../PresentMonMiddleware/FrameMetricsSource.h | 1 + 5 files changed, 43 insertions(+), 29 deletions(-) diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index 614ee880..d31e3f12 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -1286,7 +1286,7 @@ static void ReportMetrics( void mid::ConcreteMiddleware::ConsumeFrameEvents(const PM_FRAME_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t& numFrames) { - const auto frames_to_copy = numFrames; + const auto framesToCopy = numFrames; numFrames = 0; auto sourceIter = frameMetricsSources.find(processId); @@ -1295,11 +1295,14 @@ static void ReportMetrics( throw Except(std::format("Failed to find frame metrics source for pid {} in ConsumeFrameEvents", processId)); } - if (frames_to_copy == 0) { + if (framesToCopy == 0) { return; } - auto frames = sourceIter->second->Consume(frames_to_copy); + auto&& [storePid, source] = *sourceIter; + // TODO: consider making consume return one frame at a time (eliminate need for heap alloc) + auto frames = source->Consume(framesToCopy); + assert(frames.size() <= framesToCopy); for (const auto& frameMetrics : frames) { pQuery->GatherToBlob(pBlob, frameMetrics); pBlob += pQuery->GetBlobSize(); diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index 9709d01f..ad2eb129 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -59,7 +59,8 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M introRoot.FindDevice(q.deviceId); } catch (const pmapi::LookupException&) { - pmlog_error(util::ReportException("Registering frame query")); + pmlog_error(util::ReportException("Failed to find device ID while registering frame query")) + .diag(); throw Except(PM_STATUS_QUERY_MALFORMED, "Invalid device ID"); } } @@ -108,11 +109,8 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M cmd.metricId = q.metric; cmd.gatherType = frameType; cmd.blobOffset = uint32_t(blobCursor); - cmd.frameMetricsOffset = 0u; cmd.deviceId = q.deviceId; cmd.arrayIdx = q.arrayIndex; - cmd.isOptional = false; - cmd.qpcToMs = 0.0; } else if (q.deviceId == ipc::kUniversalDeviceId) { cmd = MapQueryElementToFrameGatherCommand_(q, blobCursor, metricView); @@ -160,17 +158,14 @@ size_t PM_FRAME_QUERY::GetBlobSize() const PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherCommand_(const PM_QUERY_ELEMENT& q, size_t blobByteCursor, const pmapi::intro::MetricView& metricView) { - const double qpcToMs = util::GetTimestampPeriodSeconds() * 1000.0; - - GatherCommand_ cmd{}; - cmd.metricId = q.metric; - cmd.gatherType = metricView.GetDataTypeInfo().GetFrameType(); - cmd.blobOffset = uint32_t(blobByteCursor); - cmd.frameMetricsOffset = 0u; - cmd.deviceId = q.deviceId; - cmd.arrayIdx = q.arrayIndex; - cmd.isOptional = false; - cmd.qpcToMs = 0.0; + GatherCommand_ cmd{ + .metricId = q.metric, + .gatherType = metricView.GetDataTypeInfo().GetFrameType(), + .blobOffset = uint32_t(blobByteCursor), + .frameMetricsOffset = std::numeric_limits::max(), + .deviceId = q.deviceId, + .arrayIdx = q.arrayIndex, + }; switch (q.metric) { case PM_METRIC_ALLOWS_TEARING: @@ -240,11 +235,13 @@ PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherComma break; case PM_METRIC_CPU_FRAME_TIME: case PM_METRIC_BETWEEN_APP_START: + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msCPUTime); break; case PM_METRIC_GPU_LATENCY: cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msGPULatency); break; case PM_METRIC_GPU_TIME: + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msGPUTime); break; case PM_METRIC_GPU_BUSY: cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msGPUBusy); @@ -253,6 +250,7 @@ PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherComma cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msGPUWait); break; case PM_METRIC_DROPPED_FRAMES: + cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, isDroppedFrame); break; case PM_METRIC_ANIMATION_ERROR: cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msAnimationError); @@ -288,10 +286,17 @@ PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherComma .pmwatch((int)q.metric).diag(); throw Except(PM_STATUS_QUERY_MALFORMED, "Unexpected frame metric in frame query"); } + if (cmd.frameMetricsOffset == std::numeric_limits::max()) { + pmlog_error("Frame metrics offset not set") + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch((int)q.metric); + throw Except<>("Frame metrics offset not set in command mapping"); + } return cmd; } -void PM_FRAME_QUERY::GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* pBlobBytes, const util::metrics::FrameMetrics& frameMetrics) +void PM_FRAME_QUERY::GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* pBlobBytes, + const util::metrics::FrameMetrics& frameMetrics) const { const auto pFrameMemberBytes = reinterpret_cast(&frameMetrics) + cmd.frameMetricsOffset; // metrics relating to display decay to NaN when frame was dropped/not displayed at all @@ -344,7 +349,7 @@ void PM_FRAME_QUERY::GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* break; case PM_DATA_TYPE_STRING: case PM_DATA_TYPE_VOID: - pmlog_error("Unsupported frame data type"); + pmlog_error("Unsupported frame data type").pmwatch((int)cmd.gatherType); break; } } @@ -373,7 +378,7 @@ void PM_FRAME_QUERY::GatherFromTelemetry_(const GatherCommand_& cmd, uint8_t* pB case PM_DATA_TYPE_UINT32: case PM_DATA_TYPE_STRING: case PM_DATA_TYPE_VOID: - pmlog_error("Unsupported telemetry data type"); + pmlog_error("Unsupported telemetry data type").pmwatch((int)cmd.gatherType); break; } } diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h index cf82dcb1..9640fafd 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h @@ -37,20 +37,19 @@ struct PM_FRAME_QUERY struct GatherCommand_ { PM_METRIC metricId; - PM_DATA_TYPE gatherType; + PM_DATA_TYPE gatherType = PM_DATA_TYPE_VOID; uint32_t blobOffset; // offset into FrameMetrics struct from metric calculator - uint32_t frameMetricsOffset; - uint32_t deviceId; - uint32_t arrayIdx; + uint32_t frameMetricsOffset = 0; + uint32_t deviceId = 0; + uint32_t arrayIdx = 0; // indicates whether the source data is gatherType or optional - bool isOptional; - // for qpc values that need conversion to milliseconds - double qpcToMs; + bool isOptional = false; }; // functions static GatherCommand_ MapQueryElementToFrameGatherCommand_(const PM_QUERY_ELEMENT& q, size_t blobCursor, const pmapi::intro::MetricView& metricView); - static void GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* pBlobBytes, const pmon::util::metrics::FrameMetrics& frameMetrics); + void GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* pBlobBytes, + const pmon::util::metrics::FrameMetrics& frameMetrics) const; void GatherFromTelemetry_(const GatherCommand_& cmd, uint8_t* pBlobBytes, int64_t searchQpc, const pmon::ipc::TelemetryMap& teleMap) const; // data diff --git a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp index a85e8ef0..eda89e5d 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp @@ -161,4 +161,10 @@ namespace pmon::mid return output; } + const util::QpcConverter& FrameMetricsSource::GetQpcConverter() const + { + assert(qpcConverter_); + return *qpcConverter_; + } + } diff --git a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h index 52ac3e75..84090857 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h +++ b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h @@ -41,6 +41,7 @@ namespace pmon::mid FrameMetricsSource& operator=(FrameMetricsSource&&) = delete; std::vector Consume(size_t maxFrames); + const util::QpcConverter& GetQpcConverter() const; private: void ProcessNewFrames_(); From 9f7c26a954074a7ceebde7d514f34b471362b568 Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 19 Jan 2026 14:22:54 +0900 Subject: [PATCH 126/205] failing on multiple frame query registration causes problems for ipm flusher --- .../CommonUtilities/log/EntryBuilder.h | 17 ++++++++++++++--- .../PresentMonMiddleware/ConcreteMiddleware.cpp | 13 +++---------- .../PresentMonMiddleware/ConcreteMiddleware.h | 1 - 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/log/EntryBuilder.h b/IntelPresentMon/CommonUtilities/log/EntryBuilder.h index b4c179be..1e30754c 100644 --- a/IntelPresentMon/CommonUtilities/log/EntryBuilder.h +++ b/IntelPresentMon/CommonUtilities/log/EntryBuilder.h @@ -51,11 +51,22 @@ namespace pmon::util::log EntryBuilder& watch(const char* symbol, const T& value) noexcept { try { - if (note_.empty()) { - note_ += std::format(" {} => {}", symbol, value); + if constexpr (std::is_pointer_v>) { + const void* p = static_cast(value); + if (note_.empty()) { + note_ += std::format(" {} => {}", symbol, p); + } + else { + note_ += std::format("\n {} => {}", symbol, p); + } } else { - note_ += std::format("\n {} => {}", symbol, value); + if (note_.empty()) { + note_ += std::format(" {} => {}", symbol, value); + } + else { + note_ += std::format("\n {} => {}", symbol, value); + } } } catch (...) { pmlog_panic_("Failed to format watch in EntryBuilder"); } diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index d31e3f12..373be2fe 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -1267,20 +1267,13 @@ static void ReportMetrics( PM_FRAME_QUERY* mid::ConcreteMiddleware::RegisterFrameEventQuery(std::span queryElements, uint32_t& blobSize) { - if (activeFrameEventQuery != nullptr) { - pmlog_error("Frame event query already registered").diag(); - throw Except(PM_STATUS_QUERY_QUOTA_EXCEEDED, "Frame event query already registered"); - } - activeFrameEventQuery = new PM_FRAME_QUERY{ queryElements, *pComms, GetIntrospectionRoot() }; - blobSize = (uint32_t)activeFrameEventQuery->GetBlobSize(); - return activeFrameEventQuery; + auto pQuery = new PM_FRAME_QUERY{ queryElements, *pComms, GetIntrospectionRoot() }; + blobSize = (uint32_t)pQuery->GetBlobSize(); + return pQuery; } void mid::ConcreteMiddleware::FreeFrameEventQuery(const PM_FRAME_QUERY* pQuery) { - if (pQuery == activeFrameEventQuery) { - activeFrameEventQuery = nullptr; - } delete const_cast(pQuery); } diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h index 717448ad..cb42fb9f 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h @@ -225,7 +225,6 @@ namespace pmon::mid std::unordered_map, uint64_t> queryFrameDataDeltas; // Dynamic query handle to cache data std::unordered_map, std::unique_ptr> cachedMetricDatas; - PM_FRAME_QUERY* activeFrameEventQuery = nullptr; std::vector cachedGpuInfo; std::vector cachedCpuInfo; uint32_t currentGpuInfoIndex = UINT32_MAX; From ca5c877d225182efa9b884ffe24b8af72960aff8 Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 19 Jan 2026 18:05:17 +0900 Subject: [PATCH 127/205] getting frame capture working again in ipm ui --- .../CommonUtilities/log/EntryBuilder.h | 17 +----- .../Core/source/pmon/RawFrameDataWriter.cpp | 56 +++++++++++++++++-- 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/log/EntryBuilder.h b/IntelPresentMon/CommonUtilities/log/EntryBuilder.h index 1e30754c..b4c179be 100644 --- a/IntelPresentMon/CommonUtilities/log/EntryBuilder.h +++ b/IntelPresentMon/CommonUtilities/log/EntryBuilder.h @@ -51,22 +51,11 @@ namespace pmon::util::log EntryBuilder& watch(const char* symbol, const T& value) noexcept { try { - if constexpr (std::is_pointer_v>) { - const void* p = static_cast(value); - if (note_.empty()) { - note_ += std::format(" {} => {}", symbol, p); - } - else { - note_ += std::format("\n {} => {}", symbol, p); - } + if (note_.empty()) { + note_ += std::format(" {} => {}", symbol, value); } else { - if (note_.empty()) { - note_ += std::format(" {} => {}", symbol, value); - } - else { - note_ += std::format("\n {} => {}", symbol, value); - } + note_ += std::format("\n {} => {}", symbol, value); } } catch (...) { pmlog_panic_("Failed to format watch in EntryBuilder"); } diff --git a/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.cpp b/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.cpp index 08a5184e..e00a17a4 100644 --- a/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.cpp +++ b/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2024 Intel Corporation +// Copyright (C) 2017-2024 Intel Corporation // SPDX-License-Identifier: MIT #include "RawFrameDataWriter.h" #include @@ -47,22 +47,62 @@ namespace p2c::pmon if (metric.GetType() == PM_METRIC_TYPE_DYNAMIC) { pmlog_error("Specified metric does not support frame query").raise<::pmon::util::Exception>(); } + const auto& deviceInfos = metric.GetDeviceMetricInfo(); + const bool isGraphicsAdapter = !deviceInfos.empty() && + deviceInfos.front().GetDevice().GetType() == PM_DEVICE_TYPE_GRAPHICS_ADAPTER; elements.push_back(RawFrameQueryElementDefinition{ .metricId = metricLookup.at(metricSymbol), - .deviceId = metric.GetDeviceMetricInfo().front().GetDevice().GetType() == - PM_DEVICE_TYPE_GRAPHICS_ADAPTER ? activeDeviceId : 0, + .deviceId = isGraphicsAdapter ? activeDeviceId : 0, }); } catch (...) { pmlog_error("Failed to add metric").pmwatch(metricSymbol); } } - if (elements.empty()) { - pmlog_error("No valid metrics specified for frame event capture").raise<::pmon::util::Exception>(); - } return elements; } + void FilterUnavailableMetrics_(std::vector& elements, uint32_t activeDeviceId, + const pmapi::intro::Root& introRoot) + { + std::vector filtered; + filtered.reserve(elements.size()); + for (const auto& element : elements) { + // special quasi-frame metrics + if (element.metricId == PM_METRIC_APPLICATION || + element.metricId == (PM_METRIC)PM_METRIC_INTERNAL_PROCESS_ID_) { + filtered.push_back(element); + continue; + } + const auto& metric = introRoot.FindMetric(element.metricId); + const auto checkDeviceId = element.deviceId != 0 ? element.deviceId : activeDeviceId; + bool available = false; + for (auto&& dmi : metric.GetDeviceMetricInfo()) { + if (auto devId = dmi.GetDevice().GetId(); devId == checkDeviceId || devId == 0) { + if (dmi.IsAvailable() && dmi.GetArraySize() > 0) { + available = true; + break; + } + } + } + if (!available) { + pmlog_warn("Metric not available for active device") + .pmwatch(metric.Introspect().GetSymbol()) + .pmwatch(checkDeviceId); + continue; + } + // TODO: remove this temp check for static metrics + // since we are not filling yet + if (metric.GetType() == PM_METRIC_TYPE_STATIC) { + pmlog_warn("Static metrics in frame query currently unsupported") + .pmwatch(metric.Introspect().GetSymbol()); + continue; + } + filtered.push_back(element); + } + elements = std::move(filtered); + } + class StreamFlagPreserver_ { public: @@ -407,6 +447,10 @@ namespace p2c::pmon else { elements = GetDefaultRawFrameDataMetricList(activeDeviceId, opt.enableTimestampColumn); } + FilterUnavailableMetrics_(elements, activeDeviceId, introRoot); + if (elements.empty()) { + pmlog_error("No valid metrics specified for frame event capture").raise<::pmon::util::Exception>(); + } pQueryElementContainer = std::make_unique(std::move(elements), session, introRoot, ::pmon::util::str::ToNarrow(processName), procTracker.GetPid()); blobs = pQueryElementContainer->MakeBlobs(numberOfBlobs); From 6af293670fd1b576e1313ee3d1227b75cc767afe Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 20 Jan 2026 10:29:33 +0900 Subject: [PATCH 128/205] fixing history ring access and nearest sampling --- .../Interprocess/source/HistoryRing.h | 19 ++++++++++++++++--- .../PresentMonMiddleware/FrameEventQuery.cpp | 19 +++++++++++-------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/HistoryRing.h b/IntelPresentMon/Interprocess/source/HistoryRing.h index 8a7440f0..77664840 100644 --- a/IntelPresentMon/Interprocess/source/HistoryRing.h +++ b/IntelPresentMon/Interprocess/source/HistoryRing.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "ShmRing.h" namespace pmon::ipc @@ -37,6 +37,10 @@ namespace pmon::ipc { return samples_.At(serial); } + const Sample& Nearest(uint64_t timestamp) const + { + return samples_.At(NearestSerial(timestamp)); + } std::pair GetSerialRange() const { return samples_.GetSerialRange(); @@ -66,12 +70,21 @@ namespace pmon::ipc // If the timestamp is outside the stored range, clamps to first/last. size_t NearestSerial(uint64_t timestamp) const { + const auto range = samples_.GetSerialRange(); + if (range.first == range.second) { + return range.first; + } + // First serial with timestamp >= requested size_t serial = LowerBoundSerial(timestamp); + if (serial >= range.second) { + return range.second - 1; + } + // Check whether the previous sample is actually closer. // but only if there is a sample available before this one - if (serial > samples_.GetSerialRange().first) { + if (serial > range.first) { const auto nextTimestamp = At(serial).timestamp; const auto prevTimestamp = At(serial - 1).timestamp; const uint64_t dPrev = timestamp - prevTimestamp; @@ -159,4 +172,4 @@ namespace pmon::ipc ShmRing samples_; }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index ad2eb129..c194277e 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -357,22 +357,25 @@ void PM_FRAME_QUERY::GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* void PM_FRAME_QUERY::GatherFromTelemetry_(const GatherCommand_& cmd, uint8_t* pBlobBytes, int64_t searchQpc, const ipc::TelemetryMap& teleMap) const { + const auto ResolveValue = [&, this]() -> const T& { + // resolve ring by metric id + array index + auto& ring = teleMap.FindRing(cmd.metricId)[cmd.arrayIdx]; + // find nearest sample in ring and return value + return ring.Nearest(searchQpc).value; + }; + switch (cmd.gatherType) { case PM_DATA_TYPE_UINT64: - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = ResolveValue.operator()(); break; case PM_DATA_TYPE_DOUBLE: - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = ResolveValue.operator()(); break; case PM_DATA_TYPE_ENUM: - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = ResolveValue.operator()(); break; case PM_DATA_TYPE_BOOL: - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - teleMap.FindRing(cmd.metricId)[cmd.arrayIdx].At(searchQpc).value; + *reinterpret_cast(pBlobBytes + cmd.blobOffset) = ResolveValue.operator()(); break; case PM_DATA_TYPE_INT32: case PM_DATA_TYPE_UINT32: From 0e505258b3930e5ee9511d8a7622d40f615b5fbf Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 20 Jan 2026 12:05:21 +0900 Subject: [PATCH 129/205] better logging for history ring --- IntelPresentMon/CommonUtilities/log/Verbose.h | 1 + .../Interprocess/source/HistoryRing.h | 31 +++++++++++++++++++ .../KernelProcess/KernelProcess.args.json | 2 +- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/IntelPresentMon/CommonUtilities/log/Verbose.h b/IntelPresentMon/CommonUtilities/log/Verbose.h index b92240d0..1fcebb28 100644 --- a/IntelPresentMon/CommonUtilities/log/Verbose.h +++ b/IntelPresentMon/CommonUtilities/log/Verbose.h @@ -15,6 +15,7 @@ namespace pmon::util::log etwq, kact, ipc_sto, + ipc_ring, met_use, Count }; diff --git a/IntelPresentMon/Interprocess/source/HistoryRing.h b/IntelPresentMon/Interprocess/source/HistoryRing.h index 77664840..b8179696 100644 --- a/IntelPresentMon/Interprocess/source/HistoryRing.h +++ b/IntelPresentMon/Interprocess/source/HistoryRing.h @@ -1,5 +1,8 @@ #pragma once #include "ShmRing.h" +#include "../../CommonUtilities/log/Verbose.h" +#include +#include namespace pmon::ipc { @@ -71,14 +74,38 @@ namespace pmon::ipc size_t NearestSerial(uint64_t timestamp) const { const auto range = samples_.GetSerialRange(); + // empty ring case if (range.first == range.second) { + pmlog_verb(util::log::V::ipc_ring)("Reading from empty history ring"); return range.first; } // First serial with timestamp >= requested size_t serial = LowerBoundSerial(timestamp); + // case where requested timestamp is newer than newest sample if (serial >= range.second) { + // log timing and dump ring contents in case where ring has insufficient samples + if (util::log::GlobalPolicy::VCheck(util::log::V::ipc_ring)) { + std::string recentSamples; + const size_t sampleCount = range.second - range.first; + const size_t maxSamples = 12; + const size_t dumpCount = sampleCount < maxSamples ? sampleCount : maxSamples; + const size_t startSerial = range.second - dumpCount; + for (size_t s = startSerial; s < range.second; ++s) { + const auto& sample = At(s); + if (!recentSamples.empty()) { + recentSamples += "\n"; + } + recentSamples += std::format("ts={} value={}", sample.timestamp, sample.value); + } + pmlog_verb(util::log::V::ipc_ring)("Target timestamp past end of history ring") + .pmwatch(timestamp) + .pmwatch(range.second) + .pmwatch(int64_t(At(serial - 1).timestamp) - int64_t(timestamp)) + .watch("recent_samples", recentSamples); + } + return range.second - 1; } @@ -94,6 +121,10 @@ namespace pmon::ipc } } + pmlog_verb(util::log::V::ipc_ring)("Found nearest sample") + .pmwatch(timestamp) + .pmwatch(serial) + .pmwatch(int64_t(At(serial).timestamp) - int64_t(timestamp)); return serial; } // Calls func(sample) for each sample whose timestamp is in [start, end]. diff --git a/IntelPresentMon/KernelProcess/KernelProcess.args.json b/IntelPresentMon/KernelProcess/KernelProcess.args.json index 43b7afbc..51d86ac9 100644 --- a/IntelPresentMon/KernelProcess/KernelProcess.args.json +++ b/IntelPresentMon/KernelProcess/KernelProcess.args.json @@ -34,7 +34,7 @@ }, { "Id": "49b6fc1f-b969-4280-b0d8-17a213e80f16", - "Command": "--log-verbose-modules met_use" + "Command": "--log-verbose-modules ipc_ring" }, { "Id": "bab48d6d-3a48-4b3b-9ed9-903e381824c6", From e36b39761b3c9919348ee92e91f5e3ddb558c010 Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 20 Jan 2026 14:06:08 +0900 Subject: [PATCH 130/205] diable multiple frame query registration negative test --- .../IpcMcIntegrationTests.cpp | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp index 21f54b10..cfeca0a7 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp @@ -114,24 +114,24 @@ namespace IpcMcIntegrationTests Assert::IsTrue(gotFrames, L"Expected frame query to consume frames"); } - TEST_METHOD(SecondFrameQueryRegistrationFails) - { - pmapi::Session session{ fixture_.GetCommonArgs().ctrlPipe }; - auto intro = session.GetIntrospectionRoot(); - Assert::IsTrue((bool)intro); - - auto elements = BuildUniversalFrameQueryElements_(*intro); - Assert::IsTrue(!elements.empty(), L"No universal frame metrics found"); - - // first registration succeeds - std::vector single{ elements.front() }; - auto query = session.RegisterFrameQuery(single); - Assert::IsTrue((bool)query); - - // second registration fails - Assert::ExpectException([&] { - session.RegisterFrameQuery(single); - }); - } + //TEST_METHOD(SecondFrameQueryRegistrationFails) + //{ + // pmapi::Session session{ fixture_.GetCommonArgs().ctrlPipe }; + // auto intro = session.GetIntrospectionRoot(); + // Assert::IsTrue((bool)intro); + + // auto elements = BuildUniversalFrameQueryElements_(*intro); + // Assert::IsTrue(!elements.empty(), L"No universal frame metrics found"); + + // // first registration succeeds + // std::vector single{ elements.front() }; + // auto query = session.RegisterFrameQuery(single); + // Assert::IsTrue((bool)query); + + // // second registration fails + // Assert::ExpectException([&] { + // session.RegisterFrameQuery(single); + // }); + //} }; } From e8cf536055e4768da5847d19b2c8608129a92e1f Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 20 Jan 2026 16:28:34 +0900 Subject: [PATCH 131/205] api endpoint for tracking playback --- .../Interprocess/source/metadata/EnumStatus.h | 5 ++-- .../PresentMonAPI2/PresentMonAPI.cpp | 18 +++++++++-- .../PresentMonAPI2/PresentMonAPI.h | 6 +++- .../PresentMonAPI2Loader/Implementation.cpp | 11 +++++-- .../PresentMonAPIWrapper.h | 2 +- .../PresentMonAPIWrapper.vcxproj | 4 +-- .../PresentMonAPIWrapper.vcxproj.filters | 2 +- .../PresentMonAPIWrapper/ProcessTracker.cpp | 15 +++++++--- .../PresentMonAPIWrapper/ProcessTracker.h | 4 +-- .../PresentMonAPIWrapper/Session.cpp | 8 ++--- .../PresentMonAPIWrapper/Session.h | 5 ++-- .../ConcreteMiddleware.cpp | 30 +++++++++++++++++++ .../PresentMonMiddleware/ConcreteMiddleware.h | 1 + .../PresentMonMiddleware/Middleware.h | 5 ++-- .../PresentMonService/PresentMon.cpp | 5 ++-- .../PresentMonService/PresentMon.h | 7 ++++- .../PresentMonService/acts/StartTracking.h | 10 +++++-- .../SampleClient/PacedFramePlayback.cpp | 2 +- .../SampleClient/PacedPlayback.cpp | 6 ++-- IntelPresentMon/SampleClient/SampleClient.cpp | 6 ++-- 20 files changed, 115 insertions(+), 37 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/metadata/EnumStatus.h b/IntelPresentMon/Interprocess/source/metadata/EnumStatus.h index 4103b9de..8276975f 100644 --- a/IntelPresentMon/Interprocess/source/metadata/EnumStatus.h +++ b/IntelPresentMon/Interprocess/source/metadata/EnumStatus.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "../../../PresentMonAPI2/PresentMonAPI.h" // enum annotation (enum_name_fragment, key_name_fragment, name, short_name, description) @@ -25,4 +25,5 @@ X_(STATUS, MIDDLEWARE_VERSION_HIGH, "Middleware Version High", "", "Middleware DLL version was found to be too high for compatibility") \ X_(STATUS, MIDDLEWARE_SERVICE_MISMATCH, "Middleware Service Mismatch", "", "Middleware DLL build ID does not match that of the service") \ X_(STATUS, QUERY_MALFORMED, "Query Malformed", "", "There are errors or inconsistencies in the elements of the query being registered") \ - X_(STATUS, QUERY_QUOTA_EXCEEDED, "Query Quota Exceeded", "", "Limit on the number of concurrently registered queries is exceeded (1 max for frame queries)") \ No newline at end of file + X_(STATUS, QUERY_QUOTA_EXCEEDED, "Query Quota Exceeded", "", "Limit on the number of concurrently registered queries is exceeded (1 max for frame queries)") \ + X_(STATUS, MODE_MISMATCH, "Mode Mismatch", "", "Operation is not valid for the current service mode") diff --git a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp index 944d0263..2f9f60f6 100644 --- a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp +++ b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include "../PresentMonMiddleware/ConcreteMiddleware.h" @@ -172,6 +172,20 @@ PRESENTMON_API2_EXPORT PM_STATUS pmStartTrackingProcess(PM_SESSION_HANDLE handle } } +PRESENTMON_API2_EXPORT PM_STATUS pmStartPlaybackTracking(PM_SESSION_HANDLE handle, uint32_t processId, uint32_t isBackpressured) +{ + try { + // TODO: consider tracking resource usage for process tracking to validate Start/Stop pairing + // TODO: middleware (StartStreaming) should not return status codes + return LookupMiddleware_(handle).StartPlaybackTracking(processId, isBackpressured != 0); + } + catch (...) { + const auto code = util::GeneratePmStatus(); + pmlog_error(util::ReportException()).code(code); + return code; + } +} + PRESENTMON_API2_EXPORT PM_STATUS pmStopTrackingProcess(PM_SESSION_HANDLE handle, uint32_t processId) { try { @@ -480,4 +494,4 @@ PRESENTMON_API2_EXPORT PM_STATUS pmFinishEtlLogging(PM_SESSION_HANDLE session, P pmlog_error(util::ReportException()).code(code); return code; } -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h index 44142c3c..d0f86847 100644 --- a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h +++ b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2024 Intel Corporation +// Copyright (C) 2017-2024 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #ifdef PRESENTMONAPI2_EXPORTS @@ -44,6 +44,7 @@ extern "C" { PM_STATUS_MIDDLEWARE_SERVICE_MISMATCH, PM_STATUS_QUERY_MALFORMED, PM_STATUS_QUERY_QUOTA_EXCEEDED, + PM_STATUS_MODE_MISMATCH, }; enum PM_METRIC @@ -399,6 +400,9 @@ extern "C" { PRESENTMON_API2_EXPORT PM_STATUS pmCloseSession(PM_SESSION_HANDLE handle); // command the service to process and make available frame/metric data for the specified process id PRESENTMON_API2_EXPORT PM_STATUS pmStartTrackingProcess(PM_SESSION_HANDLE handle, uint32_t process_id); + // command the service to process and make available frame/metric data for playback of the specified process id + // isBackpressured controls whether playback uses backpressured frame rings + PRESENTMON_API2_EXPORT PM_STATUS pmStartPlaybackTracking(PM_SESSION_HANDLE handle, uint32_t process_id, uint32_t isBackpressured); // command the service to cease processing and exporting frame/metric data for the specified process id PRESENTMON_API2_EXPORT PM_STATUS pmStopTrackingProcess(PM_SESSION_HANDLE handle, uint32_t process_id); // allocate and populate a tree data structure describing the available metrics, devices, etc. diff --git a/IntelPresentMon/PresentMonAPI2Loader/Implementation.cpp b/IntelPresentMon/PresentMonAPI2Loader/Implementation.cpp index 68531c5f..48f4d3c6 100644 --- a/IntelPresentMon/PresentMonAPI2Loader/Implementation.cpp +++ b/IntelPresentMon/PresentMonAPI2Loader/Implementation.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -27,6 +27,7 @@ PM_STATUS(*pFunc_pmOpenSession_)(PM_SESSION_HANDLE*) = nullptr; PM_STATUS(*pFunc_pmOpenSessionWithPipe_)(PM_SESSION_HANDLE* pHandle, const char*) = nullptr; PM_STATUS(*pFunc_pmCloseSession_)(PM_SESSION_HANDLE) = nullptr; PM_STATUS(*pFunc_pmStartTrackingProcess_)(PM_SESSION_HANDLE, uint32_t) = nullptr; +PM_STATUS(*pFunc_pmStartPlaybackTracking_)(PM_SESSION_HANDLE, uint32_t, uint32_t) = nullptr; PM_STATUS(*pFunc_pmStopTrackingProcess_)(PM_SESSION_HANDLE, uint32_t) = nullptr; PM_STATUS(*pFunc_pmGetIntrospectionRoot_)(PM_SESSION_HANDLE, const PM_INTROSPECTION_ROOT**) = nullptr; PM_STATUS(*pFunc_pmFreeIntrospectionRoot_)(const PM_INTROSPECTION_ROOT*) = nullptr; @@ -156,6 +157,7 @@ PRESENTMON_API2_EXPORT PM_STATUS LoadLibrary_(bool versionOnly = false) RESOLVE(pmOpenSessionWithPipe); RESOLVE(pmCloseSession); RESOLVE(pmStartTrackingProcess); + RESOLVE(pmStartPlaybackTracking); RESOLVE(pmStopTrackingProcess); RESOLVE(pmGetIntrospectionRoot); RESOLVE(pmFreeIntrospectionRoot); @@ -228,6 +230,11 @@ PRESENTMON_API2_EXPORT PM_STATUS pmStartTrackingProcess(PM_SESSION_HANDLE handle LoadEndpointsIfEmpty_(); return pFunc_pmStartTrackingProcess_(handle, process_id); } +PRESENTMON_API2_EXPORT PM_STATUS pmStartPlaybackTracking(PM_SESSION_HANDLE handle, uint32_t process_id, uint32_t isBackpressured) +{ + LoadEndpointsIfEmpty_(); + return pFunc_pmStartPlaybackTracking_(handle, process_id, isBackpressured); +} PRESENTMON_API2_EXPORT PM_STATUS pmStopTrackingProcess(PM_SESSION_HANDLE handle, uint32_t process_id) { LoadEndpointsIfEmpty_(); @@ -432,4 +439,4 @@ namespace pmon::util::log { return {}; } -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonAPIWrapper/PresentMonAPIWrapper.h b/IntelPresentMon/PresentMonAPIWrapper/PresentMonAPIWrapper.h index 9ca269d9..9ff79d86 100644 --- a/IntelPresentMon/PresentMonAPIWrapper/PresentMonAPIWrapper.h +++ b/IntelPresentMon/PresentMonAPIWrapper/PresentMonAPIWrapper.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "Session.h" #include "ProcessTracker.h" #include "FrameQuery.h" diff --git a/IntelPresentMon/PresentMonAPIWrapper/PresentMonAPIWrapper.vcxproj b/IntelPresentMon/PresentMonAPIWrapper/PresentMonAPIWrapper.vcxproj index 7438eb7a..87923779 100644 --- a/IntelPresentMon/PresentMonAPIWrapper/PresentMonAPIWrapper.vcxproj +++ b/IntelPresentMon/PresentMonAPIWrapper/PresentMonAPIWrapper.vcxproj @@ -1,4 +1,4 @@ - + @@ -135,4 +135,4 @@ - \ No newline at end of file + diff --git a/IntelPresentMon/PresentMonAPIWrapper/PresentMonAPIWrapper.vcxproj.filters b/IntelPresentMon/PresentMonAPIWrapper/PresentMonAPIWrapper.vcxproj.filters index f1cf4eb1..6e869963 100644 --- a/IntelPresentMon/PresentMonAPIWrapper/PresentMonAPIWrapper.vcxproj.filters +++ b/IntelPresentMon/PresentMonAPIWrapper/PresentMonAPIWrapper.vcxproj.filters @@ -75,4 +75,4 @@ Header Files - \ No newline at end of file + diff --git a/IntelPresentMon/PresentMonAPIWrapper/ProcessTracker.cpp b/IntelPresentMon/PresentMonAPIWrapper/ProcessTracker.cpp index 7032941a..85b09b66 100644 --- a/IntelPresentMon/PresentMonAPIWrapper/ProcessTracker.cpp +++ b/IntelPresentMon/PresentMonAPIWrapper/ProcessTracker.cpp @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "ProcessTracker.h" #include #include @@ -46,12 +46,19 @@ namespace pmapi ProcessTracker::operator bool() const { return !Empty(); } - ProcessTracker::ProcessTracker(PM_SESSION_HANDLE hSession, uint32_t pid) + ProcessTracker::ProcessTracker(PM_SESSION_HANDLE hSession, uint32_t pid, bool isPlayback, bool isBackpressured) : pid_{ pid }, hSession_{ hSession } { - if (auto sta = pmStartTrackingProcess(hSession_, pid_); sta != PM_STATUS_SUCCESS) { + PM_STATUS sta = PM_STATUS_SUCCESS; + if (isPlayback) { + sta = pmStartPlaybackTracking(hSession_, pid_, isBackpressured ? 1u : 0u); + } + else { + sta = pmStartTrackingProcess(hSession_, pid_); + } + if (sta != PM_STATUS_SUCCESS) { throw ApiErrorException{ sta, "start process tracking call failed" }; } } @@ -61,4 +68,4 @@ namespace pmapi pid_ = 0; hSession_ = nullptr; } -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonAPIWrapper/ProcessTracker.h b/IntelPresentMon/PresentMonAPIWrapper/ProcessTracker.h index da828699..3d1ff6a8 100644 --- a/IntelPresentMon/PresentMonAPIWrapper/ProcessTracker.h +++ b/IntelPresentMon/PresentMonAPIWrapper/ProcessTracker.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include namespace pmapi @@ -28,7 +28,7 @@ namespace pmapi operator bool() const; private: // functions - ProcessTracker(PM_SESSION_HANDLE hSession, uint32_t pid); + ProcessTracker(PM_SESSION_HANDLE hSession, uint32_t pid, bool isPlayback, bool isBackpressured); // zero out members, useful after emptying via move or reset void Clear_() noexcept; // data diff --git a/IntelPresentMon/PresentMonAPIWrapper/Session.cpp b/IntelPresentMon/PresentMonAPIWrapper/Session.cpp index 12890ba1..74431e12 100644 --- a/IntelPresentMon/PresentMonAPIWrapper/Session.cpp +++ b/IntelPresentMon/PresentMonAPIWrapper/Session.cpp @@ -1,4 +1,4 @@ -#include "Session.h" +#include "Session.h" #include #include #include @@ -77,10 +77,10 @@ namespace pmapi return pIntrospectionRootCache_ = std::make_shared(pRoot, [](const PM_INTROSPECTION_ROOT* ptr) { pmFreeIntrospectionRoot(ptr); }); } - ProcessTracker Session::TrackProcess(uint32_t pid) + ProcessTracker Session::TrackProcess(uint32_t pid, bool isPlayback, bool isBackpressured) { assert(handle_); - return { handle_, pid }; + return { handle_, pid, isPlayback, isBackpressured }; } DynamicQuery Session::RegisterDynamicQuery(std::span elements, double winSizeMs, double metricOffsetMs) @@ -131,4 +131,4 @@ namespace pmapi // initialize the enum map so that it doesn't need to be initialized explicitly EnumMap::Refresh(*GetIntrospectionRoot()); } -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonAPIWrapper/Session.h b/IntelPresentMon/PresentMonAPIWrapper/Session.h index 3df3ccfa..c4279cbb 100644 --- a/IntelPresentMon/PresentMonAPIWrapper/Session.h +++ b/IntelPresentMon/PresentMonAPIWrapper/Session.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include #include "ProcessTracker.h" #include "FrameQuery.h" @@ -51,7 +51,8 @@ namespace pmapi // see PresentMonAPIWrapperCommon/Introspection.h for details about introspection data available std::shared_ptr GetIntrospectionRoot(bool forceRefresh = false) const; // begin tracking a process, necessary to consume frame data or query metrics involving that process - ProcessTracker TrackProcess(uint32_t pid); + // set isPlayback and isBackpressured when tracking ETL playback + ProcessTracker TrackProcess(uint32_t pid, bool isPlayback = false, bool isBackpressured = false); // register (build/compile) a dynamic query used to poll metrics DynamicQuery RegisterDynamicQuery(std::span elements, double winSizeMs = 1000, double metricOffsetMs = 1020); // register (build/compile) a frame query used to consume frame events diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index 373be2fe..200833f4 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -128,6 +128,36 @@ namespace pmon::mid return PM_STATUS_SUCCESS; } + PM_STATUS ConcreteMiddleware::StartPlaybackTracking(uint32_t targetPid, bool isBackpressured) + { + try { + auto res = pActionClient->DispatchSync(StartTracking::Params{ + .targetPid = targetPid, + .isPlayback = true, + .isBackpressured = isBackpressured + }); + // Initialize client in client map using returned nsm name + auto iter = presentMonStreamClients.find(targetPid); + if (iter == presentMonStreamClients.end()) { + presentMonStreamClients.emplace(targetPid, + std::make_unique(std::move(res.nsmFileName), false)); + } + auto sourceIter = frameMetricsSources.find(targetPid); + if (sourceIter == frameMetricsSources.end()) { + frameMetricsSources.emplace(targetPid, + std::make_unique(*pComms, targetPid, kFrameMetricsPerSwapChainCapacity)); + } + } + catch (...) { + const auto code = util::GeneratePmStatus(); + pmlog_error(util::ReportException()).code(code).diag(); + return code; + } + + pmlog_info(std::format("Started playback tracking pid [{}]", targetPid)).diag(); + return PM_STATUS_SUCCESS; + } + PM_STATUS ConcreteMiddleware::StopStreaming(uint32_t targetPid) { try { diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h index cb42fb9f..b148d7bd 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h @@ -176,6 +176,7 @@ namespace pmon::mid const PM_INTROSPECTION_ROOT* GetIntrospectionData() override; void FreeIntrospectionData(const PM_INTROSPECTION_ROOT* pRoot) override; PM_STATUS StartStreaming(uint32_t processId) override; + PM_STATUS StartPlaybackTracking(uint32_t processId, bool isBackpressured) override; PM_STATUS StopStreaming(uint32_t processId) override; PM_STATUS SetTelemetryPollingPeriod(uint32_t deviceId, uint32_t timeMs) override; PM_STATUS SetEtwFlushPeriod(std::optional periodMs) override; diff --git a/IntelPresentMon/PresentMonMiddleware/Middleware.h b/IntelPresentMon/PresentMonMiddleware/Middleware.h index 3c9ffd46..ea19a8f9 100644 --- a/IntelPresentMon/PresentMonMiddleware/Middleware.h +++ b/IntelPresentMon/PresentMonMiddleware/Middleware.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "../PresentMonAPI2/PresentMonAPI.h" #include #include @@ -14,6 +14,7 @@ namespace pmon::mid virtual const PM_INTROSPECTION_ROOT* GetIntrospectionData() = 0; virtual void FreeIntrospectionData(const PM_INTROSPECTION_ROOT* pRoot) = 0; virtual PM_STATUS StartStreaming(uint32_t processId) = 0; + virtual PM_STATUS StartPlaybackTracking(uint32_t processId, bool isBackpressured) = 0; virtual PM_STATUS StopStreaming(uint32_t processId) = 0; virtual PM_STATUS SetTelemetryPollingPeriod(uint32_t deviceId, uint32_t timeMs) = 0; virtual PM_STATUS SetEtwFlushPeriod(std::optional periodMs) = 0; @@ -28,4 +29,4 @@ namespace pmon::mid virtual uint32_t StartEtlLogging() = 0; virtual std::string FinishEtlLogging(uint32_t etlLogSessionHandle) = 0; }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonService/PresentMon.cpp b/IntelPresentMon/PresentMonService/PresentMon.cpp index b02986a1..444e1354 100644 --- a/IntelPresentMon/PresentMonService/PresentMon.cpp +++ b/IntelPresentMon/PresentMonService/PresentMon.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2023 Intel Corporation +// Copyright (C) 2022-2023 Intel Corporation // SPDX-License-Identifier: MIT #include "PresentMon.h" @@ -16,7 +16,8 @@ PresentMon::PresentMon(svc::FrameBroadcaster& broadcaster, bool isRealtime) : broadcaster_{ broadcaster }, - etwLogger_{ util::win::WeAreElevated() } + etwLogger_{ util::win::WeAreElevated() }, + isRealtime_{ isRealtime } { if (isRealtime) { pSession_ = std::make_unique(broadcaster); diff --git a/IntelPresentMon/PresentMonService/PresentMon.h b/IntelPresentMon/PresentMonService/PresentMon.h index 6f2bcc56..1c66aaa6 100644 --- a/IntelPresentMon/PresentMonService/PresentMon.h +++ b/IntelPresentMon/PresentMonService/PresentMon.h @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2023 Intel Corporation +// Copyright (C) 2022-2023 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #include "PresentMonSession.h" @@ -94,6 +94,10 @@ class PresentMon { return broadcaster_; } + bool IsPlayback() const + { + return !isRealtime_; + } bool CheckDeviceMetricUsage(uint32_t deviceId) const { std::shared_lock lk{ metricDeviceUsageMtx_ }; @@ -133,6 +137,7 @@ class PresentMon svc::FrameBroadcaster& broadcaster_; svc::EtwLogger etwLogger_; std::unique_ptr pSession_; + bool isRealtime_ = true; mutable std::shared_mutex metricDeviceUsageMtx_; std::unordered_set metricDeviceUsage_; using DeviceUsageEvtKey = std::pair; diff --git a/IntelPresentMon/PresentMonService/acts/StartTracking.h b/IntelPresentMon/PresentMonService/acts/StartTracking.h index ce10a3ad..5905567f 100644 --- a/IntelPresentMon/PresentMonService/acts/StartTracking.h +++ b/IntelPresentMon/PresentMonService/acts/StartTracking.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "../../Interprocess/source/act/ActionHelper.h" #include @@ -38,6 +38,12 @@ namespace pmon::svc::acts friend class ACT_TYPE; static Response Execute_(const ACT_EXEC_CTX& ctx, SessionContext& stx, Params&& in) { + const bool serviceIsPlayback = ctx.pPmon->IsPlayback(); + if (serviceIsPlayback != in.isPlayback) { + pmlog_error("StartTracking playback mode mismatch") + .pmwatch(serviceIsPlayback).pmwatch(in.isPlayback); + throw util::Except(PM_STATUS_MODE_MISMATCH); + } std::string nsmFileName; // TODO: replace PresentMon container system and directly return the segment // from a single "StartStreaming" replacement call @@ -65,4 +71,4 @@ ACTION_TRAITS_DEF(); #undef ACT_NAME #undef ACT_EXEC_CTX #undef ACT_NS -#undef ACT_TYPE \ No newline at end of file +#undef ACT_TYPE diff --git a/IntelPresentMon/SampleClient/PacedFramePlayback.cpp b/IntelPresentMon/SampleClient/PacedFramePlayback.cpp index 7c5e55d4..688513ba 100644 --- a/IntelPresentMon/SampleClient/PacedFramePlayback.cpp +++ b/IntelPresentMon/SampleClient/PacedFramePlayback.cpp @@ -196,7 +196,7 @@ int PacedFramePlaybackTest(std::unique_ptr pSession) pmapi::FixedQueryElement msInstrumentedLatency{ this, PM_METRIC_INSTRUMENTED_LATENCY, PM_STAT_NONE }; PM_END_FIXED_QUERY query{ *pSession, 512 }; - auto tracker = pSession->TrackProcess(*opt.processId); + auto tracker = pSession->TrackProcess(*opt.processId, true, false); std::ofstream csv{ *opt.outputPath }; if (!csv.is_open()) { diff --git a/IntelPresentMon/SampleClient/PacedPlayback.cpp b/IntelPresentMon/SampleClient/PacedPlayback.cpp index 766d55cd..940030c3 100644 --- a/IntelPresentMon/SampleClient/PacedPlayback.cpp +++ b/IntelPresentMon/SampleClient/PacedPlayback.cpp @@ -1,4 +1,4 @@ -#include "MultiClient.h" +#include "MultiClient.h" #include "CliOptions.h" #include #include @@ -162,7 +162,7 @@ class TestClientModule double recordingStopSec, double pollInterval) { // start tracking target - auto tracker = pSession_->TrackProcess(targetPid); + auto tracker = pSession_->TrackProcess(targetPid, true, false); // get the waiter and the timer clocks ready using Clock = std::chrono::high_resolution_clock; const auto startTime = Clock::now(); @@ -280,4 +280,4 @@ int PacedPlaybackTest(std::unique_ptr pSession) } return -1; -} \ No newline at end of file +} diff --git a/IntelPresentMon/SampleClient/SampleClient.cpp b/IntelPresentMon/SampleClient/SampleClient.cpp index d2aaecf8..17a6fd83 100644 --- a/IntelPresentMon/SampleClient/SampleClient.cpp +++ b/IntelPresentMon/SampleClient/SampleClient.cpp @@ -92,7 +92,7 @@ void RunPlaybackFrameQuery() // track the pid we know to be active in the ETL (1268 for dwm in gold_0) pid = opt.processId.AsOptional().value_or(1268); } - auto tracker = api.TrackProcess(pid); + auto tracker = api.TrackProcess(pid, true, false); std::this_thread::sleep_for(500ms); uint32_t frameCount = 0; @@ -171,7 +171,7 @@ void RunPlaybackDynamicQuery() PM_END_FIXED_QUERY query{ api, 200., 50., 1, 1 }; // track the pid we know to be active in the ETL (1268 for dwm in gold_0) - auto tracker = api.TrackProcess(opt.processId.AsOptional().value_or(1268)); + auto tracker = api.TrackProcess(opt.processId.AsOptional().value_or(1268), true, false); pmon::util::IntervalWaiter waiter{ 0.1 }; @@ -245,7 +245,7 @@ void RunPlaybackDynamicQueryN() PM_END_FIXED_QUERY query{ api, 200., 50., 1, 1 }; // track the pid we know to be active in the ETL (1268 for dwm in gold_0) - auto tracker = api.TrackProcess(opt.processId.AsOptional().value_or(1268)); + auto tracker = api.TrackProcess(opt.processId.AsOptional().value_or(1268), true, false); pmon::util::IntervalWaiter waiter{ 0.1 }; From 610e0daeb623d714bb43845f5a49f8c14e2865b6 Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 20 Jan 2026 18:17:38 +0900 Subject: [PATCH 132/205] frame metric source latching qpc start time properly --- .../FrameMetricsSource.cpp | 10 ++++-- .../PresentMonMiddleware/FrameMetricsSource.h | 1 + .../PresentMonService/FrameBroadcaster.h | 31 +++++++++++++++---- .../MockPresentMonSession.cpp | 3 ++ .../RealtimePresentMonSession.cpp | 3 ++ 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp index eda89e5d..e49390d4 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp @@ -71,8 +71,6 @@ namespace pmon::mid // open the data store from ipc comms_.OpenFrameDataStore(processId_); pStore_ = &comms_.GetFrameDataStore(processId_); - // TODO: potentially source qpc frequency from store's bookkeeping - qpcConverter_ = util::QpcConverter{ util::GetTimestampFrequencyUint64(), (uint64_t)pStore_->bookkeeping.startQpc}; const auto range = pStore_->frameData.GetSerialRange(); nextFrameSerial_ = range.first; } @@ -109,6 +107,14 @@ namespace pmon::mid return; } + // deferred initialization of the qpc converter is required because + // when the store is first created, start qpc is not yet populated (populates on + // first frame that is broadcasted) + if (!qpcConverter_) { + // TODO: potentially source qpc frequency from store's bookkeeping + qpcConverter_ = util::QpcConverter{ util::GetTimestampFrequencyUint64(), (uint64_t)pStore_->bookkeeping.startQpc }; + } + for (size_t serial = nextFrameSerial_; serial < range.second; ++serial) { const auto& frame = ring.At(serial); auto [it, inserted] = swapChains_.try_emplace(frame.swapChainAddress, perSwapChainCapacity_); diff --git a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h index 84090857..160a3fec 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h +++ b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h @@ -51,6 +51,7 @@ namespace pmon::mid uint32_t processId_ = 0; size_t perSwapChainCapacity_ = 0; size_t nextFrameSerial_ = 0; + // optional to defer creation until we are sure store is fully initialized (startQpc) std::optional qpcConverter_; std::unordered_map swapChains_; }; diff --git a/IntelPresentMon/PresentMonService/FrameBroadcaster.h b/IntelPresentMon/PresentMonService/FrameBroadcaster.h index 623e3877..635eba3c 100644 --- a/IntelPresentMon/PresentMonService/FrameBroadcaster.h +++ b/IntelPresentMon/PresentMonService/FrameBroadcaster.h @@ -21,7 +21,6 @@ namespace pmon::svc std::shared_ptr RegisterTarget(uint32_t pid, bool isPlayback, bool isBackpressured) { std::lock_guard lk{ mtx_ }; - const auto startQpc = util::GetCurrentTimestamp(); auto pSegment = comms_.CreateOrGetFrameDataSegment(pid, isBackpressured); auto& store = pSegment->GetStore(); auto& book = store.bookkeeping; @@ -29,16 +28,14 @@ namespace pmon::svc if (!book.bookkeepingInitComplete) { book.processId = pid; // we can also init this static here book.isPlayback = isPlayback; - book.startQpc = startQpc; + book.startQpc = startQpc_; // this member is optionally lazy initialized book.bookkeepingInitComplete = true; } else { // sanity checks for subsequent opens - if (book.processId != pid || book.isPlayback != isPlayback || - book.startQpc >= startQpc) { + if (book.processId != pid || book.isPlayback != isPlayback) { pmlog_error("Mismatch in bookkeeping data") .pmwatch(book.processId).pmwatch(pid) - .pmwatch(book.isPlayback).pmwatch(isPlayback) - .pmwatch(book.startQpc).pmwatch(startQpc); + .pmwatch(book.isPlayback).pmwatch(isPlayback); } } // initialize name/pid statics on new store segment creation @@ -75,6 +72,7 @@ namespace pmon::svc } } } + std::vector GetPids() const { std::lock_guard lk{ mtx_ }; @@ -84,9 +82,30 @@ namespace pmon::svc { return comms_.GetNamer(); } + // TODO: consider how this works when multiple trace sessions (realtime and playback) + // are able to be in flight simultaneously + void SetStartQpc(int64_t startQpc) + { + if (startQpc_ == 0) { + std::lock_guard lk{ mtx_ }; + // set qpc here so that future stores are initialized with it + startQpc_ = startQpc; + // make sure all existing stores get updated right now + for (auto pid : comms_.GetFramePids()) { + try { + auto pSeg = comms_.GetFrameDataSegment(pid); + pSeg->GetStore().bookkeeping.startQpc = startQpc_; + } + catch (...) { + pmlog_warn("Failed getting store for pid, might just be closure race").pmwatch(pid); + } + } + } + } private: // data ipc::ServiceComms& comms_; mutable std::mutex mtx_; + int64_t startQpc_ = 0; }; } diff --git a/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp b/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp index 1493ebd8..a0ec649c 100644 --- a/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp +++ b/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp @@ -239,6 +239,9 @@ void MockPresentMonSession::AddPresents( if (session_active_.load(std::memory_order_acquire)) { assert(trace_session_.mStartTimestamp.QuadPart != 0); streamer_.SetStartQpc(trace_session_.mStartTimestamp.QuadPart); + if (pBroadcaster) { + pBroadcaster->SetStartQpc(trace_session_.mStartTimestamp.QuadPart); + } } for (auto n = presentEvents.size(); i < n; ++i) { diff --git a/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp b/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp index ece657e6..1d09fc72 100644 --- a/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp +++ b/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp @@ -236,6 +236,9 @@ void RealtimePresentMonSession::AddPresents( if (session_active_.load(std::memory_order_acquire)) { if (trace_session_.mStartTimestamp.QuadPart != 0) { streamer_.SetStartQpc(trace_session_.mStartTimestamp.QuadPart); + if (pBroadcaster) { + pBroadcaster->SetStartQpc(trace_session_.mStartTimestamp.QuadPart); + } } } From 318896001748e4617070402e9465aec8a10d2bc2 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 21 Jan 2026 08:57:01 +0900 Subject: [PATCH 133/205] calculate derived gpu telemetry --- .../Interprocess/source/DataStores.cpp | 24 +-------- .../source/IntrospectionCapsLookup.h | 6 +-- .../source/MetricCapabilitiesShim.cpp | 34 +++++++++++++ .../InterimBroadcasterTests.cpp | 5 -- .../PresentMonService/PMMainThread.cpp | 51 +++++++++++++++++++ 5 files changed, 90 insertions(+), 30 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/DataStores.cpp b/IntelPresentMon/Interprocess/source/DataStores.cpp index 0e0fa616..67abfa17 100644 --- a/IntelPresentMon/Interprocess/source/DataStores.cpp +++ b/IntelPresentMon/Interprocess/source/DataStores.cpp @@ -2,7 +2,6 @@ #include "MetricCapabilities.h" #include "IntrospectionTransfer.h" #include "IntrospectionDataTypeMapping.h" -#include "IntrospectionCapsLookup.h" #include "../../CommonUtilities/Meta.h" #include "../../CommonUtilities/Memory.h" #include "../../CommonUtilities/log/Verbose.h" @@ -49,30 +48,11 @@ namespace pmon::ipc return safeValueBytes + pad + sizeof(uint64_t); } - using MetricEnum_ = PM_METRIC; - using MetricUnderlying_ = std::underlying_type_t; - constexpr MetricUnderlying_ kMaxMetricUnderlying_ = 256; - - struct MiddlewareDerivedLookup_ - { - template - constexpr bool operator()() const - { - using Lookup = intro::IntrospectionCapsLookup; - return intro::IsMiddlewareDerivedMetric; - } - }; - - bool ShouldAllocateTelemetryRing_(PM_METRIC metricId, - const intro::IntrospectionMetric& metric) + bool ShouldAllocateTelemetryRing_(const intro::IntrospectionMetric& metric) { if (metric.GetMetricType() == PM_METRIC_TYPE_STATIC) { return false; } - if (util::DispatchEnumValue( - metricId, MiddlewareDerivedLookup_{}, false)) { - return false; - } return true; } @@ -104,7 +84,7 @@ namespace pmon::ipc throw std::logic_error( "DataStoreSizingInfo caps contain a metric outside the expected device type"); } - if (!ShouldAllocateTelemetryRing_(metricId, metric)) { + if (!ShouldAllocateTelemetryRing_(metric)) { continue; } const auto dataType = metric.GetDataTypeInfo().GetFrameType(); diff --git a/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h b/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h index 07892961..81b98408 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h +++ b/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h @@ -34,13 +34,13 @@ namespace pmon::ipc::intro GpuTelemetryCapBits::fan_speed_2, GpuTelemetryCapBits::fan_speed_3, GpuTelemetryCapBits::fan_speed_4, }; }; template<> struct IntrospectionCapsLookup { - using MiddlewareDerived = std::true_type; + using Derived = std::true_type; static constexpr auto gpuCapBitArray = std::array{ GpuTelemetryCapBits::max_fan_speed_0, GpuTelemetryCapBits::max_fan_speed_1, GpuTelemetryCapBits::max_fan_speed_2, GpuTelemetryCapBits::max_fan_speed_3, GpuTelemetryCapBits::max_fan_speed_4, }; }; template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_mem_used; }; template<> struct IntrospectionCapsLookup { - using MiddlewareDerived = std::true_type; + using Derived = std::true_type; static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_mem_used; }; template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_mem_write_bandwidth; }; @@ -82,5 +82,5 @@ namespace pmon::ipc::intro template concept IsGpuDeviceStaticMetric = requires { typename T::GpuDeviceStatic; }; template concept IsCpuMetric = requires { T::cpuCapBit; }; template concept IsManualDisableMetric = requires { typename T::ManualDisable; }; - template concept IsMiddlewareDerivedMetric = requires { typename T::MiddlewareDerived; }; + template concept IsDerivedMetric = requires { typename T::Derived; }; } diff --git a/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp b/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp index 403e86b4..9efa6fd4 100644 --- a/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp +++ b/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp @@ -1,6 +1,7 @@ #include "MetricCapabilitiesShim.h" #include "IntrospectionCapsLookup.h" #include "../../CommonUtilities/Meta.h" +#include namespace pmon::ipc::intro { @@ -19,12 +20,28 @@ namespace pmon::ipc::intro return bits.test(static_cast(index)); } + template + size_t CountGpuCaps_(const GpuTelemetryBitset& bits, const ArrayT& caps) + { + size_t count = 0; + for (auto flag : caps) { + if (HasCap(bits, flag)) { + ++count; + } + } + return count; + } + // GPU per-metric accumulation (only instantiated for valid enum values) template void AccumulateGpuCapability(MetricCapabilities& caps, const GpuTelemetryBitset& bits) { using Lookup = IntrospectionCapsLookup; + if constexpr (IsDerivedMetric) { + return; + } + // Single GPU capability bit -> metric present if bit set if constexpr (IsGpuDeviceMetric) { if (HasCap(bits, Lookup::gpuCapBit)) { @@ -51,6 +68,22 @@ namespace pmon::ipc::intro } } + void AccumulateDerivedGpuCapabilities_(MetricCapabilities& caps, const GpuTelemetryBitset& bits) + { + const auto fanCount = caps.Check(PM_METRIC_GPU_FAN_SPEED); + const auto maxFanCount = CountGpuCaps_(bits, + IntrospectionCapsLookup::gpuCapBitArray); + const auto derivedFanCount = std::min(fanCount, maxFanCount); + if (derivedFanCount > 0) { + caps.Set(PM_METRIC_GPU_FAN_SPEED_PERCENT, derivedFanCount); + } + + if (caps.Check(PM_METRIC_GPU_MEM_USED) > 0 && + caps.Check(PM_METRIC_GPU_MEM_SIZE) > 0) { + caps.Set(PM_METRIC_GPU_MEM_UTILIZATION, 1); + } + } + // CPU per-metric accumulation (only instantiated for valid enum values) template void AccumulateCpuCapability(MetricCapabilities& caps, const CpuTelemetryBitset& bits) @@ -73,6 +106,7 @@ namespace pmon::ipc::intro [&]() { detail::AccumulateGpuCapability(caps, bits); }); + detail::AccumulateDerivedGpuCapabilities_(caps, bits); return caps; } diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 5dc336b9..2975665d 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -512,11 +512,6 @@ namespace InterimBroadcasterTests m.GetType() != PM_METRIC_TYPE_DYNAMIC_FRAME) { continue; } - // some polled metrics are derived in middleware thus not present in shm - if (m.GetId() == PM_METRIC_GPU_MEM_UTILIZATION || - m.GetId() == PM_METRIC_GPU_FAN_SPEED_PERCENT) { - continue; - } // check availability for target gpu size_t arraySize = 0; for (auto&& di : m.GetDeviceMetricInfo()) { diff --git a/IntelPresentMon/PresentMonService/PMMainThread.cpp b/IntelPresentMon/PresentMonService/PMMainThread.cpp index d80f63c0..b41034db 100644 --- a/IntelPresentMon/PresentMonService/PMMainThread.cpp +++ b/IntelPresentMon/PresentMonService/PMMainThread.cpp @@ -16,6 +16,7 @@ #include "CliOptions.h" #include "Registry.h" #include "GlobalIdentifiers.h" +#include #include #include "../CommonUtilities/IntervalWaiter.h" #include "../CommonUtilities/PrecisionWaiter.h" @@ -85,12 +86,46 @@ void EventFlushThreadEntry_(Service* const srv, PresentMon* const pm) } } +struct DerivedGpuTelemetry_ +{ + double memUtilization = 0.0; + std::array fanSpeedPercent{}; + size_t fanSpeedPercentCount = 0; +}; + +static DerivedGpuTelemetry_ CalculateDerivedGpuTelemetry_( + const ipc::GpuDataStore& store, + const PresentMonPowerTelemetryInfo& s) noexcept +{ + DerivedGpuTelemetry_ out{}; + if (store.statics.memSize > 0) { + out.memUtilization = 100.0 * (static_cast(s.gpu_mem_used_b) / + static_cast(store.statics.memSize)); + } + + const size_t maxFanCount = store.statics.maxFanSpeedRpm.size(); + const size_t fanCount = std::min(maxFanCount, s.fan_speed_rpm.size()); + out.fanSpeedPercentCount = fanCount; + for (size_t i = 0; i < fanCount; ++i) { + const auto maxRpm = store.statics.maxFanSpeedRpm[i]; + if (maxRpm > 0) { + out.fanSpeedPercent[i] = s.fan_speed_rpm[i] / static_cast(maxRpm); + } + else { + out.fanSpeedPercent[i] = 0.0; + } + } + + return out; +} + // Translate a single power-telemetry sample into the rings for one GPU store. // This mirrors the CPU placeholder approach but handles double/uint64/bool rings. static void PopulateGpuTelemetryRings_( ipc::GpuDataStore& store, const PresentMonPowerTelemetryInfo& s) noexcept { + const auto derived = CalculateDerivedGpuTelemetry_(store, s); for (auto&& [metric, ringVariant] : store.telemetryData.Rings()) { switch (metric) { @@ -178,6 +213,17 @@ static void PopulateGpuTelemetryRings_( break; } + case PM_METRIC_GPU_FAN_SPEED_PERCENT: + { + auto& ringVect = + std::get>(ringVariant); + const size_t n = std::min(ringVect.size(), derived.fanSpeedPercentCount); + for (size_t i = 0; i < n; ++i) { + ringVect[i].Push(derived.fanSpeedPercent[i], s.qpc); + } + break; + } + // VRAM-related doubles case PM_METRIC_GPU_MEM_POWER: std::get>(ringVariant)[0] @@ -215,6 +261,11 @@ static void PopulateGpuTelemetryRings_( .Push(s.gpu_mem_read_bandwidth_bps, s.qpc); break; + case PM_METRIC_GPU_MEM_UTILIZATION: + std::get>(ringVariant)[0] + .Push(derived.memUtilization, s.qpc); + break; + // -------- uint64 metrics -------- case PM_METRIC_GPU_MEM_USED: std::get>(ringVariant)[0] From 0b476def3224d63f65dcac60942d9faa5d654a4c Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 21 Jan 2026 09:39:29 +0900 Subject: [PATCH 134/205] fixed queries have backpressure/playback options --- .../PresentMonAPI2Tests/InterimBroadcasterTests.cpp | 7 ++++--- IntelPresentMon/PresentMonAPIWrapper/FixedQuery.cpp | 11 ++++++++--- IntelPresentMon/PresentMonAPIWrapper/FixedQuery.h | 7 +++++-- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 2975665d..9f4bf701 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -1191,7 +1191,7 @@ namespace InterimBroadcasterTests // we know the pid of interest in this etl file, track it const uint32_t pid = 19736; - auto tracker = session.TrackProcess(pid); + auto tracker = query.TrackProcess(pid, true, true); // sleep here to let the etw system warm up, and frames propagate std::this_thread::sleep_for(300ms); @@ -1273,7 +1273,7 @@ namespace InterimBroadcasterTests // we know the pid of interest in this etl file, track it const uint32_t pid = 12820; - auto tracker = session.TrackProcess(pid); + auto tracker = query.TrackProcess(pid, true, true); // sleep here to let the etw system warm up, and frames propagate std::this_thread::sleep_for(300ms); @@ -1319,7 +1319,8 @@ namespace InterimBroadcasterTests const auto total = count1 + count2 + count3; // known issue with PresentData is that it sometimes outputs 24 rogue frames at // the end for P00; we can ignore these for the time being, issue added to board - Assert::IsTrue(total == 1903u || total == 1927u); + Logger::WriteMessage(std::format("Total frames: {}\n", total).c_str()); + Assert::IsTrue(total == 1902u); } }; } diff --git a/IntelPresentMon/PresentMonAPIWrapper/FixedQuery.cpp b/IntelPresentMon/PresentMonAPIWrapper/FixedQuery.cpp index 14ba552a..be59823d 100644 --- a/IntelPresentMon/PresentMonAPIWrapper/FixedQuery.cpp +++ b/IntelPresentMon/PresentMonAPIWrapper/FixedQuery.cpp @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "FixedQuery.h" #include #include @@ -46,6 +46,12 @@ namespace pmapi return activeBlobIndex_; } + ProcessTracker FixedQueryContainer_::TrackProcess(uint32_t pid, bool isPlayback, bool isBackpressured) + { + assert(pSession_); + return pSession_->TrackProcess(pid, isPlayback, isBackpressured); + } + void FixedQueryContainer_::FinalizationPreprocess_() { // replace slot indexes with device ids @@ -77,7 +83,6 @@ namespace pmapi smartElements_.clear(); rawElements_.clear(); slotDeviceIds_.clear(); - pSession_ = nullptr; } @@ -182,4 +187,4 @@ namespace pmapi { return dataType_ != PM_DATA_TYPE_VOID; } -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonAPIWrapper/FixedQuery.h b/IntelPresentMon/PresentMonAPIWrapper/FixedQuery.h index 885f1b01..ea764cac 100644 --- a/IntelPresentMon/PresentMonAPIWrapper/FixedQuery.h +++ b/IntelPresentMon/PresentMonAPIWrapper/FixedQuery.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include #include #include @@ -52,6 +52,9 @@ namespace pmapi const uint8_t* PeekActiveBlob() const; // get index of the current active blob size_t GetActiveBlobIndex() const; + // begin tracking a process using the session associated with this fixed query + // set isPlayback and isBackpressured when tracking ETL playback + ProcessTracker TrackProcess(uint32_t pid, bool isPlayback = false, bool isBackpressured = false); protected: friend class FixedQueryElement; // functions @@ -296,4 +299,4 @@ namespace pmapi // begin a fixed frame query #define PM_BEGIN_FIXED_FRAME_QUERY(type) struct type : pmapi::FixedFrameQueryContainer { using FixedFrameQueryContainer::FixedFrameQueryContainer; // end a fixed query (dynamic or frame) -#define PM_END_FIXED_QUERY private: pmapi::FinalizingElement finalizer{ this }; } \ No newline at end of file +#define PM_END_FIXED_QUERY private: pmapi::FinalizingElement finalizer{ this }; } From 6a2879b37f9f105bbd44f179ecc66108ceca7c34 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 21 Jan 2026 10:35:26 +0900 Subject: [PATCH 135/205] remove quota status --- IntelPresentMon/Interprocess/source/metadata/EnumStatus.h | 1 - IntelPresentMon/PresentMonAPI2/PresentMonAPI.h | 1 - 2 files changed, 2 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/metadata/EnumStatus.h b/IntelPresentMon/Interprocess/source/metadata/EnumStatus.h index 8276975f..6aa69d88 100644 --- a/IntelPresentMon/Interprocess/source/metadata/EnumStatus.h +++ b/IntelPresentMon/Interprocess/source/metadata/EnumStatus.h @@ -25,5 +25,4 @@ X_(STATUS, MIDDLEWARE_VERSION_HIGH, "Middleware Version High", "", "Middleware DLL version was found to be too high for compatibility") \ X_(STATUS, MIDDLEWARE_SERVICE_MISMATCH, "Middleware Service Mismatch", "", "Middleware DLL build ID does not match that of the service") \ X_(STATUS, QUERY_MALFORMED, "Query Malformed", "", "There are errors or inconsistencies in the elements of the query being registered") \ - X_(STATUS, QUERY_QUOTA_EXCEEDED, "Query Quota Exceeded", "", "Limit on the number of concurrently registered queries is exceeded (1 max for frame queries)") \ X_(STATUS, MODE_MISMATCH, "Mode Mismatch", "", "Operation is not valid for the current service mode") diff --git a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h index d0f86847..0a1ffecb 100644 --- a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h +++ b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h @@ -43,7 +43,6 @@ extern "C" { PM_STATUS_MIDDLEWARE_VERSION_HIGH, PM_STATUS_MIDDLEWARE_SERVICE_MISMATCH, PM_STATUS_QUERY_MALFORMED, - PM_STATUS_QUERY_QUOTA_EXCEEDED, PM_STATUS_MODE_MISMATCH, }; From 1ff7b4e21c73a88d92564dceafaf520e4f57eb4a Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 21 Jan 2026 14:25:07 +0900 Subject: [PATCH 136/205] static query and tests --- .../Interprocess/source/DataStores.cpp | 46 +++++++++++ .../Interprocess/source/DataStores.h | 17 +++- .../Interprocess/source/SharedMemoryTypes.h | 4 + .../IpcMcIntegrationTests.cpp | 77 ++++++++++++++----- .../ConcreteMiddleware.cpp | 31 +++++--- 5 files changed, 143 insertions(+), 32 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/DataStores.cpp b/IntelPresentMon/Interprocess/source/DataStores.cpp index 67abfa17..8a7cbed0 100644 --- a/IntelPresentMon/Interprocess/source/DataStores.cpp +++ b/IntelPresentMon/Interprocess/source/DataStores.cpp @@ -2,6 +2,7 @@ #include "MetricCapabilities.h" #include "IntrospectionTransfer.h" #include "IntrospectionDataTypeMapping.h" +#include "PmStatusError.h" #include "../../CommonUtilities/Meta.h" #include "../../CommonUtilities/Memory.h" #include "../../CommonUtilities/log/Verbose.h" @@ -145,6 +146,17 @@ namespace pmon::ipc return totalBytes; } + StaticMetricValue FrameDataStore::FindStaticMetric(PM_METRIC metric) const + { + switch (metric) { + case PM_METRIC_APPLICATION: + return statics.applicationName.c_str(); + default: + throw util::Except(PM_STATUS_QUERY_MALFORMED, + "Static metric not handled by frame data store"); + } + } + void PopulateTelemetryRings(TelemetryMap& telemetryData, const DataStoreSizingInfo& sizing, PM_DEVICE_TYPE deviceType) @@ -160,9 +172,43 @@ namespace pmon::ipc return TelemetrySegmentBytes_(sizing, PM_DEVICE_TYPE_GRAPHICS_ADAPTER); } + StaticMetricValue GpuDataStore::FindStaticMetric(PM_METRIC metric) const + { + switch (metric) { + case PM_METRIC_GPU_VENDOR: + return int32_t(statics.vendor); + case PM_METRIC_GPU_NAME: + return statics.name.c_str(); + case PM_METRIC_GPU_SUSTAINED_POWER_LIMIT: + return statics.sustainedPowerLimit; + case PM_METRIC_GPU_MEM_SIZE: + return statics.memSize; + case PM_METRIC_GPU_MEM_MAX_BANDWIDTH: + return statics.maxMemBandwidth; + default: + throw util::Except(PM_STATUS_QUERY_MALFORMED, + "Static metric not handled by gpu data store"); + } + } + size_t SystemDataStore::CalculateSegmentBytes(const DataStoreSizingInfo& sizing) { return TelemetrySegmentBytes_(sizing, PM_DEVICE_TYPE_SYSTEM); } + + StaticMetricValue SystemDataStore::FindStaticMetric(PM_METRIC metric) const + { + switch (metric) { + case PM_METRIC_CPU_VENDOR: + return int32_t(statics.cpuVendor); + case PM_METRIC_CPU_NAME: + return statics.cpuName.c_str(); + case PM_METRIC_CPU_POWER_LIMIT: + return statics.cpuPowerLimit; + default: + throw util::Except(PM_STATUS_QUERY_MALFORMED, + "Static metric not handled by system data store"); + } + } } diff --git a/IntelPresentMon/Interprocess/source/DataStores.h b/IntelPresentMon/Interprocess/source/DataStores.h index a397d4b0..4ddca06e 100644 --- a/IntelPresentMon/Interprocess/source/DataStores.h +++ b/IntelPresentMon/Interprocess/source/DataStores.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "SharedMemoryTypes.h" #include "ShmRing.h" #include "TelemetryMap.h" @@ -35,6 +35,15 @@ namespace pmon::ipc bool backpressured = false; }; + using StaticMetricValue = std::variant< + double, + uint64_t, + int32_t, + uint32_t, + bool, + int64_t, + const char*>; + struct FrameDataStore { FrameDataStore(ShmSegmentManager& segMan, size_t cap, bool backpressured) @@ -63,6 +72,8 @@ namespace pmon::ipc } bookkeeping{}; ShmRing frameData; + StaticMetricValue FindStaticMetric(PM_METRIC metric) const; + static size_t CalculateSegmentBytes(const DataStoreSizingInfo& sizing); }; @@ -89,6 +100,8 @@ namespace pmon::ipc } statics; TelemetryMap telemetryData; + StaticMetricValue FindStaticMetric(PM_METRIC metric) const; + static size_t CalculateSegmentBytes(const DataStoreSizingInfo& sizing); }; @@ -110,6 +123,8 @@ namespace pmon::ipc } statics; TelemetryMap telemetryData; + StaticMetricValue FindStaticMetric(PM_METRIC metric) const; + static size_t CalculateSegmentBytes(const DataStoreSizingInfo& sizing); }; diff --git a/IntelPresentMon/Interprocess/source/SharedMemoryTypes.h b/IntelPresentMon/Interprocess/source/SharedMemoryTypes.h index 85d089fe..bbb5f6a0 100644 --- a/IntelPresentMon/Interprocess/source/SharedMemoryTypes.h +++ b/IntelPresentMon/Interprocess/source/SharedMemoryTypes.h @@ -41,4 +41,8 @@ namespace pmon::ipc { return impl::ShmMakeUnique_(name.c_str(), pSegmentManager, std::forward

(params)...); } + inline std::string_view ToStringView(const ShmString& value) + { + return std::string_view{ value.c_str(), value.size() }; + } } \ No newline at end of file diff --git a/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp index cfeca0a7..15b9951e 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include using namespace Microsoft::VisualStudio::CppUnitTestFramework; @@ -68,6 +68,21 @@ namespace IpcMcIntegrationTests return elements; } + static std::optional FindFirstGpuDeviceId_(const pmapi::intro::Root& intro) + { + for (auto device : intro.GetDevices()) { + if (device.GetType() == PM_DEVICE_TYPE_GRAPHICS_ADAPTER) { + return device.GetId(); + } + } + return std::nullopt; + } + + static std::string GetVendorName_(const pmapi::intro::Root& intro, PM_DEVICE_VENDOR vendor) + { + return intro.FindEnumKey(PM_ENUM_DEVICE_VENDOR, static_cast(vendor)).GetName(); + } + TEST_CLASS(IpcMcIntegrationTests) { TestFixture fixture_; @@ -114,24 +129,46 @@ namespace IpcMcIntegrationTests Assert::IsTrue(gotFrames, L"Expected frame query to consume frames"); } - //TEST_METHOD(SecondFrameQueryRegistrationFails) - //{ - // pmapi::Session session{ fixture_.GetCommonArgs().ctrlPipe }; - // auto intro = session.GetIntrospectionRoot(); - // Assert::IsTrue((bool)intro); - - // auto elements = BuildUniversalFrameQueryElements_(*intro); - // Assert::IsTrue(!elements.empty(), L"No universal frame metrics found"); - - // // first registration succeeds - // std::vector single{ elements.front() }; - // auto query = session.RegisterFrameQuery(single); - // Assert::IsTrue((bool)query); - - // // second registration fails - // Assert::ExpectException([&] { - // session.RegisterFrameQuery(single); - // }); - //} + TEST_METHOD(StaticQueryReturnsExpectedValues) + { + pmapi::Session session{ fixture_.GetCommonArgs().ctrlPipe }; + auto intro = session.GetIntrospectionRoot(); + Assert::IsTrue((bool)intro); + + const auto gpuDeviceId = FindFirstGpuDeviceId_(*intro); + Assert::IsTrue(gpuDeviceId.has_value(), L"No GPU device found"); + + auto presenter = fixture_.LaunchPresenter(); + auto tracker = session.TrackProcess(presenter.GetId()); + + const auto cpuName = pmapi::PollStatic(session, tracker, PM_METRIC_CPU_NAME, ipc::kSystemDeviceId) + .As(); + Logger::WriteMessage(std::format("CPU name: {}\n", cpuName).c_str()); + Assert::IsTrue(!cpuName.empty(), L"CPU name empty"); + + const auto cpuVendor = pmapi::PollStatic(session, tracker, PM_METRIC_CPU_VENDOR, ipc::kSystemDeviceId) + .As(); + const auto cpuVendorName = GetVendorName_(*intro, cpuVendor); + Logger::WriteMessage(std::format("CPU vendor: {}\n", cpuVendorName).c_str()); + Assert::IsTrue(!cpuVendorName.empty(), L"CPU vendor name empty"); + + const auto gpuName = pmapi::PollStatic(session, tracker, PM_METRIC_GPU_NAME, *gpuDeviceId) + .As(); + const auto gpuVendor = pmapi::PollStatic(session, tracker, PM_METRIC_GPU_VENDOR, *gpuDeviceId) + .As(); + const auto gpuMemSize = pmapi::PollStatic(session, tracker, PM_METRIC_GPU_MEM_SIZE, *gpuDeviceId) + .As(); + Logger::WriteMessage(std::format("GPU name: {}\n", gpuName).c_str()); + Assert::IsTrue(!gpuName.empty(), L"GPU name empty"); + const auto gpuVendorName = GetVendorName_(*intro, gpuVendor); + Logger::WriteMessage(std::format("GPU vendor: {}\n", gpuVendorName).c_str()); + Assert::IsTrue(!gpuVendorName.empty(), L"GPU vendor name empty"); + Logger::WriteMessage(std::format("GPU memory size: {}\n", gpuMemSize).c_str()); + Assert::IsTrue(gpuMemSize > 0, L"GPU memory size not available"); + + const auto appName = pmapi::PollStatic(session, tracker, PM_METRIC_APPLICATION).As(); + Logger::WriteMessage(std::format("Application name: {}\n", appName).c_str()); + Assert::IsTrue(appName == "PresentBench.exe", L"Unexpected application name"); + } }; } diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index 200833f4..e0ac6c05 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -1281,18 +1282,26 @@ static void ReportMetrics( void ConcreteMiddleware::PollStaticQuery(const PM_QUERY_ELEMENT& element, uint32_t processId, uint8_t* pBlob) { - auto& ispec = GetIntrospectionRoot(); - auto metricView = ispec.FindMetric(element.metric); - if (metricView.GetType() != int(PM_METRIC_TYPE_STATIC)) { - pmlog_error(std::format("dynamic metric [{}] in static query poll", metricView.Introspect().GetSymbol())).diag(); - throw Except("dynamic metric in static query poll"); - } - - auto elementSize = GetDataTypeSize(metricView.GetDataTypeInfo().GetPolledType()); - - CopyStaticMetricData(element.metric, element.deviceId, pBlob, 0, elementSize); + const ipc::StaticMetricValue value = [&]() { + if (element.deviceId == ipc::kSystemDeviceId) { + return pComms->GetSystemDataStore().FindStaticMetric(element.metric); + } + if (element.deviceId == ipc::kUniversalDeviceId) { + return pComms->GetFrameDataStore(processId).FindStaticMetric(element.metric); + } + return pComms->GetGpuDataStore(element.deviceId).FindStaticMetric(element.metric); + }(); - return; + std::visit([&](auto&& v) { + using T = std::decay_t; + // need stringcopy instead of memcpy for string type data (null terminator) + if constexpr (std::is_same_v) { + strncpy_s(reinterpret_cast(pBlob), PM_MAX_PATH, v, _TRUNCATE); + } + else { + std::memcpy(pBlob, &v, sizeof(v)); + } + }, value); } PM_FRAME_QUERY* mid::ConcreteMiddleware::RegisterFrameEventQuery(std::span queryElements, uint32_t& blobSize) From 46f54d68c36f02bb4bbb1d7262dca75717ebc340 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 21 Jan 2026 16:42:02 +0900 Subject: [PATCH 137/205] filling static metrics in frame query, and tests --- .../source/IntrospectionCapsLookup.h | 5 +- .../source/MetricCapabilitiesShim.cpp | 4 + .../InterimBroadcasterTests.cpp | 1 - .../IpcMcIntegrationTests.cpp | 182 +++++++++++++++++- .../ConcreteMiddleware.cpp | 4 +- .../PresentMonMiddleware/FrameEventQuery.cpp | 91 ++++++--- .../PresentMonMiddleware/FrameEventQuery.h | 16 +- .../SampleClient/PacedFramePlayback.cpp | 7 +- 8 files changed, 277 insertions(+), 33 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h b/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h index 81b98408..fd87fcbe 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h +++ b/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h @@ -72,7 +72,9 @@ namespace pmon::ipc::intro }; // static CPU template<> struct IntrospectionCapsLookup { static constexpr auto cpuCapBit = CpuTelemetryCapBits::cpu_power_limit; }; - // TODO: what about name/vendor? (currently functioning as universal) + template<> struct IntrospectionCapsLookup { using CpuStatic = std::true_type; }; + template<> struct IntrospectionCapsLookup { using CpuStatic = std::true_type; }; + // TODO: consider additional static CPU metrics beyond name/vendor. // concepts to help determine device-metric mapping type @@ -81,6 +83,7 @@ namespace pmon::ipc::intro template concept IsGpuDeviceMetricArray = requires { T::gpuCapBitArray; }; template concept IsGpuDeviceStaticMetric = requires { typename T::GpuDeviceStatic; }; template concept IsCpuMetric = requires { T::cpuCapBit; }; + template concept IsCpuStaticMetric = requires { typename T::CpuStatic; }; template concept IsManualDisableMetric = requires { typename T::ManualDisable; }; template concept IsDerivedMetric = requires { typename T::Derived; }; } diff --git a/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp b/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp index 9efa6fd4..c06c99c7 100644 --- a/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp +++ b/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp @@ -96,6 +96,10 @@ namespace pmon::ipc::intro caps.Set(Metric, 1); } } + + if constexpr (IsCpuStaticMetric) { + caps.Set(Metric, 1); + } } } // namespace detail diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 9f4bf701..05cfaa2f 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -1088,7 +1088,6 @@ namespace InterimBroadcasterTests Assert::AreEqual(0ull, range1.first); Assert::IsTrue(range2.first <= range1.second); Assert::IsTrue(range3.first <= range2.second); - Assert::AreEqual(1905ull, range3.second); // known issue with PresentData is that it sometimes outputs 24 rogue frames at // the end for P00; we can ignore these for the time being, issue added to board Assert::IsTrue(range3.second == 1905ull || range3.second == 1929ull); diff --git a/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp index 15b9951e..d127eaf9 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp @@ -80,7 +80,118 @@ namespace IpcMcIntegrationTests static std::string GetVendorName_(const pmapi::intro::Root& intro, PM_DEVICE_VENDOR vendor) { - return intro.FindEnumKey(PM_ENUM_DEVICE_VENDOR, static_cast(vendor)).GetName(); + return intro.FindEnumKey(PM_ENUM_DEVICE_VENDOR, (int)vendor).GetName(); + } + + static std::vector BuildStaticFrameQueryElements_(uint32_t gpuDeviceId) + { + return { + PM_QUERY_ELEMENT{ + .metric = PM_METRIC_CPU_NAME, + .stat = PM_STAT_NONE, + .deviceId = ipc::kSystemDeviceId, + .arrayIndex = 0, + .dataOffset = 0, + .dataSize = 0, + }, + PM_QUERY_ELEMENT{ + .metric = PM_METRIC_CPU_VENDOR, + .stat = PM_STAT_NONE, + .deviceId = ipc::kSystemDeviceId, + .arrayIndex = 0, + .dataOffset = 0, + .dataSize = 0, + }, + PM_QUERY_ELEMENT{ + .metric = PM_METRIC_GPU_NAME, + .stat = PM_STAT_NONE, + .deviceId = gpuDeviceId, + .arrayIndex = 0, + .dataOffset = 0, + .dataSize = 0, + }, + PM_QUERY_ELEMENT{ + .metric = PM_METRIC_GPU_VENDOR, + .stat = PM_STAT_NONE, + .deviceId = gpuDeviceId, + .arrayIndex = 0, + .dataOffset = 0, + .dataSize = 0, + }, + PM_QUERY_ELEMENT{ + .metric = PM_METRIC_GPU_MEM_SIZE, + .stat = PM_STAT_NONE, + .deviceId = gpuDeviceId, + .arrayIndex = 0, + .dataOffset = 0, + .dataSize = 0, + }, + PM_QUERY_ELEMENT{ + .metric = PM_METRIC_APPLICATION, + .stat = PM_STAT_NONE, + .deviceId = ipc::kUniversalDeviceId, + .arrayIndex = 0, + .dataOffset = 0, + .dataSize = 0, + }, + }; + } + + static const PM_QUERY_ELEMENT* FindQueryElement_(const std::vector& elements, PM_METRIC metric, uint32_t deviceId) + { + for (const auto& element : elements) { + if (element.metric == metric && element.deviceId == deviceId) { + return &element; + } + } + return nullptr; + } + + static std::string FormatQueryValue_(const pmapi::intro::Root& intro, const PM_QUERY_ELEMENT& element, const uint8_t* pBlob) + { + const auto metricView = intro.FindMetric(element.metric); + const auto dataType = metricView.GetDataTypeInfo().GetFrameType(); + const uint8_t* pData = pBlob + (size_t)element.dataOffset; + + switch (dataType) { + case PM_DATA_TYPE_UINT64: + return std::format("{}", *reinterpret_cast(pData)); + case PM_DATA_TYPE_INT32: + return std::format("{}", *reinterpret_cast(pData)); + case PM_DATA_TYPE_UINT32: + return std::format("{}", *reinterpret_cast(pData)); + case PM_DATA_TYPE_DOUBLE: + return std::format("{:.6f}", *reinterpret_cast(pData)); + case PM_DATA_TYPE_ENUM: + { + const int enumValue = *reinterpret_cast(pData); + std::string enumName = "Unknown"; + try { + enumName = intro.FindEnumKey(metricView.GetDataTypeInfo().GetEnumId(), enumValue).GetName(); + } + catch (...) { + } + return std::format("{} ({})", enumName, enumValue); + } + case PM_DATA_TYPE_BOOL: + return *reinterpret_cast(pData) ? "true" : "false"; + case PM_DATA_TYPE_STRING: + return std::string(reinterpret_cast(pData)); + case PM_DATA_TYPE_VOID: + default: + return "void"; + } + } + + static void LogFrameQueryResults_(const pmapi::intro::Root& intro, const std::vector& elements, const uint8_t* pBlob) + { + for (const auto& element : elements) { + const auto metricView = intro.FindMetric(element.metric); + const auto value = FormatQueryValue_(intro, element, pBlob); + Logger::WriteMessage(std::format("{}, {}\n", + metricView.Introspect().GetSymbol(), + value).c_str()); + } } TEST_CLASS(IpcMcIntegrationTests) @@ -122,6 +233,8 @@ namespace IpcMcIntegrationTests query.Consume(tracker, blobs); if (blobs.GetNumBlobsPopulated() > 0) { gotFrames = true; + Logger::WriteMessage("Universal frame query results:\n"); + LogFrameQueryResults_(*intro, elements, blobs[0]); break; } std::this_thread::sleep_for(25ms); @@ -129,6 +242,73 @@ namespace IpcMcIntegrationTests Assert::IsTrue(gotFrames, L"Expected frame query to consume frames"); } + TEST_METHOD(FrameQueryStaticMetricsAreFilled) + { + pmapi::Session session{ fixture_.GetCommonArgs().ctrlPipe }; + auto intro = session.GetIntrospectionRoot(); + Assert::IsTrue((bool)intro); + + const auto gpuDeviceId = FindFirstGpuDeviceId_(*intro); + Assert::IsTrue(gpuDeviceId.has_value(), L"No GPU device found"); + + auto elements = BuildUniversalFrameQueryElements_(*intro); + auto staticElements = BuildStaticFrameQueryElements_(*gpuDeviceId); + elements.insert(elements.end(), staticElements.begin(), staticElements.end()); + Logger::WriteMessage(std::format("Frame query metrics (with statics): {}\n", elements.size()).c_str()); + + auto query = session.RegisterFrameQuery(elements); + + auto presenter = fixture_.LaunchPresenter(); + session.SetEtwFlushPeriod(8); + std::this_thread::sleep_for(1ms); + auto tracker = session.TrackProcess(presenter.GetId()); + + auto blobs = query.MakeBlobContainer(16); + bool gotFrames = false; + const auto deadline = std::chrono::steady_clock::now() + 2s; + while (std::chrono::steady_clock::now() < deadline) { + query.Consume(tracker, blobs); + if (blobs.GetNumBlobsPopulated() > 0) { + gotFrames = true; + Logger::WriteMessage("Frame query results with static metrics:\n"); + LogFrameQueryResults_(*intro, elements, blobs[0]); + break; + } + std::this_thread::sleep_for(25ms); + } + Assert::IsTrue(gotFrames, L"Expected frame query to consume frames"); + + const auto* cpuNameElement = FindQueryElement_(elements, PM_METRIC_CPU_NAME, ipc::kSystemDeviceId); + Assert::IsTrue(cpuNameElement != nullptr, L"CPU name element missing"); + const auto* cpuVendorElement = FindQueryElement_(elements, PM_METRIC_CPU_VENDOR, ipc::kSystemDeviceId); + Assert::IsTrue(cpuVendorElement != nullptr, L"CPU vendor element missing"); + const auto* gpuNameElement = FindQueryElement_(elements, PM_METRIC_GPU_NAME, *gpuDeviceId); + Assert::IsTrue(gpuNameElement != nullptr, L"GPU name element missing"); + const auto* gpuVendorElement = FindQueryElement_(elements, PM_METRIC_GPU_VENDOR, *gpuDeviceId); + Assert::IsTrue(gpuVendorElement != nullptr, L"GPU vendor element missing"); + const auto* gpuMemSizeElement = FindQueryElement_(elements, PM_METRIC_GPU_MEM_SIZE, *gpuDeviceId); + Assert::IsTrue(gpuMemSizeElement != nullptr, L"GPU memory size element missing"); + const auto* appNameElement = FindQueryElement_(elements, PM_METRIC_APPLICATION, ipc::kUniversalDeviceId); + Assert::IsTrue(appNameElement != nullptr, L"Application element missing"); + + const uint8_t* firstBlob = blobs[0]; + const auto cpuName = std::string(reinterpret_cast(firstBlob + (size_t)cpuNameElement->dataOffset)); + const int cpuVendorValue = *reinterpret_cast(firstBlob + (size_t)cpuVendorElement->dataOffset); + const auto cpuVendorName = GetVendorName_(*intro, (PM_DEVICE_VENDOR)cpuVendorValue); + const auto gpuName = std::string(reinterpret_cast(firstBlob + (size_t)gpuNameElement->dataOffset)); + const int gpuVendorValue = *reinterpret_cast(firstBlob + (size_t)gpuVendorElement->dataOffset); + const auto gpuVendorName = GetVendorName_(*intro, (PM_DEVICE_VENDOR)gpuVendorValue); + const auto gpuMemSize = *reinterpret_cast(firstBlob + (size_t)gpuMemSizeElement->dataOffset); + const auto appName = std::string(reinterpret_cast(firstBlob + (size_t)appNameElement->dataOffset)); + + Assert::IsTrue(!cpuName.empty(), L"CPU name empty"); + Assert::IsTrue(!cpuVendorName.empty(), L"CPU vendor name empty"); + Assert::IsTrue(!gpuName.empty(), L"GPU name empty"); + Assert::IsTrue(!gpuVendorName.empty(), L"GPU vendor name empty"); + Assert::IsTrue(gpuMemSize > 0, L"GPU memory size not available"); + Assert::IsTrue(appName == "PresentBench.exe", L"Unexpected application name"); + } + TEST_METHOD(StaticQueryReturnsExpectedValues) { pmapi::Session session{ fixture_.GetCommonArgs().ctrlPipe }; diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index e0ac6c05..46422550 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -1306,7 +1306,7 @@ static void ReportMetrics( PM_FRAME_QUERY* mid::ConcreteMiddleware::RegisterFrameEventQuery(std::span queryElements, uint32_t& blobSize) { - auto pQuery = new PM_FRAME_QUERY{ queryElements, *pComms, GetIntrospectionRoot() }; + auto pQuery = new PM_FRAME_QUERY{ queryElements, *this, *pComms, GetIntrospectionRoot() }; blobSize = (uint32_t)pQuery->GetBlobSize(); return pQuery; } @@ -1336,7 +1336,7 @@ static void ReportMetrics( auto frames = source->Consume(framesToCopy); assert(frames.size() <= framesToCopy); for (const auto& frameMetrics : frames) { - pQuery->GatherToBlob(pBlob, frameMetrics); + pQuery->GatherToBlob(pBlob, processId, frameMetrics); pBlob += pQuery->GetBlobSize(); } diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index c194277e..1faed429 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2017-2024 Intel Corporation #include "FrameEventQuery.h" +#include "Middleware.h" #include "../PresentMonAPIWrapperCommon/Introspection.h" #include "../PresentMonAPIWrapperCommon/Exception.h" #include "../Interprocess/source/Interprocess.h" @@ -19,9 +20,10 @@ namespace util = pmon::util; using namespace util; -PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::MiddlewareComms& comms, - const pmapi::intro::Root& introRoot) +PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, pmon::mid::Middleware& middleware, + ipc::MiddlewareComms& comms, const pmapi::intro::Root& introRoot) : + middleware_{ middleware }, comms_{ comms } { if (queryElements.empty()) { @@ -34,7 +36,9 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M for (auto& q : queryElements) { const auto metricView = introRoot.FindMetric(q.metric); - if (!pmapi::intro::MetricTypeIsFrameEvent(metricView.GetType())) { + const auto metricType = metricView.GetType(); + const bool isStaticMetric = metricType == PM_METRIC_TYPE_STATIC; + if (!pmapi::intro::MetricTypeIsFrameEvent(metricType) && !isStaticMetric) { pmlog_error("Non-frame metric used in frame query") .pmwatch(metricView.Introspect().GetSymbol()).diag(); throw Except(PM_STATUS_QUERY_MALFORMED, "Frame query contains non-frame metric"); @@ -74,27 +78,54 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M return std::nullopt; }(); - if (!deviceMetricInfo.has_value() || !deviceMetricInfo->IsAvailable()) { - pmlog_error("Metric not supported by device in frame query") - .pmwatch(metricView.Introspect().GetSymbol()) - .pmwatch(q.deviceId).diag(); - throw Except(PM_STATUS_QUERY_MALFORMED, "Metric not supported by device in frame query"); + if (!deviceMetricInfo.has_value()) { + if (!isStaticMetric || q.deviceId != ipc::kSystemDeviceId) { + pmlog_error("Metric not supported by device in frame query") + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch(q.deviceId).diag(); + throw Except(PM_STATUS_QUERY_MALFORMED, "Metric not supported by device in frame query"); + } + if (q.arrayIndex != 0) { + pmlog_error("Frame query array index out of bounds") + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch(q.arrayIndex) + .pmwatch(1).diag(); + throw Except(PM_STATUS_QUERY_MALFORMED, "Frame query array index out of bounds"); + } } + else { + if (!deviceMetricInfo->IsAvailable()) { + pmlog_error("Metric not supported by device in frame query") + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch(q.deviceId).diag(); + throw Except(PM_STATUS_QUERY_MALFORMED, "Metric not supported by device in frame query"); + } - const auto arraySize = deviceMetricInfo->GetArraySize(); - if (q.arrayIndex >= arraySize) { - pmlog_error("Frame query array index out of bounds") - .pmwatch(metricView.Introspect().GetSymbol()) - .pmwatch(q.arrayIndex) - .pmwatch(arraySize).diag(); - throw Except(PM_STATUS_QUERY_MALFORMED, "Frame query array index out of bounds"); + const auto arraySize = deviceMetricInfo->GetArraySize(); + if (q.arrayIndex >= arraySize) { + pmlog_error("Frame query array index out of bounds") + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch(q.arrayIndex) + .pmwatch(arraySize).diag(); + throw Except(PM_STATUS_QUERY_MALFORMED, "Frame query array index out of bounds"); + } } const auto alignment = ipc::intro::GetDataTypeAlignment(frameType); blobCursor = util::PadToAlignment(blobCursor, alignment); GatherCommand_ cmd{}; - if (q.deviceId > 0 && q.deviceId <= ipc::kSystemDeviceId) { + cmd.metricId = q.metric; + cmd.gatherType = frameType; + cmd.blobOffset = uint32_t(blobCursor); + cmd.dataSize = (uint32_t)frameTypeSize; + cmd.deviceId = q.deviceId; + cmd.arrayIdx = q.arrayIndex; + + if (isStaticMetric) { + cmd.isStatic = true; + } + else if (q.deviceId > 0 && q.deviceId <= ipc::kSystemDeviceId) { const auto& teleMap = q.deviceId == ipc::kSystemDeviceId ? comms_.GetSystemDataStore().telemetryData : comms_.GetGpuDataStore(q.deviceId).telemetryData; @@ -105,15 +136,10 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M .pmwatch(q.deviceId).diag(); throw Except(PM_STATUS_QUERY_MALFORMED, "Telemetry ring missing for metric in frame query"); } - - cmd.metricId = q.metric; - cmd.gatherType = frameType; - cmd.blobOffset = uint32_t(blobCursor); - cmd.deviceId = q.deviceId; - cmd.arrayIdx = q.arrayIndex; } else if (q.deviceId == ipc::kUniversalDeviceId) { cmd = MapQueryElementToFrameGatherCommand_(q, blobCursor, metricView); + cmd.dataSize = (uint32_t)frameTypeSize; } else { pmlog_error("Invalid device id in frame query") @@ -133,10 +159,13 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, ipc::M PM_FRAME_QUERY::~PM_FRAME_QUERY() = default; -void PM_FRAME_QUERY::GatherToBlob(uint8_t* pBlobBytes, const util::metrics::FrameMetrics& frameMetrics) const +void PM_FRAME_QUERY::GatherToBlob(uint8_t* pBlobBytes, uint32_t processId, const util::metrics::FrameMetrics& frameMetrics) const { for (auto& cmd : gatherCommands_) { - if (cmd.deviceId == ipc::kUniversalDeviceId) { + if (cmd.isStatic) { + GatherFromStatic_(cmd, pBlobBytes, processId); + } + else if (cmd.deviceId == ipc::kUniversalDeviceId) { GatherFromFrameMetrics_(cmd, pBlobBytes, frameMetrics); } else if (cmd.deviceId == ipc::kSystemDeviceId) { @@ -354,6 +383,20 @@ void PM_FRAME_QUERY::GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* } } +void PM_FRAME_QUERY::GatherFromStatic_(const GatherCommand_& cmd, uint8_t* pBlobBytes, uint32_t processId) const +{ + const PM_QUERY_ELEMENT element{ + .metric = cmd.metricId, + .stat = PM_STAT_NONE, + .deviceId = cmd.deviceId, + .arrayIndex = cmd.arrayIdx, + .dataOffset = (uint64_t)cmd.blobOffset, + .dataSize = (uint64_t)cmd.dataSize, + }; + + middleware_.PollStaticQuery(element, processId, pBlobBytes + cmd.blobOffset); +} + void PM_FRAME_QUERY::GatherFromTelemetry_(const GatherCommand_& cmd, uint8_t* pBlobBytes, int64_t searchQpc, const ipc::TelemetryMap& teleMap) const { diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h index 9640fafd..fb58b397 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h @@ -11,6 +11,11 @@ namespace pmapi::intro class MetricView; } +namespace pmon::mid +{ + class Middleware; +} + namespace pmon::ipc { class MiddlewareComms; @@ -21,10 +26,11 @@ struct PM_FRAME_QUERY { public: // functions - PM_FRAME_QUERY(std::span queryElements, pmon::ipc::MiddlewareComms& comms, - const pmapi::intro::Root& introRoot); + PM_FRAME_QUERY(std::span queryElements, pmon::mid::Middleware& middleware, + pmon::ipc::MiddlewareComms& comms, const pmapi::intro::Root& introRoot); ~PM_FRAME_QUERY(); - void GatherToBlob(uint8_t* pBlobBytes, const pmon::util::metrics::FrameMetrics& frameMetrics) const; + void GatherToBlob(uint8_t* pBlobBytes, uint32_t processId, + const pmon::util::metrics::FrameMetrics& frameMetrics) const; size_t GetBlobSize() const; PM_FRAME_QUERY(const PM_FRAME_QUERY&) = delete; @@ -39,20 +45,24 @@ struct PM_FRAME_QUERY PM_METRIC metricId; PM_DATA_TYPE gatherType = PM_DATA_TYPE_VOID; uint32_t blobOffset; + uint32_t dataSize = 0; // offset into FrameMetrics struct from metric calculator uint32_t frameMetricsOffset = 0; uint32_t deviceId = 0; uint32_t arrayIdx = 0; // indicates whether the source data is gatherType or optional bool isOptional = false; + bool isStatic = false; }; // functions static GatherCommand_ MapQueryElementToFrameGatherCommand_(const PM_QUERY_ELEMENT& q, size_t blobCursor, const pmapi::intro::MetricView& metricView); void GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* pBlobBytes, const pmon::util::metrics::FrameMetrics& frameMetrics) const; + void GatherFromStatic_(const GatherCommand_& cmd, uint8_t* pBlobBytes, uint32_t processId) const; void GatherFromTelemetry_(const GatherCommand_& cmd, uint8_t* pBlobBytes, int64_t searchQpc, const pmon::ipc::TelemetryMap& teleMap) const; // data + pmon::mid::Middleware& middleware_; const pmon::ipc::MiddlewareComms& comms_; std::vector gatherCommands_; size_t blobSize_ = 0; diff --git a/IntelPresentMon/SampleClient/PacedFramePlayback.cpp b/IntelPresentMon/SampleClient/PacedFramePlayback.cpp index 688513ba..636ed699 100644 --- a/IntelPresentMon/SampleClient/PacedFramePlayback.cpp +++ b/IntelPresentMon/SampleClient/PacedFramePlayback.cpp @@ -166,6 +166,7 @@ int PacedFramePlaybackTest(std::unique_ptr pSession) const auto frameLimit = static_cast(*opt.frameLimit); PM_BEGIN_FIXED_FRAME_QUERY(FrameQuery) + pmapi::FixedQueryElement applicationName{ this, PM_METRIC_APPLICATION, PM_STAT_NONE }; pmapi::FixedQueryElement swapChain{ this, PM_METRIC_SWAP_CHAIN_ADDRESS, PM_STAT_NONE }; pmapi::FixedQueryElement presentRuntime{ this, PM_METRIC_PRESENT_RUNTIME, PM_STAT_NONE }; pmapi::FixedQueryElement syncInterval{ this, PM_METRIC_SYNC_INTERVAL, PM_STAT_NONE }; @@ -218,7 +219,11 @@ int PacedFramePlaybackTest(std::unique_ptr pSession) if (frameLimit > 0 && totalRecorded >= frameLimit) { return; } - csv << processName << ","; + std::string appName = processName; + if (query.applicationName.IsAvailable()) { + appName = query.applicationName.As(); + } + csv << appName << ","; csv << *opt.processId << ","; csv << std::hex << std::uppercase << "0x" << query.swapChain.As() << std::dec << std::nouppercase << ","; From 581a3814b4294003d1289caea4187b826f40a6d9 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 21 Jan 2026 22:48:08 +0900 Subject: [PATCH 138/205] adding PM_METRIC_PROCESS_ID --- IntelPresentMon/Core/source/pmon/RawFrameDataMetricList.h | 4 +--- IntelPresentMon/Core/source/pmon/RawFrameDataWriter.cpp | 8 ++++---- IntelPresentMon/Interprocess/source/DataStores.cpp | 2 ++ IntelPresentMon/Interprocess/source/metadata/MetricList.h | 3 ++- IntelPresentMon/PresentMonAPI2/PresentMonAPI.h | 1 + IntelPresentMon/SampleClient/PacedFramePlayback.cpp | 8 +++++++- Tools/generate/EnumMetric/metrics.awk | 1 - 7 files changed, 17 insertions(+), 10 deletions(-) diff --git a/IntelPresentMon/Core/source/pmon/RawFrameDataMetricList.h b/IntelPresentMon/Core/source/pmon/RawFrameDataMetricList.h index 62f1820f..585e884d 100644 --- a/IntelPresentMon/Core/source/pmon/RawFrameDataMetricList.h +++ b/IntelPresentMon/Core/source/pmon/RawFrameDataMetricList.h @@ -17,8 +17,6 @@ namespace p2c::pmon std::optional index; }; - constexpr int PM_METRIC_INTERNAL_PROCESS_ID_ = 4'021'373; - inline std::vector GetDefaultRawFrameDataMetricList(uint32_t activeDeviceId, bool enableTimestamp) { namespace rn = std::ranges; @@ -27,7 +25,7 @@ namespace p2c::pmon std::vector queryElements{ // these first 2 are special cases filled by the writer logic Element{.metricId = PM_METRIC_APPLICATION, .deviceId = 0 }, - Element{.metricId = (PM_METRIC)PM_METRIC_INTERNAL_PROCESS_ID_, .deviceId = 0 }, + Element{.metricId = PM_METRIC_PROCESS_ID, .deviceId = 0 }, Element{.metricId = PM_METRIC_SWAP_CHAIN_ADDRESS, .deviceId = 0 }, Element{.metricId = PM_METRIC_PRESENT_RUNTIME, .deviceId = 0 }, Element{.metricId = PM_METRIC_SYNC_INTERVAL, .deviceId = 0 }, diff --git a/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.cpp b/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.cpp index e00a17a4..1866c5c1 100644 --- a/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.cpp +++ b/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.cpp @@ -34,10 +34,10 @@ namespace p2c::pmon elements.reserve(metricSymbols.size()); for (auto& metricSymbol : metricSymbols) { try { - // special case for pid, which is not an api metric but needs to be available in headless + // special case for pid to support headless output if (metricSymbol == "PM_METRIC_INTERNAL_PROCESS_ID_") { elements.push_back(RawFrameQueryElementDefinition{ - .metricId = (PM_METRIC)PM_METRIC_INTERNAL_PROCESS_ID_, + .metricId = PM_METRIC_PROCESS_ID, }); continue; } @@ -70,7 +70,7 @@ namespace p2c::pmon for (const auto& element : elements) { // special quasi-frame metrics if (element.metricId == PM_METRIC_APPLICATION || - element.metricId == (PM_METRIC)PM_METRIC_INTERNAL_PROCESS_ID_) { + element.metricId == PM_METRIC_PROCESS_ID) { filtered.push_back(element); continue; } @@ -307,7 +307,7 @@ namespace p2c::pmon annotationPtrs_.back()->columnName = "Application"; continue; } - else if (el.metricId == (PM_METRIC)PM_METRIC_INTERNAL_PROCESS_ID_) { + else if (el.metricId == PM_METRIC_PROCESS_ID) { annotationPtrs_.push_back(std::make_unique(pid)); annotationPtrs_.back()->columnName = "ProcessID"; continue; diff --git a/IntelPresentMon/Interprocess/source/DataStores.cpp b/IntelPresentMon/Interprocess/source/DataStores.cpp index 8a7cbed0..b4834f6f 100644 --- a/IntelPresentMon/Interprocess/source/DataStores.cpp +++ b/IntelPresentMon/Interprocess/source/DataStores.cpp @@ -151,6 +151,8 @@ namespace pmon::ipc switch (metric) { case PM_METRIC_APPLICATION: return statics.applicationName.c_str(); + case PM_METRIC_PROCESS_ID: + return bookkeeping.processId; default: throw util::Except(PM_STATUS_QUERY_MALFORMED, "Static metric not handled by frame data store"); diff --git a/IntelPresentMon/Interprocess/source/metadata/MetricList.h b/IntelPresentMon/Interprocess/source/metadata/MetricList.h index e1819b69..e954b9e2 100644 --- a/IntelPresentMon/Interprocess/source/metadata/MetricList.h +++ b/IntelPresentMon/Interprocess/source/metadata/MetricList.h @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2024 Intel Corporation +// Copyright (C) 2017-2024 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #include "../../../PresentMonAPI2/PresentMonAPI.h" @@ -6,6 +6,7 @@ #define METRIC_LIST(X_) \ X_(PM_METRIC_APPLICATION, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_STRING, PM_DATA_TYPE_STRING, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ + X_(PM_METRIC_PROCESS_ID, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_UINT32, PM_DATA_TYPE_UINT32, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ X_(PM_METRIC_SWAP_CHAIN_ADDRESS, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_UINT64, PM_DATA_TYPE_UINT64, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_MID_POINT) \ X_(PM_METRIC_GPU_VENDOR, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_DEVICE_VENDOR, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, PM_STAT_NONE) \ X_(PM_METRIC_GPU_NAME, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_STRING, PM_DATA_TYPE_STRING, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, PM_STAT_NONE) \ diff --git a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h index 0a1ffecb..5617ba26 100644 --- a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h +++ b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h @@ -137,6 +137,7 @@ extern "C" { PM_METRIC_BETWEEN_APP_START, PM_METRIC_PRESENTED_FRAME_TIME, PM_METRIC_FLIP_DELAY, + PM_METRIC_PROCESS_ID, }; enum PM_METRIC_TYPE diff --git a/IntelPresentMon/SampleClient/PacedFramePlayback.cpp b/IntelPresentMon/SampleClient/PacedFramePlayback.cpp index 636ed699..cfdeee55 100644 --- a/IntelPresentMon/SampleClient/PacedFramePlayback.cpp +++ b/IntelPresentMon/SampleClient/PacedFramePlayback.cpp @@ -167,6 +167,7 @@ int PacedFramePlaybackTest(std::unique_ptr pSession) PM_BEGIN_FIXED_FRAME_QUERY(FrameQuery) pmapi::FixedQueryElement applicationName{ this, PM_METRIC_APPLICATION, PM_STAT_NONE }; + pmapi::FixedQueryElement processId{ this, PM_METRIC_PROCESS_ID, PM_STAT_NONE }; pmapi::FixedQueryElement swapChain{ this, PM_METRIC_SWAP_CHAIN_ADDRESS, PM_STAT_NONE }; pmapi::FixedQueryElement presentRuntime{ this, PM_METRIC_PRESENT_RUNTIME, PM_STAT_NONE }; pmapi::FixedQueryElement syncInterval{ this, PM_METRIC_SYNC_INTERVAL, PM_STAT_NONE }; @@ -224,7 +225,12 @@ int PacedFramePlaybackTest(std::unique_ptr pSession) appName = query.applicationName.As(); } csv << appName << ","; - csv << *opt.processId << ","; + if (query.processId.IsAvailable()) { + csv << query.processId.As() << ","; + } + else { + csv << *opt.processId << ","; + } csv << std::hex << std::uppercase << "0x" << query.swapChain.As() << std::dec << std::nouppercase << ","; csv << TranslateGraphicsRuntime(query.presentRuntime.As()) << ","; diff --git a/Tools/generate/EnumMetric/metrics.awk b/Tools/generate/EnumMetric/metrics.awk index 77d77e89..b3750f56 100644 --- a/Tools/generate/EnumMetric/metrics.awk +++ b/Tools/generate/EnumMetric/metrics.awk @@ -29,7 +29,6 @@ BEGIN{ gsub(/ /, "", shortname) gsub(/-/, "", shortname) - if (shortname == "ProcessID") next if (shortname == "VideoBusy") next printf " \\\n\t\tX_(METRIC, %s, \"%s\", \"%s\", \"%s\")", substr(a[1], 11), name, shortname, desc From a0aff0a69ce6bc418b889221d0ee6e3bbe83a886 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 21 Jan 2026 23:22:32 +0900 Subject: [PATCH 139/205] removing manual fill from raw frame data writer --- .../Core/source/kernel/Overlay.cpp | 6 +- .../Core/source/pmon/PresentMon.cpp | 10 +-- IntelPresentMon/Core/source/pmon/PresentMon.h | 6 +- .../Core/source/pmon/RawFrameDataWriter.cpp | 70 ++----------------- .../Core/source/pmon/RawFrameDataWriter.h | 5 +- 5 files changed, 18 insertions(+), 79 deletions(-) diff --git a/IntelPresentMon/Core/source/kernel/Overlay.cpp b/IntelPresentMon/Core/source/kernel/Overlay.cpp index fc186f63..ae0e3a01 100644 --- a/IntelPresentMon/Core/source/kernel/Overlay.cpp +++ b/IntelPresentMon/Core/source/kernel/Overlay.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #include "Overlay.h" #include @@ -450,7 +450,7 @@ namespace p2c::kern return std::nullopt; } }(); - pWriter = { pm->MakeRawFrameDataWriter(std::move(fullPath), std::move(fullStatsPath), proc.pid, proc.name) }; + pWriter = { pm->MakeRawFrameDataWriter(std::move(fullPath), std::move(fullStatsPath), proc.pid) }; } else if (!active && pWriter) { pWriter.reset(); @@ -625,4 +625,4 @@ namespace p2c::kern { return remainings_[Trace_] == 0; } -} \ No newline at end of file +} diff --git a/IntelPresentMon/Core/source/pmon/PresentMon.cpp b/IntelPresentMon/Core/source/pmon/PresentMon.cpp index 2dc9c62d..02883aa7 100644 --- a/IntelPresentMon/Core/source/pmon/PresentMon.cpp +++ b/IntelPresentMon/Core/source/pmon/PresentMon.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #include "PresentMon.h" #include @@ -150,14 +150,14 @@ namespace p2c::pmon return processTracker; } std::shared_ptr PresentMon::MakeRawFrameDataWriter(std::wstring path, - std::optional statsPath, uint32_t pid, std::wstring procName) + std::optional statsPath, uint32_t pid) { // flush any buffered present events before starting capture pFlusher->Flush(processTracker); // make the frame data writer - return std::make_shared(std::move(path), processTracker, std::move(procName), - selectedAdapter.value_or(1), *pSession, std::move(statsPath), *pIntrospectionRoot); + return std::make_shared(std::move(path), processTracker, selectedAdapter.value_or(1), + *pSession, std::move(statsPath), *pIntrospectionRoot); } std::optional PresentMon::GetSelectedAdapter() const { @@ -181,4 +181,4 @@ namespace p2c::pmon { return etwFlushPeriodMs; } -} \ No newline at end of file +} diff --git a/IntelPresentMon/Core/source/pmon/PresentMon.h b/IntelPresentMon/Core/source/pmon/PresentMon.h index c8a0ea21..aee20427 100644 --- a/IntelPresentMon/Core/source/pmon/PresentMon.h +++ b/IntelPresentMon/Core/source/pmon/PresentMon.h @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #include @@ -41,7 +41,7 @@ namespace p2c::pmon std::optional GetPid() const; const pmapi::ProcessTracker& GetTracker() const; std::shared_ptr MakeRawFrameDataWriter(std::wstring path, std::optional statsPath, - uint32_t pid, std::wstring procName); + uint32_t pid); std::optional GetSelectedAdapter() const; const pmapi::intro::Root& GetIntrospectionRoot() const; pmapi::Session& GetSession(); @@ -56,4 +56,4 @@ namespace p2c::pmon pmapi::ProcessTracker processTracker; std::optional selectedAdapter; }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.cpp b/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.cpp index 1866c5c1..a18be8cf 100644 --- a/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.cpp +++ b/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.cpp @@ -1,7 +1,6 @@ // Copyright (C) 2017-2024 Intel Corporation // SPDX-License-Identifier: MIT #include "RawFrameDataWriter.h" -#include #include #include #include @@ -12,7 +11,6 @@ namespace p2c::pmon { - using ::pmon::util::str::ToNarrow; namespace rn = std::ranges; namespace vi = rn::views; @@ -34,13 +32,6 @@ namespace p2c::pmon elements.reserve(metricSymbols.size()); for (auto& metricSymbol : metricSymbols) { try { - // special case for pid to support headless output - if (metricSymbol == "PM_METRIC_INTERNAL_PROCESS_ID_") { - elements.push_back(RawFrameQueryElementDefinition{ - .metricId = PM_METRIC_PROCESS_ID, - }); - continue; - } const auto metricId = metricLookup.at(metricSymbol); const auto& metric = introRoot.FindMetric(metricId); // make sure metric is valid for a frame query @@ -68,12 +59,6 @@ namespace p2c::pmon std::vector filtered; filtered.reserve(elements.size()); for (const auto& element : elements) { - // special quasi-frame metrics - if (element.metricId == PM_METRIC_APPLICATION || - element.metricId == PM_METRIC_PROCESS_ID) { - filtered.push_back(element); - continue; - } const auto& metric = introRoot.FindMetric(element.metricId); const auto checkDeviceId = element.deviceId != 0 ? element.deviceId : activeDeviceId; bool available = false; @@ -91,13 +76,6 @@ namespace p2c::pmon .pmwatch(checkDeviceId); continue; } - // TODO: remove this temp check for static metrics - // since we are not filling yet - if (metric.GetType() == PM_METRIC_TYPE_STATIC) { - pmlog_warn("Static metrics in frame query currently unsupported") - .pmwatch(metric.Introspect().GetSymbol()); - continue; - } filtered.push_back(element); } elements = std::move(filtered); @@ -173,30 +151,6 @@ namespace p2c::pmon } } }; - struct ProcessNameAnnotation_ : public Annotation_ - { - ProcessNameAnnotation_(std::string name) - : - name_{ std::move(name) } - {} - void Write(std::ostream& out, const uint8_t*) const override - { - out << name_; - } - std::string name_; - }; - struct PidAnnotation_ : public Annotation_ - { - PidAnnotation_(uint32_t pid) - : - pid_{ pid } - {} - void Write(std::ostream& out, const uint8_t*) const override - { - out << pid_; - } - uint32_t pid_; - }; template<> struct TypedAnnotation_ : public Annotation_ { @@ -298,20 +252,9 @@ namespace p2c::pmon { public: QueryElementContainer_(std::span elements, - pmapi::Session& session, const pmapi::intro::Root& introRoot, std::string procName, uint32_t pid) + pmapi::Session& session, const pmapi::intro::Root& introRoot) { for (auto& el : elements) { - // check for specific fill-in metrics - if (el.metricId == PM_METRIC_APPLICATION) { - annotationPtrs_.push_back(std::make_unique(procName)); - annotationPtrs_.back()->columnName = "Application"; - continue; - } - else if (el.metricId == PM_METRIC_PROCESS_ID) { - annotationPtrs_.push_back(std::make_unique(pid)); - annotationPtrs_.back()->columnName = "ProcessID"; - continue; - } const auto metric = introRoot.FindMetric(el.metricId); annotationPtrs_.push_back(Annotation_::MakeTyped(el.metricId, el.deviceId, metric)); // append metric array index to column name if array metric @@ -392,9 +335,8 @@ namespace p2c::pmon { return reinterpret_cast(pBlob[queryElements_[animationErrorElementIdx_].dataOffset]); } - void WriteFrame(uint32_t pid, const std::string& procName, std::ostream& out, const uint8_t* pBlob) + void WriteFrame(std::ostream& out, const uint8_t* pBlob) { - // TODO: use metrics from procname and pid piped through middleware (after IPC upgrade) // loop over each element (column/field) in a frame of data for (auto&& [i, pAnno] : annotationPtrs_ | vi::enumerate) { if (i) { @@ -429,11 +371,10 @@ namespace p2c::pmon int animationErrorElementIdx_ = -1; }; - RawFrameDataWriter::RawFrameDataWriter(std::wstring path, const pmapi::ProcessTracker& procTrackerIn, std::wstring processName, uint32_t activeDeviceId, + RawFrameDataWriter::RawFrameDataWriter(std::wstring path, const pmapi::ProcessTracker& procTrackerIn, uint32_t activeDeviceId, pmapi::Session& session, std::optional frameStatsPathIn, const pmapi::intro::Root& introRoot) : procTracker{ procTrackerIn }, - procName{ ToNarrow(processName) }, frameStatsPath{ std::move(frameStatsPathIn) }, pStatsTracker{ frameStatsPath ? std::make_unique() : nullptr }, pAnimationErrorTracker{ frameStatsPath ? std::make_unique() : nullptr }, @@ -451,8 +392,7 @@ namespace p2c::pmon if (elements.empty()) { pmlog_error("No valid metrics specified for frame event capture").raise<::pmon::util::Exception>(); } - pQueryElementContainer = std::make_unique(std::move(elements), session, - introRoot, ::pmon::util::str::ToNarrow(processName), procTracker.GetPid()); + pQueryElementContainer = std::make_unique(std::move(elements), session, introRoot); blobs = pQueryElementContainer->MakeBlobs(numberOfBlobs); // write header pQueryElementContainer->WriteHeader(file); @@ -483,7 +423,7 @@ namespace p2c::pmon pAnimationErrorTracker->Push(std::abs(animationError)); } } - pQueryElementContainer->WriteFrame(procTracker.GetPid(), procName, file, pBlob); + pQueryElementContainer->WriteFrame(file, pBlob); } } while (blobs.AllBlobsPopulated()); // if container filled, means more might be left file << std::flush; diff --git a/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.h b/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.h index ff89039f..5842a3ef 100644 --- a/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.h +++ b/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.h @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #include @@ -18,7 +18,7 @@ namespace p2c::pmon class RawFrameDataWriter { public: - RawFrameDataWriter(std::wstring path, const pmapi::ProcessTracker& procTracker, std::wstring processName, uint32_t activeDeviceId, + RawFrameDataWriter(std::wstring path, const pmapi::ProcessTracker& procTracker, uint32_t activeDeviceId, pmapi::Session& session, std::optional frameStatsPath, const pmapi::intro::Root& introRoot); RawFrameDataWriter(const RawFrameDataWriter&) = delete; RawFrameDataWriter& operator=(const RawFrameDataWriter&) = delete; @@ -31,7 +31,6 @@ namespace p2c::pmon // data static constexpr uint32_t numberOfBlobs = 150u; const pmapi::ProcessTracker& procTracker; - std::string procName; std::unique_ptr pQueryElementContainer; std::optional frameStatsPath; std::unique_ptr pStatsTracker; From d227a34dc1f306d5fcfe29ad04b6fc054fc3daac Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 22 Jan 2026 09:47:25 +0900 Subject: [PATCH 140/205] noexcept for move ops --- IntelPresentMon/Interprocess/source/ShmRing.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/ShmRing.h b/IntelPresentMon/Interprocess/source/ShmRing.h index 57e2e5ce..c26c0e61 100644 --- a/IntelPresentMon/Interprocess/source/ShmRing.h +++ b/IntelPresentMon/Interprocess/source/ShmRing.h @@ -30,14 +30,14 @@ namespace pmon::ipc ShmRing& operator=(const ShmRing&) = delete; // we need to enable move for use inside vectors // not enabled by default because of the atomic member - ShmRing(ShmRing&& other) + ShmRing(ShmRing&& other) noexcept : backpressured_{ other.backpressured_ }, data_{ std::move(other.data_) }, nextWriteSerial_{ other.nextWriteSerial_.load() } { } - ShmRing& operator=(ShmRing&& other) + ShmRing& operator=(ShmRing&& other) noexcept { if (this != &other) { data_ = std::move(other.data_); From 4112025a098dda55d88b3888b41b9d36701e5143 Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 22 Jan 2026 11:16:39 +0900 Subject: [PATCH 141/205] generalize history ring for frame data usage --- .../Interprocess/Interprocess.vcxproj | 3 +- .../Interprocess/Interprocess.vcxproj.filters | 5 +- .../Interprocess/source/DataStores.h | 5 +- .../Interprocess/source/HistoryRing.h | 101 ++++++++++++------ .../Interprocess/source/TelemetryMap.h | 6 +- .../InterimBroadcasterTests.cpp | 14 +-- .../PresentMonAPI2Tests/IpcComponentTests.cpp | 6 +- 7 files changed, 93 insertions(+), 47 deletions(-) diff --git a/IntelPresentMon/Interprocess/Interprocess.vcxproj b/IntelPresentMon/Interprocess/Interprocess.vcxproj index 4d2ef325..eab4d6ef 100644 --- a/IntelPresentMon/Interprocess/Interprocess.vcxproj +++ b/IntelPresentMon/Interprocess/Interprocess.vcxproj @@ -66,6 +66,7 @@ + @@ -173,4 +174,4 @@ - + \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/Interprocess.vcxproj.filters b/IntelPresentMon/Interprocess/Interprocess.vcxproj.filters index 667fb90b..34a52430 100644 --- a/IntelPresentMon/Interprocess/Interprocess.vcxproj.filters +++ b/IntelPresentMon/Interprocess/Interprocess.vcxproj.filters @@ -182,5 +182,8 @@ Header Files + + Header Files + - + \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/source/DataStores.h b/IntelPresentMon/Interprocess/source/DataStores.h index 4ddca06e..11d15cc9 100644 --- a/IntelPresentMon/Interprocess/source/DataStores.h +++ b/IntelPresentMon/Interprocess/source/DataStores.h @@ -1,6 +1,6 @@ #pragma once #include "SharedMemoryTypes.h" -#include "ShmRing.h" +#include "HistoryRing.h" #include "TelemetryMap.h" #include "../../CommonUtilities/Exception.h" #include "../../CommonUtilities/log/Log.h" @@ -16,6 +16,7 @@ namespace pmon::ipc { using FrameData = util::metrics::FrameData; + using FrameHistoryRing = HistoryRing; class MetricCapabilities; namespace intro @@ -70,7 +71,7 @@ namespace pmon::ipc bool bookkeepingInitComplete = false; bool isPlayback = false; } bookkeeping{}; - ShmRing frameData; + FrameHistoryRing frameData; StaticMetricValue FindStaticMetric(PM_METRIC metric) const; diff --git a/IntelPresentMon/Interprocess/source/HistoryRing.h b/IntelPresentMon/Interprocess/source/HistoryRing.h index b8179696..e31f8d41 100644 --- a/IntelPresentMon/Interprocess/source/HistoryRing.h +++ b/IntelPresentMon/Interprocess/source/HistoryRing.h @@ -2,45 +2,49 @@ #include "ShmRing.h" #include "../../CommonUtilities/log/Verbose.h" #include +#include #include +#include +#include namespace pmon::ipc { // wrapper around ShmRing that adds the ability to search/address by timestamp // meant for use with telemetry data - template + template class HistoryRing { public: - // types - struct Sample - { - T value; - uint64_t timestamp; - }; // functions - HistoryRing(size_t capacity, ShmVector::allocator_type alloc) + HistoryRing(size_t capacity, ShmVector::allocator_type alloc, bool backpressured = false) : - samples_{ capacity, alloc } + samples_{ capacity, alloc, backpressured } { if (capacity < ReadBufferSize * 2) { throw std::logic_error{ "The capacity of a ShmRing must be at least double its ReadBufferSize" }; } } - void Push(const T& value, uint64_t timestamp) + bool Push(const T& sample, std::optional timeoutMs = {}) { - samples_.Push({ value, timestamp }); + return samples_.Push(sample, timeoutMs); } - const Sample& Newest() const + template().value)>, + typename = std::enable_if_t>> + bool Push(const ValueT& value, uint64_t timestamp, std::optional timeoutMs = {}) + { + return samples_.Push(U{ value, timestamp }, timeoutMs); + } + const T& Newest() const { auto&&[first, last] = samples_.GetSerialRange(); return At(last - 1); } - const Sample& At(size_t serial) const + const T& At(size_t serial) const { return samples_.At(serial); } - const Sample& Nearest(uint64_t timestamp) const + const T& Nearest(uint64_t timestamp) const { return samples_.At(NearestSerial(timestamp)); } @@ -57,17 +61,21 @@ namespace pmon::ipc const auto range = GetSerialRange(); return range.second - range.first; } + void MarkNextRead(size_t serial) const + { + samples_.MarkNextRead(serial); + } // First serial with timestamp >= given timestamp. // If all samples have timestamp < given timestamp, returns last (one past end). size_t LowerBoundSerial(uint64_t timestamp) const { - return BoundSerial_(timestamp); + return BoundSerial_(timestamp); } // First serial with timestamp > given timestamp. // If all samples have timestamp <= given timestamp, returns last (one past end). size_t UpperBoundSerial(uint64_t timestamp) const { - return BoundSerial_(timestamp); + return BoundSerial_(timestamp); } // Find the serial whose timestamp is closest to the given timestamp. // If the timestamp is outside the stored range, clamps to first/last. @@ -97,12 +105,23 @@ namespace pmon::ipc if (!recentSamples.empty()) { recentSamples += "\n"; } - recentSamples += std::format("ts={} value={}", sample.timestamp, sample.value); + if constexpr (HasValueMember_::value) { + using ValueMemberT = std::decay_t().value)>; + if constexpr (IsFormattable_::value) { + recentSamples += std::format("ts={} value={}", TimestampOf_(sample), sample.value); + } + else { + recentSamples += std::format("ts={}", TimestampOf_(sample)); + } + } + else { + recentSamples += std::format("ts={}", TimestampOf_(sample)); + } } pmlog_verb(util::log::V::ipc_ring)("Target timestamp past end of history ring") .pmwatch(timestamp) .pmwatch(range.second) - .pmwatch(int64_t(At(serial - 1).timestamp) - int64_t(timestamp)) + .pmwatch(int64_t(TimestampOf_(At(serial - 1))) - int64_t(timestamp)) .watch("recent_samples", recentSamples); } @@ -112,8 +131,8 @@ namespace pmon::ipc // Check whether the previous sample is actually closer. // but only if there is a sample available before this one if (serial > range.first) { - const auto nextTimestamp = At(serial).timestamp; - const auto prevTimestamp = At(serial - 1).timestamp; + const auto nextTimestamp = TimestampOf_(At(serial)); + const auto prevTimestamp = TimestampOf_(At(serial - 1)); const uint64_t dPrev = timestamp - prevTimestamp; const uint64_t dNext = nextTimestamp - timestamp; if (dPrev <= dNext) { @@ -124,7 +143,7 @@ namespace pmon::ipc pmlog_verb(util::log::V::ipc_ring)("Found nearest sample") .pmwatch(timestamp) .pmwatch(serial) - .pmwatch(int64_t(At(serial).timestamp) - int64_t(timestamp)); + .pmwatch(int64_t(TimestampOf_(At(serial))) - int64_t(timestamp)); return serial; } // Calls func(sample) for each sample whose timestamp is in [start, end]. @@ -141,8 +160,8 @@ namespace pmon::ipc size_t count = 0; // Walk forward until we leave the [start, end] window or hit last for (; serial < range.second; ++serial) { - const Sample& s = At(serial); - if (s.timestamp > end) { + const T& s = At(serial); + if (TimestampOf_(s) > end) { break; } // s.timestamp is guaranteed >= start by LowerBoundSerial @@ -155,14 +174,26 @@ namespace pmon::ipc private: // types - enum class BoundKind + enum class BoundKind_ { Lower, Upper }; + template + struct HasValueMember_ : std::false_type {}; + template + struct HasValueMember_().value)>> : std::true_type {}; + template + struct IsFormattable_ : std::false_type {}; + template + struct IsFormattable_()))>> : std::true_type {}; // functions + static uint64_t TimestampOf_(const T& sample) + { + return sample.*TimestampMember; + } // Shared binary search implementation for LowerBoundSerial / UpperBoundSerial. - template + template size_t BoundSerial_(uint64_t timestamp) const { auto range = samples_.GetSerialRange(); @@ -175,11 +206,11 @@ namespace pmon::ipc // Standard lower/upper bound style search over [first, last) while (lo < hi) { size_t mid = lo + (hi - lo) / 2; - const Sample& s = At(mid); + const T& s = At(mid); - if constexpr (Kind == BoundKind::Lower) { + if constexpr (Kind == BoundKind_::Lower) { // First with s.timestamp >= timestamp - if (s.timestamp < timestamp) { + if (TimestampOf_(s) < timestamp) { lo = mid + 1; } else { @@ -188,7 +219,7 @@ namespace pmon::ipc } else { // First with s.timestamp > timestamp - if (s.timestamp <= timestamp) { + if (TimestampOf_(s) <= timestamp) { lo = mid + 1; } else { @@ -200,7 +231,17 @@ namespace pmon::ipc return lo; // in [first, last] } // data - ShmRing samples_; + ShmRing samples_; }; + template + struct TelemetrySample + { + using value_type = TValue; + TValue value; + uint64_t timestamp; + }; + // Alias for telemetry convenience; matches the prior HistoryRing behavior. + template + using SampleHistoryRing = HistoryRing, &TelemetrySample::timestamp, ReadBufferSize>; } diff --git a/IntelPresentMon/Interprocess/source/TelemetryMap.h b/IntelPresentMon/Interprocess/source/TelemetryMap.h index 2eda76b5..8017504a 100644 --- a/IntelPresentMon/Interprocess/source/TelemetryMap.h +++ b/IntelPresentMon/Interprocess/source/TelemetryMap.h @@ -11,7 +11,7 @@ namespace pmon::ipc { public: template - using HistoryRingVect = ShmVector>; + using HistoryRingVect = ShmVector>; using MapValueType = std::variant< HistoryRingVect, HistoryRingVect, HistoryRingVect, HistoryRingVect>; @@ -51,9 +51,9 @@ namespace pmon::ipc "Unsupported ring type for TelemetryMap" ); - using RingAlloc = typename MapType::allocator_type::template rebind>::other; + using RingAlloc = typename MapType::allocator_type::template rebind>::other; - // Construct an allocator for HistoryRing from the map's allocator + // Construct an allocator for SampleHistoryRing from the map's allocator RingAlloc ringAlloc(ringMap_.get_allocator()); // Insert (or get existing) entry for this id, constructing the correct variant alternative diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 05cfaa2f..95f64761 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -29,7 +29,7 @@ using namespace pmon; namespace InterimBroadcasterTests { - static std::string DumpRing_(const ipc::HistoryRing& ring, size_t maxSamples = 8) + static std::string DumpRing_(const ipc::SampleHistoryRing& ring, size_t maxSamples = 8) { std::ostringstream oss; const auto [first, last] = ring.GetSerialRange(); @@ -216,8 +216,8 @@ namespace InterimBroadcasterTests std::this_thread::sleep_for(150ms); // check that we have data for frequency and utilization - std::vector::Sample> utilizSamples; - std::vector::Sample> freqSamples; + std::vector> utilizSamples; + std::vector> freqSamples; for (int i = 0; i < 10; i++) { std::this_thread::sleep_for(250ms); { @@ -432,8 +432,8 @@ namespace InterimBroadcasterTests // allow a short warmup std::this_thread::sleep_for(150ms); - std::vector::Sample> tempSamples; - std::vector::Sample> powerSamples; + std::vector> tempSamples; + std::vector> powerSamples; for (int i = 0; i < 10; i++) { std::this_thread::sleep_for(250ms); @@ -610,7 +610,7 @@ namespace InterimBroadcasterTests auto& sysFreqRing = sys.telemetryData.FindRing(PM_METRIC_CPU_FREQUENCY).at(0); auto& gpuRing = gpu.telemetryData.FindRing(PM_METRIC_GPU_TEMPERATURE).at(0); std::this_thread::sleep_for(1500ms); - const auto logRing = [](const char* label, const ipc::HistoryRing& ring) { + const auto logRing = [](const char* label, const ipc::SampleHistoryRing& ring) { const auto range = ring.GetSerialRange(); Logger::WriteMessage(std::format( "{}: serial [{}, {}) count={}\n", @@ -675,7 +675,7 @@ namespace InterimBroadcasterTests auto& gpuPowerRing = gpu.telemetryData.FindRing(PM_METRIC_GPU_POWER).at(0); auto& sysRing = sys.telemetryData.FindRing(PM_METRIC_CPU_UTILIZATION).at(0); std::this_thread::sleep_for(1500ms); - const auto logRing = [](const char* label, const ipc::HistoryRing& ring) { + const auto logRing = [](const char* label, const ipc::SampleHistoryRing& ring) { const auto range = ring.GetSerialRange(); Logger::WriteMessage(std::format( "{}: serial [{}, {}) count={}\n", diff --git a/IntelPresentMon/PresentMonAPI2Tests/IpcComponentTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/IpcComponentTests.cpp index f469d40e..4c7db296 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/IpcComponentTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/IpcComponentTests.cpp @@ -55,7 +55,7 @@ namespace IpcComponentTests } }; - static std::string DumpRing_(const ipc::HistoryRing& ring, size_t maxSamples = 8) + static std::string DumpRing_(const ipc::SampleHistoryRing& ring, size_t maxSamples = 8) { std::ostringstream oss; const auto [first, last] = ring.GetSerialRange(); @@ -91,7 +91,7 @@ namespace IpcComponentTests return oss.str(); } - static void LogRing_(const char* label, const ipc::HistoryRing& ring) + static void LogRing_(const char* label, const ipc::SampleHistoryRing& ring) { std::ostringstream oss; oss << label << "\n" << DumpRing_(ring); @@ -116,7 +116,7 @@ namespace IpcComponentTests return 50.0 + 2.0 * static_cast(i); } - static void AssertScalarSampleMatches_(const ipc::HistoryRing& ring, size_t serial) + static void AssertScalarSampleMatches_(const ipc::SampleHistoryRing& ring, size_t serial) { const auto& sample = ring.At(serial); const uint64_t expectedTs = kBaseTs + static_cast(serial); From a976eac1ae39f2c3e2e99e6f9af782b93c43689b Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 22 Jan 2026 17:25:24 +0900 Subject: [PATCH 142/205] 1st draft of dynamic stat --- .../Interprocess/source/metadata/MetricList.h | 2 +- .../PresentMonMiddleware/DynamicMetric.cpp | 12 + .../PresentMonMiddleware/DynamicMetric.h | 19 ++ .../PresentMonMiddleware/DynamicQuery.cpp | 5 + .../PresentMonMiddleware/DynamicQuery.h | 37 ++- .../PresentMonMiddleware/DynamicQueryWindow.h | 11 + .../PresentMonMiddleware/DynamicStat.cpp | 295 ++++++++++++++++++ .../PresentMonMiddleware/DynamicStat.h | 34 ++ .../HistoryRingTraverser.cpp | 12 + .../HistoryRingTraverser.h | 18 ++ .../PresentMonMiddleware.vcxproj | 10 +- .../PresentMonMiddleware.vcxproj.filters | 26 +- 12 files changed, 463 insertions(+), 18 deletions(-) create mode 100644 IntelPresentMon/PresentMonMiddleware/DynamicMetric.cpp create mode 100644 IntelPresentMon/PresentMonMiddleware/DynamicMetric.h create mode 100644 IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp create mode 100644 IntelPresentMon/PresentMonMiddleware/DynamicQueryWindow.h create mode 100644 IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp create mode 100644 IntelPresentMon/PresentMonMiddleware/DynamicStat.h create mode 100644 IntelPresentMon/PresentMonMiddleware/HistoryRingTraverser.cpp create mode 100644 IntelPresentMon/PresentMonMiddleware/HistoryRingTraverser.h diff --git a/IntelPresentMon/Interprocess/source/metadata/MetricList.h b/IntelPresentMon/Interprocess/source/metadata/MetricList.h index e954b9e2..c6ef4915 100644 --- a/IntelPresentMon/Interprocess/source/metadata/MetricList.h +++ b/IntelPresentMon/Interprocess/source/metadata/MetricList.h @@ -67,7 +67,7 @@ X_(PM_METRIC_GPU_MEM_VOLTAGE_LIMITED, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_BOOLEAN, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_BOOL, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ X_(PM_METRIC_GPU_MEM_UTILIZATION_LIMITED, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_BOOLEAN, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_BOOL, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ X_(PM_METRIC_CPU_UTILIZATION, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_PERCENT, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, FULL_STATS) \ - X_(PM_METRIC_CPU_POWER_LIMIT, PM_METRIC_TYPE_STATIC, PM_UNIT_WATTS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, PM_STAT_MID_POINT) \ + X_(PM_METRIC_CPU_POWER_LIMIT, PM_METRIC_TYPE_STATIC, PM_UNIT_WATTS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, PM_STAT_MID_POINT) /* maybe stat NONE? */ \ X_(PM_METRIC_CPU_POWER, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_WATTS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, FULL_STATS) \ X_(PM_METRIC_CPU_TEMPERATURE, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_CELSIUS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, FULL_STATS) \ X_(PM_METRIC_CPU_FREQUENCY, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MEGAHERTZ, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, FULL_STATS) \ diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.cpp new file mode 100644 index 00000000..3d3640fd --- /dev/null +++ b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.cpp @@ -0,0 +1,12 @@ +#include "DynamicMetric.h" + +namespace pmon::mid +{ + DynamicMetric::DynamicMetric() = default; + DynamicMetric::~DynamicMetric() = default; + + uint32_t DynamicMetric::GetId() const + { + return id_; + } +} diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h new file mode 100644 index 00000000..b47072fa --- /dev/null +++ b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include +#include "DynamicStat.h" + +namespace pmon::mid +{ + // container for multiple stats connected to a single metric + // implements shared stat calculation facilities (i.e. sorted sample buffer for %) + template + class DynamicMetric + { + public: + DynamicMetric(); + ~DynamicMetric(); + private: + std::vector samples_; + }; +} diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp new file mode 100644 index 00000000..3969c3bf --- /dev/null +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp @@ -0,0 +1,5 @@ +#include "DynamicQuery.h" + +PM_DYNAMIC_QUERY::PM_DYNAMIC_QUERY() +{ +} diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h index 542e69bd..76a9af9b 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include #include #include @@ -6,21 +6,28 @@ #include "../ControlLib/CpuTelemetryInfo.h" #include "../ControlLib/PresentMonPowerTelemetry.h" +namespace pmapi::intro +{ + class Root; + class MetricView; +} + +namespace pmon::mid +{ + class Middleware; +} + +namespace pmon::ipc +{ + class MiddlewareComms; + class TelemetryMap; +} + struct PM_DYNAMIC_QUERY { - std::vector elements; - size_t GetBlobSize() const - { - return elements.back().dataOffset + elements.back().dataSize; - } - // Data used to track what should be accumulated - bool accumFpsData = false; - std::bitset(GpuTelemetryCapBits::gpu_telemetry_count)> accumGpuBits; - std::bitset(CpuTelemetryCapBits::cpu_telemetry_count)> accumCpuBits; - // Data used to calculate the requested metrics - double windowSizeMs = 0; - double metricOffsetMs = 0.; - size_t queryCacheSize = 0; - std::optional cachedGpuInfoIndex; +public: + PM_DYNAMIC_QUERY(); + +private: }; diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQueryWindow.h b/IntelPresentMon/PresentMonMiddleware/DynamicQueryWindow.h new file mode 100644 index 00000000..3a2f033c --- /dev/null +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQueryWindow.h @@ -0,0 +1,11 @@ +#pragma once +#include + +namespace pmon::mid +{ + struct DynamicQueryWindow + { + uint64_t oldest = 0; + uint64_t newest = 0; + }; +} diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp new file mode 100644 index 00000000..d1e0d6d0 --- /dev/null +++ b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp @@ -0,0 +1,295 @@ +#include "DynamicStat.h" +#include "../CommonUtilities/Exception.h" +#include "../Interprocess/source/PmStatusError.h" +#include + +namespace ipc = pmon::ipc; +namespace util = pmon::util; + +namespace pmon::mid +{ + namespace + { + // only supports double + class DynamicStatAverage_ : public DynamicStat + { + public: + DynamicStatAverage_(PM_DATA_TYPE dataType, size_t offsetBytes, bool skipZero) + : DynamicStat{ dataType, offsetBytes }, + skipZero_{ skipZero } + { + } + bool NeedsUpdate() const override { return true; } + bool NeedsPointSample() const override { return false; } + bool NeedsSortedWindow() const override { return false; } + void AddSample(double val) override + { + if (skipZero_ && val == 0.0) { + return; + } + sum_ += val; + ++count_; + } + void Populate(uint8_t* pBase) const override + { + double avg = 0.0; + if (count_ > 0) { + avg = sum_ / (double)count_; + } + auto* pTarget = pBase + offsetBytes_; + *reinterpret_cast(pTarget) = avg; + } + private: + bool skipZero_ = false; + double sum_ = 0.0; + size_t count_ = 0; + }; + + // only supports double + class DynamicStatPercentile_ : public DynamicStat + { + public: + DynamicStatPercentile_(PM_DATA_TYPE dataType, size_t offsetBytes, double percentile) + : DynamicStat{ dataType, offsetBytes }, + percentile_{ percentile } + { + } + bool NeedsUpdate() const override { return true; } + bool NeedsPointSample() const override { return false; } + bool NeedsSortedWindow() const override { return true; } + void InputSortedSamples(std::span sortedSamples) override + { + if (sortedSamples.empty()) { + value_ = 0.0; + hasValue_ = false; + return; + } + + const double clamped = std::clamp(percentile_, 0.0, 1.0); + const size_t last = sortedSamples.size() - 1; + const double position = clamped * (double)last; + const size_t index = (size_t)(position + 0.5); + value_ = sortedSamples[index]; + hasValue_ = true; + } + void Populate(uint8_t* pBase) const override + { + auto* pTarget = pBase + offsetBytes_; + *reinterpret_cast(pTarget) = hasValue_ ? value_ : 0.0; + } + private: + double percentile_ = 0.0; + double value_ = 0.0; + bool hasValue_ = false; + }; + + // only supports double + class DynamicStatMinMax_ : public DynamicStat + { + public: + DynamicStatMinMax_(PM_DATA_TYPE dataType, size_t offsetBytes, bool isMax) + : DynamicStat{ dataType, offsetBytes }, + isMax_{ isMax } + { + } + bool NeedsUpdate() const override { return true; } + bool NeedsPointSample() const override { return false; } + bool NeedsSortedWindow() const override { return false; } + void AddSample(double val) override + { + if (!hasValue_) { + value_ = val; + hasValue_ = true; + return; + } + if (isMax_) { + if (val > value_) { + value_ = val; + } + } + else { + if (val < value_) { + value_ = val; + } + } + } + void Populate(uint8_t* pBase) const override + { + auto* pTarget = pBase + offsetBytes_; + *reinterpret_cast(pTarget) = hasValue_ ? value_ : 0.0; + } + private: + bool isMax_ = false; + double value_ = 0.0; + bool hasValue_ = false; + }; + + // supports double AND int32/enum + class DynamicStatPoint_ : public DynamicStat + { + public: + DynamicStatPoint_(PM_DATA_TYPE dataType, size_t offsetBytes, PM_STAT mode) + : DynamicStat{ dataType, offsetBytes }, + mode_{ mode } + { + } + bool NeedsUpdate() const override { return false; } + bool NeedsPointSample() const override { return true; } + bool NeedsSortedWindow() const override { return false; } + uint64_t GetSamplePoint(const DynamicQueryWindow& win) const override + { + switch (mode_) { + case PM_STAT_OLDEST_POINT: + return win.oldest; + case PM_STAT_NEWEST_POINT: + return win.newest; + case PM_STAT_MID_POINT: + return win.oldest + (win.newest - win.oldest) / 2; + } + return win.newest; + } + void SetSampledValueDouble_(double val) override + { + if (dataType_ != PM_DATA_TYPE_DOUBLE) { + ThrowMalformed_("DynamicStat point expects int32 or enum value"); + } + valueDouble_ = val; + hasValue_ = true; + } + void SetSampledValueInt32_(int32_t val) override + { + if (dataType_ != PM_DATA_TYPE_INT32 && dataType_ != PM_DATA_TYPE_ENUM) { + ThrowMalformed_("DynamicStat point expects double value"); + } + valueInt32_ = val; + hasValue_ = true; + } + void Populate(uint8_t* pBase) const override + { + if (dataType_ == PM_DATA_TYPE_DOUBLE) { + auto* pTarget = pBase + offsetBytes_; + *reinterpret_cast(pTarget) = hasValue_ ? valueDouble_ : 0.0; + } + else { + auto* pTarget = pBase + offsetBytes_; + *reinterpret_cast(pTarget) = hasValue_ ? valueInt32_ : 0; + } + } + private: + PM_STAT mode_ = PM_STAT_NEWEST_POINT; + bool hasValue_ = false; + double valueDouble_ = 0.0; + int32_t valueInt32_ = 0; + }; + } + + DynamicStat::DynamicStat(PM_DATA_TYPE dataType, size_t offsetBytes) + : dataType_{ dataType }, + offsetBytes_{ offsetBytes } + { + } + + DynamicStat::~DynamicStat() = default; + + void DynamicStat::ThrowMalformed_(const char* msg) const + { + throw util::Except(PM_STATUS_QUERY_MALFORMED, msg); + } + + void DynamicStat::AddSample(double) + { + ThrowMalformed_("DynamicStat::AddSample unsupported for this stat"); + } + + uint64_t DynamicStat::GetSamplePoint(const DynamicQueryWindow&) const + { + ThrowMalformed_("DynamicStat::GetSamplePoint unsupported for this stat"); + return 0; + } + + void DynamicStat::SetSampledValue(double val) + { + SetSampledValueDouble_(val); + } + + void DynamicStat::SetSampledValue(int32_t val) + { + SetSampledValueInt32_(val); + } + + void DynamicStat::SetSampledValueDouble_(double) + { + ThrowMalformed_("DynamicStat::SetSampledValueDouble unsupported for this stat"); + } + + void DynamicStat::SetSampledValueInt32_(int32_t) + { + ThrowMalformed_("DynamicStat::SetSampledValueInt32 unsupported for this stat"); + } + + void DynamicStat::InputSortedSamples(std::span) + { + ThrowMalformed_("DynamicStat::InputSortedSamples unsupported for this stat"); + } + + void DynamicStat::Populate(uint8_t*) const + { + ThrowMalformed_("DynamicStat::Populate unsupported for this stat"); + } + + std::unique_ptr CreateDynamicStat(PM_STAT stat, PM_DATA_TYPE dataType, size_t offsetBytes) + { + const bool isPointStat = stat == PM_STAT_MID_POINT + || stat == PM_STAT_NEWEST_POINT + || stat == PM_STAT_OLDEST_POINT; + if (isPointStat) { + switch (dataType) { + case PM_DATA_TYPE_DOUBLE: + case PM_DATA_TYPE_INT32: + case PM_DATA_TYPE_ENUM: + break; + default: + throw util::Except(PM_STATUS_QUERY_MALFORMED, "Unsupported dynamic stat data type"); + } + } + else { + if (dataType != PM_DATA_TYPE_DOUBLE) { + throw util::Except(PM_STATUS_QUERY_MALFORMED, "Unsupported dynamic stat data type"); + } + } + + switch (stat) { + case PM_STAT_AVG: + return std::make_unique(dataType, offsetBytes, false); + case PM_STAT_NON_ZERO_AVG: + return std::make_unique(dataType, offsetBytes, true); + case PM_STAT_PERCENTILE_99: + return std::make_unique(dataType, offsetBytes, 0.99); + case PM_STAT_PERCENTILE_95: + return std::make_unique(dataType, offsetBytes, 0.95); + case PM_STAT_PERCENTILE_90: + return std::make_unique(dataType, offsetBytes, 0.90); + case PM_STAT_PERCENTILE_01: + return std::make_unique(dataType, offsetBytes, 0.01); + case PM_STAT_PERCENTILE_05: + return std::make_unique(dataType, offsetBytes, 0.05); + case PM_STAT_PERCENTILE_10: + return std::make_unique(dataType, offsetBytes, 0.10); + case PM_STAT_MAX: + return std::make_unique(dataType, offsetBytes, true); + case PM_STAT_MIN: + return std::make_unique(dataType, offsetBytes, false); + case PM_STAT_MID_POINT: + return std::make_unique(dataType, offsetBytes, PM_STAT_MID_POINT); + case PM_STAT_NEWEST_POINT: + return std::make_unique(dataType, offsetBytes, PM_STAT_NEWEST_POINT); + case PM_STAT_OLDEST_POINT: + return std::make_unique(dataType, offsetBytes, PM_STAT_OLDEST_POINT); + case PM_STAT_NONE: + case PM_STAT_MID_LERP: + case PM_STAT_COUNT: + default: + throw util::Except(PM_STATUS_QUERY_MALFORMED, "Unsupported dynamic stat"); + } + } +} diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicStat.h b/IntelPresentMon/PresentMonMiddleware/DynamicStat.h new file mode 100644 index 00000000..0e72b77c --- /dev/null +++ b/IntelPresentMon/PresentMonMiddleware/DynamicStat.h @@ -0,0 +1,34 @@ +#pragma once +#include +#include +#include +#include +#include "DynamicQueryWindow.h" +#include "../PresentMonAPI2/PresentMonAPI.h" + +namespace pmon::mid +{ + class DynamicStat + { + public: + virtual ~DynamicStat(); + virtual bool NeedsUpdate() const = 0; + virtual bool NeedsPointSample() const = 0; + virtual bool NeedsSortedWindow() const = 0; + virtual void AddSample(double val); + virtual uint64_t GetSamplePoint(const DynamicQueryWindow& win) const; + void SetSampledValue(double val); + void SetSampledValue(int32_t val); + virtual void InputSortedSamples(std::span sortedSamples); + virtual void Populate(uint8_t* pBase) const; + protected: + DynamicStat(PM_DATA_TYPE dataType, size_t offsetBytes); + void ThrowMalformed_(const char* msg) const; + virtual void SetSampledValueDouble_(double val); + virtual void SetSampledValueInt32_(int32_t val); + PM_DATA_TYPE dataType_ = PM_DATA_TYPE_DOUBLE; + size_t offsetBytes_ = 0; + }; + + std::unique_ptr CreateDynamicStat(PM_STAT stat, PM_DATA_TYPE dataType, size_t offsetBytes); +} diff --git a/IntelPresentMon/PresentMonMiddleware/HistoryRingTraverser.cpp b/IntelPresentMon/PresentMonMiddleware/HistoryRingTraverser.cpp new file mode 100644 index 00000000..44f82630 --- /dev/null +++ b/IntelPresentMon/PresentMonMiddleware/HistoryRingTraverser.cpp @@ -0,0 +1,12 @@ +#include "HistoryRingTraverser.h" + +namespace pmon::mid +{ + HistoryRingTraverser::HistoryRingTraverser() = default; + HistoryRingTraverser::~HistoryRingTraverser() = default; + + size_t HistoryRingTraverser::GetCursor() const + { + return cursor_; + } +} diff --git a/IntelPresentMon/PresentMonMiddleware/HistoryRingTraverser.h b/IntelPresentMon/PresentMonMiddleware/HistoryRingTraverser.h new file mode 100644 index 00000000..4670bea5 --- /dev/null +++ b/IntelPresentMon/PresentMonMiddleware/HistoryRingTraverser.h @@ -0,0 +1,18 @@ +#pragma once +#include + +namespace pmon::mid +{ + class HistoryRingTraverser + { + public: + HistoryRingTraverser(); + ~HistoryRingTraverser(); + + size_t GetCursor() const; + + private: + size_t cursor_ = 0; + size_t size_ = 0; + }; +} diff --git a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj index 47f71b1c..8fd8f91f 100644 --- a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj +++ b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj @@ -12,12 +12,16 @@ + + + + @@ -25,8 +29,12 @@ /bigobj %(AdditionalOptions) + + + + @@ -136,4 +144,4 @@ - + \ No newline at end of file diff --git a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters index 3e4931de..02ef3317 100644 --- a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters +++ b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters @@ -27,6 +27,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + Header Files @@ -42,17 +51,32 @@ Header Files + + Header Files + Source Files + + Source Files + + + Source Files + + + Source Files + Source Files Source Files + + Source Files + Source Files @@ -60,4 +84,4 @@ Source Files - + \ No newline at end of file From 8778e2dd551166fa6b96d3483455bf434ace98ae Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 23 Jan 2026 07:04:52 +0900 Subject: [PATCH 143/205] 2nd draft templated dynamic stat --- .../PresentMonMiddleware/DynamicStat.cpp | 342 ++++-------------- .../PresentMonMiddleware/DynamicStat.h | 297 ++++++++++++++- 2 files changed, 358 insertions(+), 281 deletions(-) diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp index d1e0d6d0..bf5eb429 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp +++ b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp @@ -1,7 +1,4 @@ #include "DynamicStat.h" -#include "../CommonUtilities/Exception.h" -#include "../Interprocess/source/PmStatusError.h" -#include namespace ipc = pmon::ipc; namespace util = pmon::util; @@ -10,286 +7,97 @@ namespace pmon::mid { namespace { - // only supports double - class DynamicStatAverage_ : public DynamicStat + bool IsAvgStat_(PM_STAT stat) { - public: - DynamicStatAverage_(PM_DATA_TYPE dataType, size_t offsetBytes, bool skipZero) - : DynamicStat{ dataType, offsetBytes }, - skipZero_{ skipZero } - { - } - bool NeedsUpdate() const override { return true; } - bool NeedsPointSample() const override { return false; } - bool NeedsSortedWindow() const override { return false; } - void AddSample(double val) override - { - if (skipZero_ && val == 0.0) { - return; - } - sum_ += val; - ++count_; - } - void Populate(uint8_t* pBase) const override - { - double avg = 0.0; - if (count_ > 0) { - avg = sum_ / (double)count_; - } - auto* pTarget = pBase + offsetBytes_; - *reinterpret_cast(pTarget) = avg; - } - private: - bool skipZero_ = false; - double sum_ = 0.0; - size_t count_ = 0; - }; + return stat == PM_STAT_AVG || stat == PM_STAT_NON_ZERO_AVG; + } - // only supports double - class DynamicStatPercentile_ : public DynamicStat + bool IsSupportedOutputType_(PM_DATA_TYPE outType, bool allowBool) { - public: - DynamicStatPercentile_(PM_DATA_TYPE dataType, size_t offsetBytes, double percentile) - : DynamicStat{ dataType, offsetBytes }, - percentile_{ percentile } - { - } - bool NeedsUpdate() const override { return true; } - bool NeedsPointSample() const override { return false; } - bool NeedsSortedWindow() const override { return true; } - void InputSortedSamples(std::span sortedSamples) override - { - if (sortedSamples.empty()) { - value_ = 0.0; - hasValue_ = false; - return; - } - - const double clamped = std::clamp(percentile_, 0.0, 1.0); - const size_t last = sortedSamples.size() - 1; - const double position = clamped * (double)last; - const size_t index = (size_t)(position + 0.5); - value_ = sortedSamples[index]; - hasValue_ = true; - } - void Populate(uint8_t* pBase) const override - { - auto* pTarget = pBase + offsetBytes_; - *reinterpret_cast(pTarget) = hasValue_ ? value_ : 0.0; + switch (outType) { + case PM_DATA_TYPE_DOUBLE: + case PM_DATA_TYPE_INT32: + case PM_DATA_TYPE_ENUM: + return true; + case PM_DATA_TYPE_BOOL: + return allowBool; + default: + return false; } - private: - double percentile_ = 0.0; - double value_ = 0.0; - bool hasValue_ = false; - }; + } - // only supports double - class DynamicStatMinMax_ : public DynamicStat + void ValidateOutputType_(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType) { - public: - DynamicStatMinMax_(PM_DATA_TYPE dataType, size_t offsetBytes, bool isMax) - : DynamicStat{ dataType, offsetBytes }, - isMax_{ isMax } - { - } - bool NeedsUpdate() const override { return true; } - bool NeedsPointSample() const override { return false; } - bool NeedsSortedWindow() const override { return false; } - void AddSample(double val) override - { - if (!hasValue_) { - value_ = val; - hasValue_ = true; - return; - } - if (isMax_) { - if (val > value_) { - value_ = val; - } - } - else { - if (val < value_) { - value_ = val; - } + if (IsAvgStat_(stat)) { + if (outType != PM_DATA_TYPE_DOUBLE) { + throw util::Except(PM_STATUS_QUERY_MALFORMED, + "Dynamic stat average expects double output value"); } + return; } - void Populate(uint8_t* pBase) const override - { - auto* pTarget = pBase + offsetBytes_; - *reinterpret_cast(pTarget) = hasValue_ ? value_ : 0.0; - } - private: - bool isMax_ = false; - double value_ = 0.0; - bool hasValue_ = false; - }; - // supports double AND int32/enum - class DynamicStatPoint_ : public DynamicStat - { - public: - DynamicStatPoint_(PM_DATA_TYPE dataType, size_t offsetBytes, PM_STAT mode) - : DynamicStat{ dataType, offsetBytes }, - mode_{ mode } - { - } - bool NeedsUpdate() const override { return false; } - bool NeedsPointSample() const override { return true; } - bool NeedsSortedWindow() const override { return false; } - uint64_t GetSamplePoint(const DynamicQueryWindow& win) const override - { - switch (mode_) { - case PM_STAT_OLDEST_POINT: - return win.oldest; - case PM_STAT_NEWEST_POINT: - return win.newest; - case PM_STAT_MID_POINT: - return win.oldest + (win.newest - win.oldest) / 2; - } - return win.newest; - } - void SetSampledValueDouble_(double val) override - { - if (dataType_ != PM_DATA_TYPE_DOUBLE) { - ThrowMalformed_("DynamicStat point expects int32 or enum value"); - } - valueDouble_ = val; - hasValue_ = true; - } - void SetSampledValueInt32_(int32_t val) override - { - if (dataType_ != PM_DATA_TYPE_INT32 && dataType_ != PM_DATA_TYPE_ENUM) { - ThrowMalformed_("DynamicStat point expects double value"); - } - valueInt32_ = val; - hasValue_ = true; - } - void Populate(uint8_t* pBase) const override - { - if (dataType_ == PM_DATA_TYPE_DOUBLE) { - auto* pTarget = pBase + offsetBytes_; - *reinterpret_cast(pTarget) = hasValue_ ? valueDouble_ : 0.0; - } - else { - auto* pTarget = pBase + offsetBytes_; - *reinterpret_cast(pTarget) = hasValue_ ? valueInt32_ : 0; - } + const bool allowBool = inType == PM_DATA_TYPE_BOOL; + if (!IsSupportedOutputType_(outType, allowBool)) { + throw util::Except(PM_STATUS_QUERY_MALFORMED, + "Unsupported dynamic stat output data type"); } - private: - PM_STAT mode_ = PM_STAT_NEWEST_POINT; - bool hasValue_ = false; - double valueDouble_ = 0.0; - int32_t valueInt32_ = 0; - }; - } - - DynamicStat::DynamicStat(PM_DATA_TYPE dataType, size_t offsetBytes) - : dataType_{ dataType }, - offsetBytes_{ offsetBytes } - { - } - - DynamicStat::~DynamicStat() = default; - - void DynamicStat::ThrowMalformed_(const char* msg) const - { - throw util::Except(PM_STATUS_QUERY_MALFORMED, msg); - } - - void DynamicStat::AddSample(double) - { - ThrowMalformed_("DynamicStat::AddSample unsupported for this stat"); - } - - uint64_t DynamicStat::GetSamplePoint(const DynamicQueryWindow&) const - { - ThrowMalformed_("DynamicStat::GetSamplePoint unsupported for this stat"); - return 0; - } - - void DynamicStat::SetSampledValue(double val) - { - SetSampledValueDouble_(val); - } - - void DynamicStat::SetSampledValue(int32_t val) - { - SetSampledValueInt32_(val); - } - - void DynamicStat::SetSampledValueDouble_(double) - { - ThrowMalformed_("DynamicStat::SetSampledValueDouble unsupported for this stat"); - } - - void DynamicStat::SetSampledValueInt32_(int32_t) - { - ThrowMalformed_("DynamicStat::SetSampledValueInt32 unsupported for this stat"); - } - - void DynamicStat::InputSortedSamples(std::span) - { - ThrowMalformed_("DynamicStat::InputSortedSamples unsupported for this stat"); - } - - void DynamicStat::Populate(uint8_t*) const - { - ThrowMalformed_("DynamicStat::Populate unsupported for this stat"); - } + } - std::unique_ptr CreateDynamicStat(PM_STAT stat, PM_DATA_TYPE dataType, size_t offsetBytes) - { - const bool isPointStat = stat == PM_STAT_MID_POINT - || stat == PM_STAT_NEWEST_POINT - || stat == PM_STAT_OLDEST_POINT; - if (isPointStat) { - switch (dataType) { - case PM_DATA_TYPE_DOUBLE: - case PM_DATA_TYPE_INT32: - case PM_DATA_TYPE_ENUM: - break; + template + std::unique_ptr> MakeDynamicStatTyped_(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes) + { + ValidateOutputType_(stat, inType, outType); + + switch (stat) { + case PM_STAT_AVG: + return std::make_unique>(inType, outType, offsetBytes, false); + case PM_STAT_NON_ZERO_AVG: + return std::make_unique>(inType, outType, offsetBytes, true); + case PM_STAT_PERCENTILE_99: + return std::make_unique>(inType, outType, offsetBytes, 0.99); + case PM_STAT_PERCENTILE_95: + return std::make_unique>(inType, outType, offsetBytes, 0.95); + case PM_STAT_PERCENTILE_90: + return std::make_unique>(inType, outType, offsetBytes, 0.90); + case PM_STAT_PERCENTILE_01: + return std::make_unique>(inType, outType, offsetBytes, 0.01); + case PM_STAT_PERCENTILE_05: + return std::make_unique>(inType, outType, offsetBytes, 0.05); + case PM_STAT_PERCENTILE_10: + return std::make_unique>(inType, outType, offsetBytes, 0.10); + case PM_STAT_MAX: + return std::make_unique>(inType, outType, offsetBytes, true); + case PM_STAT_MIN: + return std::make_unique>(inType, outType, offsetBytes, false); + case PM_STAT_MID_POINT: + return std::make_unique>(inType, outType, offsetBytes, PM_STAT_MID_POINT); + case PM_STAT_NEWEST_POINT: + return std::make_unique>(inType, outType, offsetBytes, PM_STAT_NEWEST_POINT); + case PM_STAT_OLDEST_POINT: + return std::make_unique>(inType, outType, offsetBytes, PM_STAT_OLDEST_POINT); + case PM_STAT_NONE: + case PM_STAT_MID_LERP: + case PM_STAT_COUNT: default: - throw util::Except(PM_STATUS_QUERY_MALFORMED, "Unsupported dynamic stat data type"); - } - } - else { - if (dataType != PM_DATA_TYPE_DOUBLE) { - throw util::Except(PM_STATUS_QUERY_MALFORMED, "Unsupported dynamic stat data type"); + throw util::Except(PM_STATUS_QUERY_MALFORMED, + "Unsupported dynamic stat"); } } + } - switch (stat) { - case PM_STAT_AVG: - return std::make_unique(dataType, offsetBytes, false); - case PM_STAT_NON_ZERO_AVG: - return std::make_unique(dataType, offsetBytes, true); - case PM_STAT_PERCENTILE_99: - return std::make_unique(dataType, offsetBytes, 0.99); - case PM_STAT_PERCENTILE_95: - return std::make_unique(dataType, offsetBytes, 0.95); - case PM_STAT_PERCENTILE_90: - return std::make_unique(dataType, offsetBytes, 0.90); - case PM_STAT_PERCENTILE_01: - return std::make_unique(dataType, offsetBytes, 0.01); - case PM_STAT_PERCENTILE_05: - return std::make_unique(dataType, offsetBytes, 0.05); - case PM_STAT_PERCENTILE_10: - return std::make_unique(dataType, offsetBytes, 0.10); - case PM_STAT_MAX: - return std::make_unique(dataType, offsetBytes, true); - case PM_STAT_MIN: - return std::make_unique(dataType, offsetBytes, false); - case PM_STAT_MID_POINT: - return std::make_unique(dataType, offsetBytes, PM_STAT_MID_POINT); - case PM_STAT_NEWEST_POINT: - return std::make_unique(dataType, offsetBytes, PM_STAT_NEWEST_POINT); - case PM_STAT_OLDEST_POINT: - return std::make_unique(dataType, offsetBytes, PM_STAT_OLDEST_POINT); - case PM_STAT_NONE: - case PM_STAT_MID_LERP: - case PM_STAT_COUNT: + std::unique_ptr MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes) + { + switch (inType) { + case PM_DATA_TYPE_DOUBLE: + return MakeDynamicStatTyped_(stat, inType, outType, offsetBytes); + case PM_DATA_TYPE_INT32: + case PM_DATA_TYPE_ENUM: + return MakeDynamicStatTyped_(stat, inType, outType, offsetBytes); + case PM_DATA_TYPE_BOOL: + return MakeDynamicStatTyped_(stat, inType, outType, offsetBytes); default: - throw util::Except(PM_STATUS_QUERY_MALFORMED, "Unsupported dynamic stat"); + throw util::Except(PM_STATUS_QUERY_MALFORMED, + "Unsupported dynamic stat input data type"); } } } diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicStat.h b/IntelPresentMon/PresentMonMiddleware/DynamicStat.h index 0e72b77c..72472ab6 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicStat.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicStat.h @@ -1,34 +1,303 @@ #pragma once +#include #include #include #include #include #include "DynamicQueryWindow.h" +#include "../CommonUtilities/Exception.h" +#include "../Interprocess/source/PmStatusError.h" #include "../PresentMonAPI2/PresentMonAPI.h" namespace pmon::mid { - class DynamicStat + class DynamicStatBase { public: - virtual ~DynamicStat(); + virtual ~DynamicStatBase() = default; virtual bool NeedsUpdate() const = 0; virtual bool NeedsPointSample() const = 0; virtual bool NeedsSortedWindow() const = 0; - virtual void AddSample(double val); - virtual uint64_t GetSamplePoint(const DynamicQueryWindow& win) const; - void SetSampledValue(double val); - void SetSampledValue(int32_t val); - virtual void InputSortedSamples(std::span sortedSamples); - virtual void Populate(uint8_t* pBase) const; + virtual void AddSample(double) + { + ThrowMalformed_("DynamicStat::AddSample unsupported for this stat"); + } + virtual void AddSample(int32_t) + { + ThrowMalformed_("DynamicStat::AddSample unsupported for this stat"); + } + virtual void AddSample(bool) + { + ThrowMalformed_("DynamicStat::AddSample unsupported for this stat"); + } + virtual uint64_t GetSamplePoint(const DynamicQueryWindow& win) const + { + ThrowMalformed_("DynamicStat::GetSamplePoint unsupported for this stat"); + return 0; + } + virtual void SetSampledValue(double) + { + ThrowMalformed_("DynamicStat::SetSampledValue unsupported for this stat"); + } + virtual void SetSampledValue(int32_t) + { + ThrowMalformed_("DynamicStat::SetSampledValue unsupported for this stat"); + } + virtual void SetSampledValue(bool) + { + ThrowMalformed_("DynamicStat::SetSampledValue unsupported for this stat"); + } + virtual void InputSortedSamples(std::span) + { + ThrowMalformed_("DynamicStat::InputSortedSamples unsupported for this stat"); + } + virtual void InputSortedSamples(std::span) + { + ThrowMalformed_("DynamicStat::InputSortedSamples unsupported for this stat"); + } + virtual void InputSortedSamples(std::span) + { + ThrowMalformed_("DynamicStat::InputSortedSamples unsupported for this stat"); + } + virtual void Populate(uint8_t* pBase) const + { + ThrowMalformed_("DynamicStat::Populate unsupported for this stat"); + } protected: - DynamicStat(PM_DATA_TYPE dataType, size_t offsetBytes); - void ThrowMalformed_(const char* msg) const; - virtual void SetSampledValueDouble_(double val); - virtual void SetSampledValueInt32_(int32_t val); - PM_DATA_TYPE dataType_ = PM_DATA_TYPE_DOUBLE; + DynamicStatBase(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes) + : inType_{ inType }, + outType_{ outType }, + offsetBytes_{ offsetBytes } + { + } + void ThrowMalformed_(const char* msg) const + { + throw pmon::util::Except(PM_STATUS_QUERY_MALFORMED, msg); + } + PM_DATA_TYPE inType_ = PM_DATA_TYPE_DOUBLE; + PM_DATA_TYPE outType_ = PM_DATA_TYPE_DOUBLE; size_t offsetBytes_ = 0; }; - std::unique_ptr CreateDynamicStat(PM_STAT stat, PM_DATA_TYPE dataType, size_t offsetBytes); + template + class DynamicStat : public DynamicStatBase + { + public: + void SetSampledValue(T val) override + { + SetSampledValue_(val); + } + protected: + DynamicStat(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes) + : DynamicStatBase(inType, outType, offsetBytes) + { + } + virtual void SetSampledValue_(T) + { + ThrowMalformed_("DynamicStat::SetSampledValue unsupported for this stat"); + } + }; + + namespace detail + { + template + class DynamicStatAverage_ : public DynamicStat + { + public: + DynamicStatAverage_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, bool skipZero) + : DynamicStat{ inType, outType, offsetBytes }, + skipZero_{ skipZero } + { + } + bool NeedsUpdate() const override { return true; } + bool NeedsPointSample() const override { return false; } + bool NeedsSortedWindow() const override { return false; } + void AddSample(T val) override + { + if (skipZero_ && val == (T)0) { + return; + } + sum_ += (double)val; + ++count_; + } + void Populate(uint8_t* pBase) const override + { + if (this->outType_ != PM_DATA_TYPE_DOUBLE) { + this->ThrowMalformed_("DynamicStat average expects double output value"); + } + double avg = 0.0; + if (count_ > 0) { + avg = sum_ / (double)count_; + } + auto* pTarget = pBase + this->offsetBytes_; + *reinterpret_cast(pTarget) = avg; + } + private: + bool skipZero_ = false; + double sum_ = 0.0; + size_t count_ = 0; + }; + + template + class DynamicStatPercentile_ : public DynamicStat + { + public: + DynamicStatPercentile_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, double percentile) + : DynamicStat{ inType, outType, offsetBytes }, + percentile_{ percentile } + { + } + bool NeedsUpdate() const override { return true; } + bool NeedsPointSample() const override { return false; } + bool NeedsSortedWindow() const override { return true; } + void InputSortedSamples(std::span sortedSamples) override + { + if (sortedSamples.empty()) { + value_ = 0.0; + hasValue_ = false; + return; + } + + const double clamped = std::clamp(percentile_, 0.0, 1.0); + const size_t last = sortedSamples.size() - 1; + const double position = clamped * (double)last; + const size_t index = (size_t)(position + 0.5); + value_ = (double)sortedSamples[index]; + hasValue_ = true; + } + void Populate(uint8_t* pBase) const override + { + auto* pTarget = pBase + this->offsetBytes_; + switch (this->outType_) { + case PM_DATA_TYPE_DOUBLE: + *reinterpret_cast(pTarget) = hasValue_ ? value_ : 0.0; + break; + case PM_DATA_TYPE_INT32: + case PM_DATA_TYPE_ENUM: + *reinterpret_cast(pTarget) = hasValue_ ? (int32_t)value_ : 0; + break; + case PM_DATA_TYPE_BOOL: + *reinterpret_cast(pTarget) = hasValue_ ? value_ != 0.0 : false; + break; + default: + this->ThrowMalformed_("DynamicStat percentile expects double, int32, enum, or bool output value"); + } + } + private: + double percentile_ = 0.0; + double value_ = 0.0; + bool hasValue_ = false; + }; + + template + class DynamicStatMinMax_ : public DynamicStat + { + public: + DynamicStatMinMax_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, bool isMax) + : DynamicStat{ inType, outType, offsetBytes }, + isMax_{ isMax } + { + } + bool NeedsUpdate() const override { return true; } + bool NeedsPointSample() const override { return false; } + bool NeedsSortedWindow() const override { return false; } + void AddSample(T val) override + { + const double doubleVal = (double)val; + if (!hasValue_) { + value_ = doubleVal; + hasValue_ = true; + return; + } + if (isMax_) { + if (doubleVal > value_) { + value_ = doubleVal; + } + } + else { + if (doubleVal < value_) { + value_ = doubleVal; + } + } + } + void Populate(uint8_t* pBase) const override + { + auto* pTarget = pBase + this->offsetBytes_; + switch (this->outType_) { + case PM_DATA_TYPE_DOUBLE: + *reinterpret_cast(pTarget) = hasValue_ ? value_ : 0.0; + break; + case PM_DATA_TYPE_INT32: + case PM_DATA_TYPE_ENUM: + *reinterpret_cast(pTarget) = hasValue_ ? (int32_t)value_ : 0; + break; + case PM_DATA_TYPE_BOOL: + *reinterpret_cast(pTarget) = hasValue_ ? value_ != 0.0 : false; + break; + default: + this->ThrowMalformed_("DynamicStat min/max expects double, int32, enum, or bool output value"); + } + } + private: + bool isMax_ = false; + double value_ = 0.0; + bool hasValue_ = false; + }; + + template + class DynamicStatPoint_ : public DynamicStat + { + public: + DynamicStatPoint_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, PM_STAT mode) + : DynamicStat{ inType, outType, offsetBytes }, + mode_{ mode } + { + } + bool NeedsUpdate() const override { return false; } + bool NeedsPointSample() const override { return true; } + bool NeedsSortedWindow() const override { return false; } + uint64_t GetSamplePoint(const DynamicQueryWindow& win) const override + { + switch (mode_) { + case PM_STAT_OLDEST_POINT: + return win.oldest; + case PM_STAT_NEWEST_POINT: + return win.newest; + case PM_STAT_MID_POINT: + return win.oldest + (win.newest - win.oldest) / 2; + } + return win.newest; + } + void Populate(uint8_t* pBase) const override + { + auto* pTarget = pBase + this->offsetBytes_; + const double doubleVal = (double)value_; + switch (this->outType_) { + case PM_DATA_TYPE_DOUBLE: + *reinterpret_cast(pTarget) = hasValue_ ? doubleVal : 0.0; + break; + case PM_DATA_TYPE_INT32: + case PM_DATA_TYPE_ENUM: + *reinterpret_cast(pTarget) = hasValue_ ? (int32_t)doubleVal : 0; + break; + case PM_DATA_TYPE_BOOL: + *reinterpret_cast(pTarget) = hasValue_ ? doubleVal != 0.0 : false; + break; + default: + this->ThrowMalformed_("DynamicStat point expects double, int32, enum, or bool output value"); + } + } + private: + void SetSampledValue_(T val) override + { + value_ = val; + hasValue_ = true; + } + PM_STAT mode_ = PM_STAT_MID_POINT; + bool hasValue_ = false; + T value_ = (T)0; + }; + } + + std::unique_ptr MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); } From 3bc49ea88ed947b78fba611b6b19418e05b393ea Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 23 Jan 2026 13:56:29 +0900 Subject: [PATCH 144/205] 1st draft dynamic metric --- .../PresentMonMiddleware/DynamicMetric.cpp | 12 - .../PresentMonMiddleware/DynamicMetric.h | 179 ++++++++++- .../PresentMonMiddleware/DynamicQuery.cpp | 3 - .../PresentMonMiddleware/DynamicQuery.h | 31 +- .../PresentMonMiddleware/DynamicStat.cpp | 292 ++++++++++++++++- .../PresentMonMiddleware/DynamicStat.h | 298 +----------------- .../PresentMonMiddleware.vcxproj | 1 - .../PresentMonMiddleware.vcxproj.filters | 3 - 8 files changed, 496 insertions(+), 323 deletions(-) delete mode 100644 IntelPresentMon/PresentMonMiddleware/DynamicMetric.cpp diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.cpp deleted file mode 100644 index 3d3640fd..00000000 --- a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "DynamicMetric.h" - -namespace pmon::mid -{ - DynamicMetric::DynamicMetric() = default; - DynamicMetric::~DynamicMetric() = default; - - uint32_t DynamicMetric::GetId() const - { - return id_; - } -} diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h index b47072fa..84620cb2 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h @@ -1,19 +1,190 @@ #pragma once +#include +#include #include +#include +#include +#include #include +#include +#include "DynamicQueryWindow.h" #include "DynamicStat.h" +#include "../CommonUtilities/Exception.h" +#include "../Interprocess/source/IntrospectionHelpers.h" +#include "../Interprocess/source/PmStatusError.h" +#include "../PresentMonAPIWrapperCommon/Introspection.h" + +namespace pmapi::intro +{ + class Root; + class MetricView; + class StatView; +} namespace pmon::mid { // container for multiple stats connected to a single metric // implements shared stat calculation facilities (i.e. sorted sample buffer for %) - template + template class DynamicMetric { public: - DynamicMetric(); - ~DynamicMetric(); + DynamicMetric() = default; + ~DynamicMetric() = default; + + virtual void AddSample(const S& sample) = 0; + virtual const std::vector& GetRequestedSamplePoints(const DynamicQueryWindow& window) const = 0; + virtual void InputRequestedPointSamples(const std::vector& samples) = 0; + virtual void GatherToBlob(uint8_t* pBlobBase) const = 0; + virtual uint32_t AddStat(PM_STAT stat, uint32_t blobByteOffset, const pmapi::intro::Root& intro) = 0; + virtual void FinalizeStats() = 0; + }; + + template + class DynamicMetricBinding : public DynamicMetric + { + public: + DynamicMetricBinding(PM_METRIC metric) + : + metric_{ metric } + { + static_assert(std::is_same_v || std::is_same_v || std::is_same_v, + "DynamicMetricBinding only supports double, int32_t, and bool sample types."); + } + + void AddSample(const S& sample) override + { + const auto& value = sample.*MemberPtr; + // if samples has reserved size, it is needed + if (samples_.capacity()) { + samples_.push_back(value); + } + for (auto* stat : needsUpdatePtrs_) { + stat->AddSample(value); + } + } + + const std::vector& GetRequestedSamplePoints(const DynamicQueryWindow& window) const override + { + if (!needsSamplePtrs_.empty()) { + requestedSamplePoints_.clear(); + for (auto* stat : needsSamplePtrs_) { + requestedSamplePoints_.push_back(stat->GetSamplePoint(window)); + } + } + return requestedSamplePoints_; + } + + void InputRequestedPointSamples(const std::vector& samplePtrs) override + { + if (samplePtrs.size() != needsSamplePtrs_.size()) { + throw pmon::util::Except(PM_STATUS_FAILURE, + "DynamicMetricBinding received unexpected number of point samples."); + } + + for (auto&& [sample, stat] : std::views::zip(samplePtrs, needsSamplePtrs_)) { + if (sample == nullptr) { + throw pmon::util::Except(PM_STATUS_FAILURE, + "DynamicMetricBinding received null point sample."); + } + stat->SetSampledValue(sample->*MemberPtr); + } + } + + void GatherToBlob(uint8_t* pBlobBase) const override + { + if (!needsSortedWindowPtrs_.empty()) { + std::ranges::sort(samples_); + for (auto pStat : needsSortedWindowPtrs_) { + pStat->InputSortedSamples(samples_); + } + } + // clear the sample sorting buffer for the next poll + samples_.clear(); + for (auto& pStat : statPtrs_) { + pStat->GatherToBlob(pBlobBase); + } + } + + uint32_t AddStat(PM_STAT stat, uint32_t blobByteOffset, const pmapi::intro::Root& intro) override + { + const auto metricView = intro.FindMetric(metric_); + if (!IsStatSupported_(stat, metricView)) { + throw pmon::util::Except(PM_STATUS_QUERY_MALFORMED, + "Dynamic metric stat not supported by metric."); + } + + const auto inType = GetSampleType_(); + auto outType = SelectOutputType_(stat, metricView.GetDataTypeInfo().GetPolledType()); + auto statPtr = MakeDynamicStat(stat, inType, outType, blobByteOffset); + auto* rawPtr = statPtr.get(); + statPtrs_.push_back(std::move(statPtr)); + + if (rawPtr->NeedsPointSample()) { + needsSamplePtrs_.push_back(rawPtr); + } + else if (rawPtr->NeedsSortedWindow()) { + needsSortedWindowPtrs_.push_back(rawPtr); + } + else if (!rawPtr->NeedsSortedWindow() && rawPtr->NeedsUpdate()) { + needsUpdatePtrs_.push_back(rawPtr); + } + + return (uint32_t)ipc::intro::GetDataTypeSize(outType); + } + + void FinalizeStats() override + { + requestedSamplePoints_.reserve(needsSamplePtrs_.size()); + if (!needsSortedWindowPtrs_.empty()) { + // a decent middleground based on typical 1 sec window and 144 fps typical upper fps + samples_.reserve(150); + } + } + + ~DynamicMetricBinding() = default; + private: - std::vector samples_; + static constexpr PM_DATA_TYPE GetSampleType_() + { + if constexpr (std::is_same_v) { + return PM_DATA_TYPE_DOUBLE; + } + else if constexpr (std::is_same_v) { + return PM_DATA_TYPE_INT32; + } + else if constexpr (std::is_same_v) { + return PM_DATA_TYPE_BOOL; + } + else { + return PM_DATA_TYPE_VOID; + } + } + + static PM_DATA_TYPE SelectOutputType_(PM_STAT stat, PM_DATA_TYPE metricOutType) + { + if (stat == PM_STAT_AVG || stat == PM_STAT_NON_ZERO_AVG) { + return PM_DATA_TYPE_DOUBLE; + } + return metricOutType; + } + + static bool IsStatSupported_(PM_STAT stat, const pmapi::intro::MetricView& metricView) + { + for (auto statInfo : metricView.GetStatInfo()) { + if (statInfo.GetStat() == stat) { + return true; + } + } + return false; + } + + PM_METRIC metric_; + mutable std::vector samples_; + std::vector>> statPtrs_; + std::vector*> needsUpdatePtrs_; + std::vector*> needsSamplePtrs_; + std::vector*> needsSortedWindowPtrs_; + mutable std::vector requestedSamplePoints_; }; } diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp index 3969c3bf..79c11df7 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp @@ -1,5 +1,2 @@ #include "DynamicQuery.h" -PM_DYNAMIC_QUERY::PM_DYNAMIC_QUERY() -{ -} diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h index 76a9af9b..d547542e 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h @@ -2,6 +2,7 @@ #include #include #include +#include #include "../PresentMonAPI2/PresentMonAPI.h" #include "../ControlLib/CpuTelemetryInfo.h" #include "../ControlLib/PresentMonPowerTelemetry.h" @@ -23,11 +24,33 @@ namespace pmon::ipc class TelemetryMap; } -struct PM_DYNAMIC_QUERY +namespace pmon::mid::todo { -public: - PM_DYNAMIC_QUERY(); + struct PM_DYNAMIC_QUERY + { + public: + PM_DYNAMIC_QUERY(); + + private: + }; +} -private: +// TODO: legacy: to be deleted +struct PM_DYNAMIC_QUERY +{ + std::vector elements; + size_t GetBlobSize() const + { + return elements.back().dataOffset + elements.back().dataSize; + } + // Data used to track what should be accumulated + bool accumFpsData = false; + std::bitset(GpuTelemetryCapBits::gpu_telemetry_count)> accumGpuBits; + std::bitset(CpuTelemetryCapBits::cpu_telemetry_count)> accumCpuBits; + // Data used to calculate the requested metrics + double windowSizeMs = 0; + double metricOffsetMs = 0.; + size_t queryCacheSize = 0; + std::optional cachedGpuInfoIndex; }; diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp index bf5eb429..8c6f124c 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp +++ b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp @@ -1,12 +1,283 @@ #include "DynamicStat.h" +#include "DynamicQueryWindow.h" +#include "../CommonUtilities/Exception.h" +#include "../Interprocess/source/PmStatusError.h" +#include +#include namespace ipc = pmon::ipc; namespace util = pmon::util; namespace pmon::mid { + namespace detail + { + template + class DynamicStatBase_ : public DynamicStat + { + public: + void AddSample(T) override + { + ThrowMalformed_("DynamicStat::AddSample unsupported for this stat"); + } + uint64_t GetSamplePoint(const DynamicQueryWindow& win) const override + { + ThrowMalformed_("DynamicStat::GetSamplePoint unsupported for this stat"); + return 0; + } + void SetSampledValue(T) override + { + ThrowMalformed_("DynamicStat::SetSampledValue unsupported for this stat"); + } + void InputSortedSamples(std::span) override + { + ThrowMalformed_("DynamicStat::InputSortedSamples unsupported for this stat"); + } + protected: + DynamicStatBase_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes) + : inType_{ inType }, + outType_{ outType }, + offsetBytes_{ offsetBytes } + {} + void ThrowMalformed_(const char* msg) const + { + throw util::Except(PM_STATUS_QUERY_MALFORMED, msg); + } + PM_DATA_TYPE inType_ = PM_DATA_TYPE_DOUBLE; + PM_DATA_TYPE outType_ = PM_DATA_TYPE_DOUBLE; + size_t offsetBytes_ = 0; + }; + + template + class DynamicStatAverage_ : public DynamicStatBase_ + { + public: + DynamicStatAverage_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, bool skipZero) + : DynamicStatBase_{ inType, outType, offsetBytes }, + skipZero_{ skipZero } + { + } + bool NeedsUpdate() const override { return true; } + bool NeedsPointSample() const override { return false; } + bool NeedsSortedWindow() const override { return false; } + void AddSample(T val) override + { + if (skipZero_ && val == (T)0) { + return; + } + sum_ += (double)val; + ++count_; + } + void GatherToBlob(uint8_t* pBase) const override + { + if (this->outType_ != PM_DATA_TYPE_DOUBLE) { + this->ThrowMalformed_("DynamicStat average expects double output value"); + } + double avg = 0.0; + if (count_ > 0) { + avg = sum_ / (double)count_; + } + auto* pTarget = pBase + this->offsetBytes_; + *reinterpret_cast(pTarget) = avg; + } + private: + bool skipZero_ = false; + double sum_ = 0.0; + size_t count_ = 0; + }; + + template + class DynamicStatPercentile_ : public DynamicStatBase_ + { + public: + DynamicStatPercentile_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, double percentile) + : DynamicStatBase_{ inType, outType, offsetBytes }, + percentile_{ percentile } + { + } + bool NeedsUpdate() const override { return true; } + bool NeedsPointSample() const override { return false; } + bool NeedsSortedWindow() const override { return true; } + void InputSortedSamples(std::span sortedSamples) override + { + if (sortedSamples.empty()) { + value_ = 0.0; + hasValue_ = false; + return; + } + + const double clamped = std::clamp(percentile_, 0.0, 1.0); + const size_t last = sortedSamples.size() - 1; + const double position = clamped * (double)last; + const size_t index = (size_t)(position + 0.5); + value_ = (double)sortedSamples[index]; + hasValue_ = true; + } + void GatherToBlob(uint8_t* pBase) const override + { + auto* pTarget = pBase + this->offsetBytes_; + switch (this->outType_) { + case PM_DATA_TYPE_DOUBLE: + *reinterpret_cast(pTarget) = hasValue_ ? value_ : 0.0; + break; + case PM_DATA_TYPE_INT32: + case PM_DATA_TYPE_ENUM: + *reinterpret_cast(pTarget) = hasValue_ ? (int32_t)value_ : 0; + break; + case PM_DATA_TYPE_BOOL: + *reinterpret_cast(pTarget) = hasValue_ ? value_ != 0.0 : false; + break; + default: + this->ThrowMalformed_("DynamicStat percentile expects double, int32, enum, or bool output value"); + } + } + private: + double percentile_ = 0.0; + double value_ = 0.0; + bool hasValue_ = false; + }; + + template + class DynamicStatMinMax_ : public DynamicStatBase_ + { + public: + DynamicStatMinMax_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, bool isMax) + : DynamicStatBase_{ inType, outType, offsetBytes }, + isMax_{ isMax } + { + } + bool NeedsUpdate() const override { return true; } + bool NeedsPointSample() const override { return false; } + bool NeedsSortedWindow() const override { return false; } + void AddSample(T val) override + { + const double doubleVal = (double)val; + if (!hasValue_) { + value_ = doubleVal; + hasValue_ = true; + return; + } + if (isMax_) { + if (doubleVal > value_) { + value_ = doubleVal; + } + } + else { + if (doubleVal < value_) { + value_ = doubleVal; + } + } + } + void GatherToBlob(uint8_t* pBase) const override + { + auto* pTarget = pBase + this->offsetBytes_; + switch (this->outType_) { + case PM_DATA_TYPE_DOUBLE: + *reinterpret_cast(pTarget) = hasValue_ ? value_ : 0.0; + break; + case PM_DATA_TYPE_INT32: + case PM_DATA_TYPE_ENUM: + *reinterpret_cast(pTarget) = hasValue_ ? (int32_t)value_ : 0; + break; + case PM_DATA_TYPE_BOOL: + *reinterpret_cast(pTarget) = hasValue_ ? value_ != 0.0 : false; + break; + default: + this->ThrowMalformed_("DynamicStat min/max expects double, int32, enum, or bool output value"); + } + } + private: + bool isMax_ = false; + double value_ = 0.0; + bool hasValue_ = false; + }; + + template + class DynamicStatPoint_ : public DynamicStatBase_ + { + public: + DynamicStatPoint_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, PM_STAT mode) + : DynamicStatBase_{ inType, outType, offsetBytes }, + mode_{ mode } + { + } + bool NeedsUpdate() const override { return false; } + bool NeedsPointSample() const override { return true; } + bool NeedsSortedWindow() const override { return false; } + uint64_t GetSamplePoint(const DynamicQueryWindow& win) const override + { + switch (mode_) { + case PM_STAT_OLDEST_POINT: + return win.oldest; + case PM_STAT_NEWEST_POINT: + return win.newest; + case PM_STAT_MID_POINT: + return win.oldest + (win.newest - win.oldest) / 2; + default: + this->ThrowMalformed_("DynamicStat point expects point mode"); + return win.newest; + } + } + void GatherToBlob(uint8_t* pBase) const override + { + auto* pTarget = pBase + this->offsetBytes_; + const double doubleVal = (double)value_; + switch (this->outType_) { + case PM_DATA_TYPE_DOUBLE: + *reinterpret_cast(pTarget) = hasValue_ ? doubleVal : 0.0; + break; + case PM_DATA_TYPE_INT32: + case PM_DATA_TYPE_ENUM: + *reinterpret_cast(pTarget) = hasValue_ ? (int32_t)doubleVal : 0; + break; + case PM_DATA_TYPE_BOOL: + *reinterpret_cast(pTarget) = hasValue_ ? doubleVal != 0.0 : false; + break; + default: + this->ThrowMalformed_("DynamicStat point expects double, int32, enum, or bool output value"); + } + } + private: + void SetSampledValue(T val) override + { + value_ = val; + hasValue_ = true; + } + PM_STAT mode_ = PM_STAT_MID_POINT; + bool hasValue_ = false; + T value_ = (T)0; + }; + } + namespace { + template + void ValidateInputType_(PM_DATA_TYPE inType) + { + if constexpr (std::is_same_v) { + if (inType != PM_DATA_TYPE_DOUBLE) { + throw util::Except(PM_STATUS_QUERY_MALFORMED, + "Unsupported dynamic stat input data type"); + } + } + else if constexpr (std::is_same_v) { + if (inType != PM_DATA_TYPE_INT32 && inType != PM_DATA_TYPE_ENUM) { + throw util::Except(PM_STATUS_QUERY_MALFORMED, + "Unsupported dynamic stat input data type"); + } + } + else if constexpr (std::is_same_v) { + if (inType != PM_DATA_TYPE_BOOL) { + throw util::Except(PM_STATUS_QUERY_MALFORMED, + "Unsupported dynamic stat input data type"); + } + } + else { + throw util::Except(PM_STATUS_QUERY_MALFORMED, + "Unsupported dynamic stat input data type"); + } + } + bool IsAvgStat_(PM_STAT stat) { return stat == PM_STAT_AVG || stat == PM_STAT_NON_ZERO_AVG; @@ -85,19 +356,14 @@ namespace pmon::mid } } - std::unique_ptr MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes) + template + std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes) { - switch (inType) { - case PM_DATA_TYPE_DOUBLE: - return MakeDynamicStatTyped_(stat, inType, outType, offsetBytes); - case PM_DATA_TYPE_INT32: - case PM_DATA_TYPE_ENUM: - return MakeDynamicStatTyped_(stat, inType, outType, offsetBytes); - case PM_DATA_TYPE_BOOL: - return MakeDynamicStatTyped_(stat, inType, outType, offsetBytes); - default: - throw util::Except(PM_STATUS_QUERY_MALFORMED, - "Unsupported dynamic stat input data type"); - } + ValidateInputType_(inType); + return MakeDynamicStatTyped_(stat, inType, outType, offsetBytes); } + + template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); + template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); + template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); } diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicStat.h b/IntelPresentMon/PresentMonMiddleware/DynamicStat.h index 72472ab6..891b2f7e 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicStat.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicStat.h @@ -1,303 +1,35 @@ #pragma once -#include #include #include #include #include -#include "DynamicQueryWindow.h" -#include "../CommonUtilities/Exception.h" -#include "../Interprocess/source/PmStatusError.h" #include "../PresentMonAPI2/PresentMonAPI.h" namespace pmon::mid { - class DynamicStatBase + struct DynamicQueryWindow; + + template + class DynamicStat { public: - virtual ~DynamicStatBase() = default; + virtual ~DynamicStat() = default; virtual bool NeedsUpdate() const = 0; virtual bool NeedsPointSample() const = 0; virtual bool NeedsSortedWindow() const = 0; - virtual void AddSample(double) - { - ThrowMalformed_("DynamicStat::AddSample unsupported for this stat"); - } - virtual void AddSample(int32_t) - { - ThrowMalformed_("DynamicStat::AddSample unsupported for this stat"); - } - virtual void AddSample(bool) - { - ThrowMalformed_("DynamicStat::AddSample unsupported for this stat"); - } - virtual uint64_t GetSamplePoint(const DynamicQueryWindow& win) const - { - ThrowMalformed_("DynamicStat::GetSamplePoint unsupported for this stat"); - return 0; - } - virtual void SetSampledValue(double) - { - ThrowMalformed_("DynamicStat::SetSampledValue unsupported for this stat"); - } - virtual void SetSampledValue(int32_t) - { - ThrowMalformed_("DynamicStat::SetSampledValue unsupported for this stat"); - } - virtual void SetSampledValue(bool) - { - ThrowMalformed_("DynamicStat::SetSampledValue unsupported for this stat"); - } - virtual void InputSortedSamples(std::span) - { - ThrowMalformed_("DynamicStat::InputSortedSamples unsupported for this stat"); - } - virtual void InputSortedSamples(std::span) - { - ThrowMalformed_("DynamicStat::InputSortedSamples unsupported for this stat"); - } - virtual void InputSortedSamples(std::span) - { - ThrowMalformed_("DynamicStat::InputSortedSamples unsupported for this stat"); - } - virtual void Populate(uint8_t* pBase) const - { - ThrowMalformed_("DynamicStat::Populate unsupported for this stat"); - } + virtual void GatherToBlob(uint8_t* pBlobBase) const = 0; + virtual void AddSample(T) = 0; + virtual uint64_t GetSamplePoint(const DynamicQueryWindow& win) const = 0; + virtual void SetSampledValue(T) = 0; + virtual void InputSortedSamples(std::span) = 0; protected: - DynamicStatBase(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes) - : inType_{ inType }, - outType_{ outType }, - offsetBytes_{ offsetBytes } - { - } - void ThrowMalformed_(const char* msg) const - { - throw pmon::util::Except(PM_STATUS_QUERY_MALFORMED, msg); - } - PM_DATA_TYPE inType_ = PM_DATA_TYPE_DOUBLE; - PM_DATA_TYPE outType_ = PM_DATA_TYPE_DOUBLE; - size_t offsetBytes_ = 0; + DynamicStat() = default; }; template - class DynamicStat : public DynamicStatBase - { - public: - void SetSampledValue(T val) override - { - SetSampledValue_(val); - } - protected: - DynamicStat(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes) - : DynamicStatBase(inType, outType, offsetBytes) - { - } - virtual void SetSampledValue_(T) - { - ThrowMalformed_("DynamicStat::SetSampledValue unsupported for this stat"); - } - }; - - namespace detail - { - template - class DynamicStatAverage_ : public DynamicStat - { - public: - DynamicStatAverage_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, bool skipZero) - : DynamicStat{ inType, outType, offsetBytes }, - skipZero_{ skipZero } - { - } - bool NeedsUpdate() const override { return true; } - bool NeedsPointSample() const override { return false; } - bool NeedsSortedWindow() const override { return false; } - void AddSample(T val) override - { - if (skipZero_ && val == (T)0) { - return; - } - sum_ += (double)val; - ++count_; - } - void Populate(uint8_t* pBase) const override - { - if (this->outType_ != PM_DATA_TYPE_DOUBLE) { - this->ThrowMalformed_("DynamicStat average expects double output value"); - } - double avg = 0.0; - if (count_ > 0) { - avg = sum_ / (double)count_; - } - auto* pTarget = pBase + this->offsetBytes_; - *reinterpret_cast(pTarget) = avg; - } - private: - bool skipZero_ = false; - double sum_ = 0.0; - size_t count_ = 0; - }; - - template - class DynamicStatPercentile_ : public DynamicStat - { - public: - DynamicStatPercentile_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, double percentile) - : DynamicStat{ inType, outType, offsetBytes }, - percentile_{ percentile } - { - } - bool NeedsUpdate() const override { return true; } - bool NeedsPointSample() const override { return false; } - bool NeedsSortedWindow() const override { return true; } - void InputSortedSamples(std::span sortedSamples) override - { - if (sortedSamples.empty()) { - value_ = 0.0; - hasValue_ = false; - return; - } - - const double clamped = std::clamp(percentile_, 0.0, 1.0); - const size_t last = sortedSamples.size() - 1; - const double position = clamped * (double)last; - const size_t index = (size_t)(position + 0.5); - value_ = (double)sortedSamples[index]; - hasValue_ = true; - } - void Populate(uint8_t* pBase) const override - { - auto* pTarget = pBase + this->offsetBytes_; - switch (this->outType_) { - case PM_DATA_TYPE_DOUBLE: - *reinterpret_cast(pTarget) = hasValue_ ? value_ : 0.0; - break; - case PM_DATA_TYPE_INT32: - case PM_DATA_TYPE_ENUM: - *reinterpret_cast(pTarget) = hasValue_ ? (int32_t)value_ : 0; - break; - case PM_DATA_TYPE_BOOL: - *reinterpret_cast(pTarget) = hasValue_ ? value_ != 0.0 : false; - break; - default: - this->ThrowMalformed_("DynamicStat percentile expects double, int32, enum, or bool output value"); - } - } - private: - double percentile_ = 0.0; - double value_ = 0.0; - bool hasValue_ = false; - }; - - template - class DynamicStatMinMax_ : public DynamicStat - { - public: - DynamicStatMinMax_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, bool isMax) - : DynamicStat{ inType, outType, offsetBytes }, - isMax_{ isMax } - { - } - bool NeedsUpdate() const override { return true; } - bool NeedsPointSample() const override { return false; } - bool NeedsSortedWindow() const override { return false; } - void AddSample(T val) override - { - const double doubleVal = (double)val; - if (!hasValue_) { - value_ = doubleVal; - hasValue_ = true; - return; - } - if (isMax_) { - if (doubleVal > value_) { - value_ = doubleVal; - } - } - else { - if (doubleVal < value_) { - value_ = doubleVal; - } - } - } - void Populate(uint8_t* pBase) const override - { - auto* pTarget = pBase + this->offsetBytes_; - switch (this->outType_) { - case PM_DATA_TYPE_DOUBLE: - *reinterpret_cast(pTarget) = hasValue_ ? value_ : 0.0; - break; - case PM_DATA_TYPE_INT32: - case PM_DATA_TYPE_ENUM: - *reinterpret_cast(pTarget) = hasValue_ ? (int32_t)value_ : 0; - break; - case PM_DATA_TYPE_BOOL: - *reinterpret_cast(pTarget) = hasValue_ ? value_ != 0.0 : false; - break; - default: - this->ThrowMalformed_("DynamicStat min/max expects double, int32, enum, or bool output value"); - } - } - private: - bool isMax_ = false; - double value_ = 0.0; - bool hasValue_ = false; - }; - - template - class DynamicStatPoint_ : public DynamicStat - { - public: - DynamicStatPoint_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, PM_STAT mode) - : DynamicStat{ inType, outType, offsetBytes }, - mode_{ mode } - { - } - bool NeedsUpdate() const override { return false; } - bool NeedsPointSample() const override { return true; } - bool NeedsSortedWindow() const override { return false; } - uint64_t GetSamplePoint(const DynamicQueryWindow& win) const override - { - switch (mode_) { - case PM_STAT_OLDEST_POINT: - return win.oldest; - case PM_STAT_NEWEST_POINT: - return win.newest; - case PM_STAT_MID_POINT: - return win.oldest + (win.newest - win.oldest) / 2; - } - return win.newest; - } - void Populate(uint8_t* pBase) const override - { - auto* pTarget = pBase + this->offsetBytes_; - const double doubleVal = (double)value_; - switch (this->outType_) { - case PM_DATA_TYPE_DOUBLE: - *reinterpret_cast(pTarget) = hasValue_ ? doubleVal : 0.0; - break; - case PM_DATA_TYPE_INT32: - case PM_DATA_TYPE_ENUM: - *reinterpret_cast(pTarget) = hasValue_ ? (int32_t)doubleVal : 0; - break; - case PM_DATA_TYPE_BOOL: - *reinterpret_cast(pTarget) = hasValue_ ? doubleVal != 0.0 : false; - break; - default: - this->ThrowMalformed_("DynamicStat point expects double, int32, enum, or bool output value"); - } - } - private: - void SetSampledValue_(T val) override - { - value_ = val; - hasValue_ = true; - } - PM_STAT mode_ = PM_STAT_MID_POINT; - bool hasValue_ = false; - T value_ = (T)0; - }; - } + std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t blobOffsetBytes); - std::unique_ptr MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); + extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); + extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); + extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); } diff --git a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj index 8fd8f91f..8e5e5bd9 100644 --- a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj +++ b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj @@ -30,7 +30,6 @@ /bigobj %(AdditionalOptions) - diff --git a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters index 02ef3317..8d86e4b4 100644 --- a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters +++ b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters @@ -59,9 +59,6 @@ Source Files - - Source Files - Source Files From 956295c281c487513f49d656951fb214beed3bba Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 23 Jan 2026 15:01:12 +0900 Subject: [PATCH 145/205] move framemetric member mapping to central location --- IntelPresentMon/CommonUtilities/Meta.h | 36 +++++ .../mc/FrameMetricsMemberMap.h | 46 ++++++ .../PresentMonMiddleware/FrameEventQuery.cpp | 133 +++--------------- 3 files changed, 102 insertions(+), 113 deletions(-) create mode 100644 IntelPresentMon/CommonUtilities/mc/FrameMetricsMemberMap.h diff --git a/IntelPresentMon/CommonUtilities/Meta.h b/IntelPresentMon/CommonUtilities/Meta.h index 13ff7742..8c484f47 100644 --- a/IntelPresentMon/CommonUtilities/Meta.h +++ b/IntelPresentMon/CommonUtilities/Meta.h @@ -1,4 +1,7 @@ #pragma once +#include +#include +#include #include #include @@ -10,6 +13,13 @@ namespace pmon::util template inline constexpr bool DependentFalse = DependentFalseT::value; + template + struct IsStdOptionalT : std::false_type {}; + template + struct IsStdOptionalT> : std::true_type {}; + template + inline constexpr bool IsStdOptional = IsStdOptionalT>::value; + // deconstruct member pointer into the object type the pointer works with and the member type template struct MemberPointerInfo; template @@ -17,6 +27,30 @@ namespace pmon::util using StructType = S; using MemberType = M; }; + template + struct MemberPointerInfo { + using StructType = S; + using MemberType = M; + }; + template + struct MemberPointerInfo { + using StructType = S; + using MemberType = M; + }; + template + struct MemberPointerInfo { + using StructType = S; + using MemberType = M; + }; + + template + std::size_t MemberPointerOffset(M S::*memberPtr) + { + const S sample{}; + const auto* base = reinterpret_cast(&sample); + const auto* member = reinterpret_cast(&(sample.*memberPtr)); + return static_cast(member - base); + } // get the type of the elements in any iterable type (typically containers) template @@ -92,6 +126,8 @@ namespace pmon::util } } + // compile-time static to runtime dynamic TMP bridges (on enum) + template MaxValue, typename Functor> constexpr void ForEachEnumValue(Functor&& func) { diff --git a/IntelPresentMon/CommonUtilities/mc/FrameMetricsMemberMap.h b/IntelPresentMon/CommonUtilities/mc/FrameMetricsMemberMap.h new file mode 100644 index 00000000..f50a21fe --- /dev/null +++ b/IntelPresentMon/CommonUtilities/mc/FrameMetricsMemberMap.h @@ -0,0 +1,46 @@ +// Copyright (C) 2026 Intel Corporation +// SPDX-License-Identifier: MIT +#pragma once +#include "MetricsTypes.h" +#include "../PresentMonAPI2/PresentMonAPI.h" + +namespace pmon::util::metrics +{ + templatestruct FrameMetricMember{}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::allowsTearing;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::runtime;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::presentMode;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::presentFlags;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::syncInterval;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::swapChainAddress;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::presentStartQpc;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::presentStartMs;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::cpuStartQpc;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::cpuStartMs;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msBetweenPresents;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msInPresentApi;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msUntilRenderComplete;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msBetweenDisplayChange;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msUntilDisplayed;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msDisplayedTime;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msDisplayLatency;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msBetweenSimStarts;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msPcLatency;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msCPUBusy;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msCPUWait;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msCPUTime;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msCPUTime;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msGPULatency;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msGPUTime;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msGPUBusy;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msGPUWait;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::isDroppedFrame;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msAnimationError;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msAnimationTime;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msClickToPhotonLatency;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msAllInputPhotonLatency;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msInstrumentedLatency;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msFlipDelay;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::frameType;}; + templateinline constexpr bool HasFrameMetricMember=requires{FrameMetricMember::member;}; +} diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index 1faed429..992aadb1 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -9,11 +9,14 @@ #include "../Interprocess/source/PmStatusError.h" #include "../CommonUtilities/log/Log.h" #include "../CommonUtilities/Exception.h" +#include "../CommonUtilities/Meta.h" #include "../CommonUtilities/Memory.h" #include "../CommonUtilities/Qpc.h" +#include "../CommonUtilities/mc/FrameMetricsMemberMap.h" #include #include #include +#include namespace ipc = pmon::ipc; namespace util = pmon::util; @@ -196,119 +199,23 @@ PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherComma .arrayIdx = q.arrayIndex, }; - switch (q.metric) { - case PM_METRIC_ALLOWS_TEARING: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, allowsTearing); - break; - case PM_METRIC_PRESENT_RUNTIME: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, runtime); - break; - case PM_METRIC_PRESENT_MODE: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, presentMode); - break; - case PM_METRIC_PRESENT_FLAGS: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, presentFlags); - break; - case PM_METRIC_SYNC_INTERVAL: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, syncInterval); - break; - case PM_METRIC_SWAP_CHAIN_ADDRESS: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, swapChainAddress); - break; - case PM_METRIC_PRESENT_START_QPC: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, presentStartQpc); - break; - case PM_METRIC_PRESENT_START_TIME: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, presentStartMs); - break; - case PM_METRIC_CPU_START_QPC: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, cpuStartQpc); - break; - case PM_METRIC_CPU_START_TIME: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, cpuStartMs); - break; - case PM_METRIC_BETWEEN_PRESENTS: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msBetweenPresents); - break; - case PM_METRIC_IN_PRESENT_API: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msInPresentApi); - break; - case PM_METRIC_RENDER_PRESENT_LATENCY: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msUntilRenderComplete); - break; - case PM_METRIC_BETWEEN_DISPLAY_CHANGE: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msBetweenDisplayChange); - break; - case PM_METRIC_UNTIL_DISPLAYED: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msUntilDisplayed); - break; - case PM_METRIC_DISPLAYED_TIME: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msDisplayedTime); - break; - case PM_METRIC_DISPLAY_LATENCY: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msDisplayLatency); - break; - case PM_METRIC_BETWEEN_SIMULATION_START: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msBetweenSimStarts); - cmd.isOptional = true; - break; - case PM_METRIC_PC_LATENCY: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msPcLatency); - cmd.isOptional = true; - break; - case PM_METRIC_CPU_BUSY: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msCPUBusy); - break; - case PM_METRIC_CPU_WAIT: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msCPUWait); - break; - case PM_METRIC_CPU_FRAME_TIME: - case PM_METRIC_BETWEEN_APP_START: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msCPUTime); - break; - case PM_METRIC_GPU_LATENCY: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msGPULatency); - break; - case PM_METRIC_GPU_TIME: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msGPUTime); - break; - case PM_METRIC_GPU_BUSY: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msGPUBusy); - break; - case PM_METRIC_GPU_WAIT: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msGPUWait); - break; - case PM_METRIC_DROPPED_FRAMES: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, isDroppedFrame); - break; - case PM_METRIC_ANIMATION_ERROR: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msAnimationError); - cmd.isOptional = true; - break; - case PM_METRIC_ANIMATION_TIME: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msAnimationTime); - cmd.isOptional = true; - break; - case PM_METRIC_CLICK_TO_PHOTON_LATENCY: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msClickToPhotonLatency); - cmd.isOptional = true; - break; - case PM_METRIC_ALL_INPUT_TO_PHOTON_LATENCY: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msAllInputPhotonLatency); - cmd.isOptional = true; - break; - case PM_METRIC_INSTRUMENTED_LATENCY: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msInstrumentedLatency); - cmd.isOptional = true; - break; - case PM_METRIC_FLIP_DELAY: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, msFlipDelay); - cmd.isOptional = true; - break; - case PM_METRIC_FRAME_TYPE: - cmd.frameMetricsOffset = offsetof(util::metrics::FrameMetrics, frameType); - break; - default: + using MetricUnderlying = std::underlying_type_t; + constexpr MetricUnderlying kMaxMetricUnderlying = MetricUnderlying(PM_METRIC_PROCESS_ID) + 1; + const bool mapped = util::DispatchEnumValue( + q.metric, + [&]() -> bool { + if constexpr (util::metrics::HasFrameMetricMember) { + constexpr auto memberPtr = util::metrics::FrameMetricMember::member; + using MemberType = typename util::MemberPointerInfo::MemberType; + static const uint32_t memberOffset = uint32_t(util::MemberPointerOffset(memberPtr)); + cmd.frameMetricsOffset = memberOffset; + cmd.isOptional = util::IsStdOptional; + return true; + } + return false; + }, + false); + if (!mapped) { pmlog_error("Unexpected frame metric in frame query") .pmwatch(metricView.Introspect().GetSymbol()) .pmwatch(metricView.IntrospectType().GetSymbol()) From 5f0d5c581bd667d9114359d41a9da91a9ca3d8c4 Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 23 Jan 2026 15:59:39 +0900 Subject: [PATCH 146/205] add count for PM_METRIC --- .../Interprocess/source/IntrospectionCapsLookup.h | 2 ++ .../Interprocess/source/MetricCapabilitiesShim.cpp | 6 ++---- IntelPresentMon/Interprocess/source/metadata/MetricList.h | 1 + IntelPresentMon/PresentMonAPI2/PresentMonAPI.h | 1 + IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp | 4 +--- IntelPresentMon/metrics.csv | 2 ++ 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h b/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h index fd87fcbe..380d82f4 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h +++ b/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h @@ -9,6 +9,8 @@ namespace pmon::ipc::intro { // mapping of caps to metrics template struct IntrospectionCapsLookup { using Universal = std::true_type; }; + // sentinel metric used for enumeration only (no availability) + template<> struct IntrospectionCapsLookup {}; // GPU caps template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_power; }; template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_voltage; }; diff --git a/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp b/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp index c06c99c7..47757a81 100644 --- a/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp +++ b/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp @@ -8,10 +8,8 @@ namespace pmon::ipc::intro namespace detail { using MetricEnum = PM_METRIC; - using MetricUnderlying = std::underlying_type_t; - - // Probe underlying values in [0, MaxMetricUnderlying) - constexpr MetricUnderlying MaxMetricUnderlying = 256; + // Probe underlying values in [0, COUNT) + constexpr auto MaxMetricUnderlying = int(PM_METRIC_COUNT); // xxxCapBits is std::bitset template diff --git a/IntelPresentMon/Interprocess/source/metadata/MetricList.h b/IntelPresentMon/Interprocess/source/metadata/MetricList.h index c6ef4915..11bf921b 100644 --- a/IntelPresentMon/Interprocess/source/metadata/MetricList.h +++ b/IntelPresentMon/Interprocess/source/metadata/MetricList.h @@ -95,3 +95,4 @@ X_(PM_METRIC_PRESENTED_FRAME_TIME, PM_METRIC_TYPE_DYNAMIC, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_BETWEEN_APP_START, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_FLIP_DELAY, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ + X_(PM_METRIC_COUNT, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_VOID, PM_ENUM_NULL_ENUM, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ diff --git a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h index 5617ba26..e11e7dbf 100644 --- a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h +++ b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h @@ -138,6 +138,7 @@ extern "C" { PM_METRIC_PRESENTED_FRAME_TIME, PM_METRIC_FLIP_DELAY, PM_METRIC_PROCESS_ID, + PM_METRIC_COUNT, // sentry to mark end of metric list; not an actual query metric }; enum PM_METRIC_TYPE diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index 992aadb1..75bfef6d 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -199,9 +199,7 @@ PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherComma .arrayIdx = q.arrayIndex, }; - using MetricUnderlying = std::underlying_type_t; - constexpr MetricUnderlying kMaxMetricUnderlying = MetricUnderlying(PM_METRIC_PROCESS_ID) + 1; - const bool mapped = util::DispatchEnumValue( + const bool mapped = util::DispatchEnumValue( q.metric, [&]() -> bool { if constexpr (util::metrics::HasFrameMetricMember) { diff --git a/IntelPresentMon/metrics.csv b/IntelPresentMon/metrics.csv index 7b1cc0b3..2bc6ac88 100644 --- a/IntelPresentMon/metrics.csv +++ b/IntelPresentMon/metrics.csv @@ -104,3 +104,5 @@ PM_METRIC_DISPLAYED_FRAME_TIME,1,FrameTime-Display,"The time between when the pr PM_METRIC_PRESENTED_FRAME_TIME,1,FrameTime-Presents,"The time between this Present call and the previous one, in milliseconds." PM_METRIC_BETWEEN_APP_START,,Ms Between App Start,"How long it took from the start of this frame until the CPU started working on the next frame, in milliseconds." PM_METRIC_FLIP_DELAY,,Ms Flip Delay,"Delay added to when the Present() was displayed." +,,, +PM_METRIC_COUNT,1,Metric Count,"Sentinel value for enumeration/reflection only. Not an actual metric for use in queries." From 0061a8e398064fe018b248f7456a82d7adc46660 Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 23 Jan 2026 17:14:33 +0900 Subject: [PATCH 147/205] RingMetricBinding poll --- .../PresentMonMiddleware/DynamicMetric.h | 1 + .../HistoryRingTraverser.cpp | 12 ----- .../HistoryRingTraverser.h | 18 ------- .../PresentMonMiddleware.vcxproj | 4 +- .../PresentMonMiddleware.vcxproj.filters | 4 +- .../RingMetricBinding.cpp | 5 ++ .../PresentMonMiddleware/RingMetricBinding.h | 49 +++++++++++++++++++ 7 files changed, 59 insertions(+), 34 deletions(-) delete mode 100644 IntelPresentMon/PresentMonMiddleware/HistoryRingTraverser.cpp delete mode 100644 IntelPresentMon/PresentMonMiddleware/HistoryRingTraverser.h create mode 100644 IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp create mode 100644 IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h index 84620cb2..15ce9604 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h @@ -38,6 +38,7 @@ namespace pmon::mid virtual void GatherToBlob(uint8_t* pBlobBase) const = 0; virtual uint32_t AddStat(PM_STAT stat, uint32_t blobByteOffset, const pmapi::intro::Root& intro) = 0; virtual void FinalizeStats() = 0; + virtual bool NeedsFullTraversal() const = 0; }; template diff --git a/IntelPresentMon/PresentMonMiddleware/HistoryRingTraverser.cpp b/IntelPresentMon/PresentMonMiddleware/HistoryRingTraverser.cpp deleted file mode 100644 index 44f82630..00000000 --- a/IntelPresentMon/PresentMonMiddleware/HistoryRingTraverser.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "HistoryRingTraverser.h" - -namespace pmon::mid -{ - HistoryRingTraverser::HistoryRingTraverser() = default; - HistoryRingTraverser::~HistoryRingTraverser() = default; - - size_t HistoryRingTraverser::GetCursor() const - { - return cursor_; - } -} diff --git a/IntelPresentMon/PresentMonMiddleware/HistoryRingTraverser.h b/IntelPresentMon/PresentMonMiddleware/HistoryRingTraverser.h deleted file mode 100644 index 4670bea5..00000000 --- a/IntelPresentMon/PresentMonMiddleware/HistoryRingTraverser.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -#include - -namespace pmon::mid -{ - class HistoryRingTraverser - { - public: - HistoryRingTraverser(); - ~HistoryRingTraverser(); - - size_t GetCursor() const; - - private: - size_t cursor_ = 0; - size_t size_ = 0; - }; -} diff --git a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj index 8e5e5bd9..b843a59f 100644 --- a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj +++ b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj @@ -21,7 +21,7 @@ - + @@ -33,7 +33,7 @@ - + diff --git a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters index 8d86e4b4..2c16949c 100644 --- a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters +++ b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters @@ -33,7 +33,7 @@ Header Files - + Header Files @@ -71,7 +71,7 @@ Source Files - + Source Files diff --git a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp new file mode 100644 index 00000000..29edb27d --- /dev/null +++ b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp @@ -0,0 +1,5 @@ +#include "RingMetricBinding.h" + +namespace pmon::mid +{ +} diff --git a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h new file mode 100644 index 00000000..1ea2fd7f --- /dev/null +++ b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h @@ -0,0 +1,49 @@ +#pragma once +#include +#include +#include +#include +#include "DynamicMetric.h" +#include "DynamicQueryWindow.h" +#include "../Interprocess/source/HistoryRing.h" + +namespace pmon::mid +{ + // container to bind and type erase a single metric ring to one or more metrics + // (telemetry rings are always 1 metric per ring, but the frame ring serves many metrics) + template + class RingMetricBinding + { + public: + RingMetricBinding(); + ~RingMetricBinding(); + void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase) const + { + // traverse ring once to handle all metrics + ring_.ForEachInTimestampRange(window.oldest, window.newest, [this](const S& sample) { + for (auto pMetric : needsFullTraversalMetricPtrs_) { + pMetric->AddSample(sample); + } + }); + for (const std::unique_ptr>& pMetric : metricPtrs_) { + const auto& requestedPoints = pMetric->GetRequestedSamplePoints(window); + if (!requestedPoints.empty()) { + sampledPtrs_.clear(); + if (!ring_.Empty()) { + for (auto point : requestedPoints) { + sampledPtrs_.push_back(&ring_.Nearest(point)); + } + pMetric->InputRequestedPointSamples(sampledPtrs_); + } + // if ring is empty, do not input point samples and the stats can fallback to last value or null/zero + } + pMetric->GatherToBlob(pBlobBase); + } + } + private: + std::vector>> metricPtrs_; + std::vector*> needsFullTraversalMetricPtrs_; + ipc::HistoryRing& ring_; + std::vector sampledPtrs_; + }; +} From f0cd10008a1468d1b142fef7020050c0254bb166 Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 26 Jan 2026 06:28:38 +0900 Subject: [PATCH 148/205] dynamic metric factory --- .../PresentMonMiddleware/DynamicMetric.h | 42 +++++++++++++++---- .../PresentMonMiddleware/RingMetricBinding.h | 33 ++++++++++++++- 2 files changed, 66 insertions(+), 9 deletions(-) diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h index 15ce9604..2c273092 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h @@ -10,6 +10,9 @@ #include "DynamicQueryWindow.h" #include "DynamicStat.h" #include "../CommonUtilities/Exception.h" +#include "../CommonUtilities/Meta.h" +#include "../CommonUtilities/Memory.h" +#include "../CommonUtilities/mc/FrameMetricsMemberMap.h" #include "../Interprocess/source/IntrospectionHelpers.h" #include "../Interprocess/source/PmStatusError.h" #include "../PresentMonAPIWrapperCommon/Introspection.h" @@ -36,7 +39,7 @@ namespace pmon::mid virtual const std::vector& GetRequestedSamplePoints(const DynamicQueryWindow& window) const = 0; virtual void InputRequestedPointSamples(const std::vector& samples) = 0; virtual void GatherToBlob(uint8_t* pBlobBase) const = 0; - virtual uint32_t AddStat(PM_STAT stat, uint32_t blobByteOffset, const pmapi::intro::Root& intro) = 0; + virtual void AddStat(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) = 0; virtual void FinalizeStats() = 0; virtual bool NeedsFullTraversal() const = 0; }; @@ -107,17 +110,20 @@ namespace pmon::mid } } - uint32_t AddStat(PM_STAT stat, uint32_t blobByteOffset, const pmapi::intro::Root& intro) override + void AddStat(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) override { const auto metricView = intro.FindMetric(metric_); - if (!IsStatSupported_(stat, metricView)) { + if (!IsStatSupported_(qel.stat, metricView)) { throw pmon::util::Except(PM_STATUS_QUERY_MALFORMED, "Dynamic metric stat not supported by metric."); } const auto inType = GetSampleType_(); - auto outType = SelectOutputType_(stat, metricView.GetDataTypeInfo().GetPolledType()); - auto statPtr = MakeDynamicStat(stat, inType, outType, blobByteOffset); + auto outType = SelectOutputType_(qel.stat, metricView.GetDataTypeInfo().GetPolledType()); + qel.dataSize = (uint32_t)ipc::intro::GetDataTypeSize(outType); + // adjust offset written in qel when padding is needed for type alignment + qel.dataOffset = util::PadToAlignment(qel.dataOffset, qel.dataSize); + auto statPtr = MakeDynamicStat(qel.stat, inType, outType, qel.dataOffset); auto* rawPtr = statPtr.get(); statPtrs_.push_back(std::move(statPtr)); @@ -130,8 +136,6 @@ namespace pmon::mid else if (!rawPtr->NeedsSortedWindow() && rawPtr->NeedsUpdate()) { needsUpdatePtrs_.push_back(rawPtr); } - - return (uint32_t)ipc::intro::GetDataTypeSize(outType); } void FinalizeStats() override @@ -188,4 +192,28 @@ namespace pmon::mid std::vector*> needsSortedWindowPtrs_; mutable std::vector requestedSamplePoints_; }; + + template + std::unique_ptr> MakeDynamicMetric(const PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) + { + return util::DispatchEnumValue( + qel.metric, + [&]() -> std::unique_ptr> { + if constexpr (util::metrics::HasFrameMetricMember) { + // frame data case + constexpr auto memberPtr = util::metrics::FrameMetricMember::member; + using MemberInfo = util::MemberPointerInfo; + using MemberType = typename MemberInfo::MemberType; + return std::make_unique>(Metric); + } + else { + // telemetry case + constexpr auto memberPtr = &S::value; + using MemberInfo = util::MemberPointerInfo; + using MemberType = typename MemberInfo::MemberType; + return std::make_unique>(Metric); + } + }, {} + ); + } } diff --git a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h index 1ea2fd7f..591c2024 100644 --- a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h +++ b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h @@ -3,21 +3,37 @@ #include #include #include +#include #include "DynamicMetric.h" #include "DynamicQueryWindow.h" +#include "../CommonUtilities/Exception.h" +#include "../CommonUtilities/Meta.h" +#include "../CommonUtilities/mc/FrameMetricsMemberMap.h" #include "../Interprocess/source/HistoryRing.h" +#include "../Interprocess/source/PmStatusError.h" namespace pmon::mid { + class RingMetricBindingBase + { + public: + RingMetricBindingBase() = default; + virtual ~RingMetricBindingBase() = default; + + virtual void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase) const = 0; + virtual void Finalize() = 0; + virtual void AddMetricStat(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) = 0; + }; + // container to bind and type erase a single metric ring to one or more metrics // (telemetry rings are always 1 metric per ring, but the frame ring serves many metrics) template - class RingMetricBinding + class RingMetricBinding : public RingMetricBindingBase { public: RingMetricBinding(); ~RingMetricBinding(); - void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase) const + void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase) const override { // traverse ring once to handle all metrics ring_.ForEachInTimestampRange(window.oldest, window.newest, [this](const S& sample) { @@ -40,8 +56,21 @@ namespace pmon::mid pMetric->GatherToBlob(pBlobBase); } } + void AddMetricStat(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) override + { + } + void Finalize() override + { + needsFullTraversalMetricPtrs_.clear(); + for (const auto& metric : metricPtrs_) { + if (metric->NeedsFullTraversal()) { + needsFullTraversalMetricPtrs_.push_back(metric.get()); + } + } + } private: std::vector>> metricPtrs_; + std::vector metricIds_; std::vector*> needsFullTraversalMetricPtrs_; ipc::HistoryRing& ring_; std::vector sampledPtrs_; From 42c95300c42e28d2ba39dea3581b8931fdc779b7 Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 26 Jan 2026 14:25:26 +0900 Subject: [PATCH 149/205] working RingMetricBind, but vector specialization is problematic --- IntelPresentMon/CommonUtilities/Meta.h | 12 +- .../PresentMonMiddleware/DynamicMetric.h | 53 +++++-- .../PresentMonMiddleware/DynamicStat.cpp | 23 ++- .../PresentMonMiddleware/DynamicStat.h | 1 + .../RingMetricBinding.cpp | 147 ++++++++++++++++++ .../PresentMonMiddleware/RingMetricBinding.h | 66 ++------ 6 files changed, 229 insertions(+), 73 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/Meta.h b/IntelPresentMon/CommonUtilities/Meta.h index 8c484f47..24219ccd 100644 --- a/IntelPresentMon/CommonUtilities/Meta.h +++ b/IntelPresentMon/CommonUtilities/Meta.h @@ -113,16 +113,16 @@ namespace pmon::util template MaxValue, std::underlying_type_t Value, typename Functor, typename ReturnT> constexpr ReturnT DispatchEnumValueRecursive_(std::underlying_type_t target, - Functor& func, ReturnT defaultValue) + Functor& func, ReturnT&& defaultValue) { if constexpr (Value < MaxValue) { if (target == Value) { return func.template operator()(Value)>(); } return DispatchEnumValueRecursive_( - target, func, defaultValue); + target, func, std::move(defaultValue)); } - return defaultValue; + return std::move(defaultValue); } } @@ -136,11 +136,13 @@ namespace pmon::util } template MaxValue, typename Functor, typename ReturnT> - constexpr ReturnT DispatchEnumValue(Enum value, Functor&& func, ReturnT defaultValue) + constexpr std::remove_reference_t DispatchEnumValue(Enum value, Functor&& func, ReturnT&& defaultValue) { + using Ret = std::remove_reference_t; auto& fn = func; + Ret defaultValueCopy = std::forward(defaultValue); return impl::DispatchEnumValueRecursive_( static_cast>(value), - fn, defaultValue); + fn, std::move(defaultValueCopy)); } } diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h index 2c273092..42e8c29f 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h @@ -42,18 +42,25 @@ namespace pmon::mid virtual void AddStat(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) = 0; virtual void FinalizeStats() = 0; virtual bool NeedsFullTraversal() const = 0; + virtual PM_METRIC GetMetricId() const = 0; }; template class DynamicMetricBinding : public DynamicMetric { + using SampleStorageT = std::conditional_t, uint8_t, T>; public: DynamicMetricBinding(PM_METRIC metric) : metric_{ metric } { - static_assert(std::is_same_v || std::is_same_v || std::is_same_v, - "DynamicMetricBinding only supports double, int32_t, and bool sample types."); + static_assert(std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v, + "DynamicMetricBinding only supports double, int32_t, uint64_t, and bool sample types."); + } + + PM_METRIC GetMetricId() const override + { + return metric_; } void AddSample(const S& sample) override @@ -61,7 +68,7 @@ namespace pmon::mid const auto& value = sample.*MemberPtr; // if samples has reserved size, it is needed if (samples_.capacity()) { - samples_.push_back(value); + samples_.push_back((SampleStorageT)value); } for (auto* stat : needsUpdatePtrs_) { stat->AddSample(value); @@ -99,8 +106,24 @@ namespace pmon::mid { if (!needsSortedWindowPtrs_.empty()) { std::ranges::sort(samples_); - for (auto pStat : needsSortedWindowPtrs_) { - pStat->InputSortedSamples(samples_); + if constexpr (std::is_same_v) { + const size_t sampleCount = samples_.size(); + std::unique_ptr boolSamples; + if (sampleCount > 0) { + boolSamples = std::make_unique(sampleCount); + for (size_t i = 0; i < sampleCount; ++i) { + boolSamples[i] = samples_[i] != 0; + } + } + const std::span spanSamples{ boolSamples.get(), sampleCount }; + for (auto pStat : needsSortedWindowPtrs_) { + pStat->InputSortedSamples(spanSamples); + } + } + else { + for (auto pStat : needsSortedWindowPtrs_) { + pStat->InputSortedSamples(samples_); + } } } // clear the sample sorting buffer for the next poll @@ -146,6 +169,10 @@ namespace pmon::mid samples_.reserve(150); } } + bool NeedsFullTraversal() const override + { + return !needsUpdatePtrs_.empty() || !needsSortedWindowPtrs_.empty(); + } ~DynamicMetricBinding() = default; @@ -185,7 +212,7 @@ namespace pmon::mid } PM_METRIC metric_; - mutable std::vector samples_; + mutable std::vector samples_; std::vector>> statPtrs_; std::vector*> needsUpdatePtrs_; std::vector*> needsSamplePtrs_; @@ -194,26 +221,28 @@ namespace pmon::mid }; template - std::unique_ptr> MakeDynamicMetric(const PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) + std::unique_ptr> MakeDynamicMetric(const PM_QUERY_ELEMENT& qel) { return util::DispatchEnumValue( qel.metric, [&]() -> std::unique_ptr> { if constexpr (util::metrics::HasFrameMetricMember) { - // frame data case constexpr auto memberPtr = util::metrics::FrameMetricMember::member; using MemberInfo = util::MemberPointerInfo; - using MemberType = typename MemberInfo::MemberType; - return std::make_unique>(Metric); + if constexpr (std::is_same_v) { + using MemberType = typename MemberInfo::MemberType; + return std::make_unique>(Metric); + } } - else { + if constexpr (requires { &S::value; }) { // telemetry case constexpr auto memberPtr = &S::value; using MemberInfo = util::MemberPointerInfo; using MemberType = typename MemberInfo::MemberType; return std::make_unique>(Metric); } - }, {} + return {}; + }, std::unique_ptr>{} ); } } diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp index 8c6f124c..787f2221 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp +++ b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp @@ -127,6 +127,9 @@ namespace pmon::mid case PM_DATA_TYPE_BOOL: *reinterpret_cast(pTarget) = hasValue_ ? value_ != 0.0 : false; break; + case PM_DATA_TYPE_UINT64: + *reinterpret_cast(pTarget) = hasValue_ ? (uint64_t)value_ : 0; + break; default: this->ThrowMalformed_("DynamicStat percentile expects double, int32, enum, or bool output value"); } @@ -182,6 +185,9 @@ namespace pmon::mid case PM_DATA_TYPE_BOOL: *reinterpret_cast(pTarget) = hasValue_ ? value_ != 0.0 : false; break; + case PM_DATA_TYPE_UINT64: + *reinterpret_cast(pTarget) = hasValue_ ? (uint64_t)value_ : 0; + break; default: this->ThrowMalformed_("DynamicStat min/max expects double, int32, enum, or bool output value"); } @@ -233,6 +239,9 @@ namespace pmon::mid case PM_DATA_TYPE_BOOL: *reinterpret_cast(pTarget) = hasValue_ ? doubleVal != 0.0 : false; break; + case PM_DATA_TYPE_UINT64: + *reinterpret_cast(pTarget) = hasValue_ ? (uint64_t)value_ : 0; + break; default: this->ThrowMalformed_("DynamicStat point expects double, int32, enum, or bool output value"); } @@ -266,6 +275,12 @@ namespace pmon::mid "Unsupported dynamic stat input data type"); } } + else if constexpr (std::is_same_v) { + if (inType != PM_DATA_TYPE_UINT64) { + throw util::Except(PM_STATUS_QUERY_MALFORMED, + "Unsupported dynamic stat input data type"); + } + } else if constexpr (std::is_same_v) { if (inType != PM_DATA_TYPE_BOOL) { throw util::Except(PM_STATUS_QUERY_MALFORMED, @@ -283,7 +298,7 @@ namespace pmon::mid return stat == PM_STAT_AVG || stat == PM_STAT_NON_ZERO_AVG; } - bool IsSupportedOutputType_(PM_DATA_TYPE outType, bool allowBool) + bool IsSupportedOutputType_(PM_DATA_TYPE outType, bool allowBool, bool allowUint64) { switch (outType) { case PM_DATA_TYPE_DOUBLE: @@ -292,6 +307,8 @@ namespace pmon::mid return true; case PM_DATA_TYPE_BOOL: return allowBool; + case PM_DATA_TYPE_UINT64: + return allowUint64; default: return false; } @@ -308,7 +325,8 @@ namespace pmon::mid } const bool allowBool = inType == PM_DATA_TYPE_BOOL; - if (!IsSupportedOutputType_(outType, allowBool)) { + const bool allowUint64 = inType == PM_DATA_TYPE_UINT64; + if (!IsSupportedOutputType_(outType, allowBool, allowUint64)) { throw util::Except(PM_STATUS_QUERY_MALFORMED, "Unsupported dynamic stat output data type"); } @@ -365,5 +383,6 @@ namespace pmon::mid template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); + template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); } diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicStat.h b/IntelPresentMon/PresentMonMiddleware/DynamicStat.h index 891b2f7e..55b383e5 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicStat.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicStat.h @@ -31,5 +31,6 @@ namespace pmon::mid extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); + extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); } diff --git a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp index 29edb27d..414ee7d5 100644 --- a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp +++ b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp @@ -1,5 +1,152 @@ #include "RingMetricBinding.h" +#include "../Interprocess/source/Interprocess.h" +#include "../Interprocess/source/IntrospectionDataTypeMapping.h" +#include "../Interprocess/source/SystemDeviceId.h" namespace pmon::mid { + template + class RingMetricBindingBound : public RingMetricBinding + { + public: + RingMetricBindingBound(const PM_QUERY_ELEMENT& qel) + : + deviceId_{ qel.deviceId }, + arrayIndex_{ qel.arrayIndex }, + metricIds_{ { qel.metric } } + {} + void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase, ipc::MiddlewareComms& comms, + std::optional pid) const override + { + // find the history ring for this metric(s) + const ipc::HistoryRing* pRing = nullptr; + if constexpr (std::is_same_v) { + if (!pid) { + throw util::Except(PM_STATUS_QUERY_MALFORMED, + "Frame metrics require a process id."); + } + pRing = &comms.GetFrameDataStore(*pid).frameData; + } + else { + if (pid) { + throw util::Except(PM_STATUS_QUERY_MALFORMED, + "Telemetry metrics do not accept a process id."); + } + using ValueType = typename S::value_type; + if (deviceId_ == ipc::kSystemDeviceId) { + pRing = &comms.GetSystemDataStore().telemetryData.FindRing(metricIds_.front()).at(arrayIndex_); + } + else { + pRing = &comms.GetGpuDataStore(deviceId_).telemetryData.FindRing(metricIds_.front()).at(arrayIndex_); + } + } + // traverse ring once to handle all metrics + pRing->ForEachInTimestampRange(window.oldest, window.newest, [this](const S& sample) { + for (auto pMetric : needsFullTraversalMetricPtrs_) { + pMetric->AddSample(sample); + } + }); + // handle metrics having point-sampled stats + for (const std::unique_ptr>& pMetric : metricPtrs_) { + const auto& requestedPoints = pMetric->GetRequestedSamplePoints(window); + if (!requestedPoints.empty()) { + sampledPtrs_.clear(); + if (!pRing->Empty()) { + for (auto point : requestedPoints) { + sampledPtrs_.push_back(&pRing->Nearest(point)); + } + pMetric->InputRequestedPointSamples(sampledPtrs_); + } + // if ring is empty, do not input point samples and the stats can fallback to last value or null/zero + } + pMetric->GatherToBlob(pBlobBase); + } + } + void AddMetricStat(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) override + { + DynamicMetric* pMetric = nullptr; + // if metric already exists, we add the stat to it + if (auto it = std::ranges::find(metricPtrs_, + qel.metric, [](const auto& p) { return p->GetMetricId(); }); it != metricPtrs_.end()) { + pMetric = it->get(); + } + + // if metric doesn't exist, we must make it + if (!pMetric) { + auto pNewMetric = MakeDynamicMetric(qel); + pMetric = pNewMetric.get(); + if (pMetric == nullptr) { + throw util::Except(PM_STATUS_QUERY_MALFORMED, + "Unsupported metric for dynamic query."); + } + metricPtrs_.push_back(std::move(pNewMetric)); + } + + // now add the stat + pMetric->AddStat(qel, intro); + } + void Finalize() override + { + needsFullTraversalMetricPtrs_.clear(); + for (const auto& metric : metricPtrs_) { + if (metric->NeedsFullTraversal()) { + needsFullTraversalMetricPtrs_.push_back(metric.get()); + } + } + } + private: + // device id only required for telemetry ring lookup + uint32_t deviceId_; + // index currently only applies to telemetry ring lookup + uint32_t arrayIndex_; + std::vector>> metricPtrs_; + std::vector metricIds_; + std::vector*> needsFullTraversalMetricPtrs_; + mutable std::vector sampledPtrs_; + }; + + namespace + { + template + inline constexpr bool IsTelemetryRingValue_ = + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v; + + template + struct TelemetryRingBindingBridger_ + { + static std::unique_ptr Invoke(PM_ENUM, PM_QUERY_ELEMENT& qel) + { + using ValueType = typename ipc::intro::DataTypeToStaticType

::type; + if constexpr (IsTelemetryRingValue_) { + using SampleType = ipc::TelemetrySample; + return std::make_unique>(qel); + } + else { + throw util::Except(PM_STATUS_QUERY_MALFORMED, + "Unsupported telemetry ring data type for dynamic query."); + } + } + static std::unique_ptr Default(PM_QUERY_ELEMENT&) + { + throw util::Except(PM_STATUS_QUERY_MALFORMED, + "Unknown telemetry ring data type for dynamic query."); + } + }; + } + + std::unique_ptr MakeRingMetricBinding(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) + { + // handle frame metric store case + if (qel.deviceId == ipc::kUniversalDeviceId) { + return std::make_unique>(qel); + } + // handle telemetry metric store case + const auto metricView = intro.FindMetric(qel.metric); + const auto typeInfo = metricView.GetDataTypeInfo(); + return ipc::intro::BridgeDataTypeWithEnum( + typeInfo.GetFrameType(), typeInfo.GetEnumId(), qel); + } } diff --git a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h index 591c2024..edd97892 100644 --- a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h +++ b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "DynamicMetric.h" #include "DynamicQueryWindow.h" #include "../CommonUtilities/Exception.h" @@ -12,67 +13,24 @@ #include "../Interprocess/source/HistoryRing.h" #include "../Interprocess/source/PmStatusError.h" +namespace pmon::ipc +{ + class MiddlewareComms; +} + namespace pmon::mid { - class RingMetricBindingBase + // container to bind and type erase a metric ring static type to one or more metrics + // (telemetry rings are always 1 metric per ring, but the frame ring serves many metrics) + class RingMetricBinding { public: - RingMetricBindingBase() = default; - virtual ~RingMetricBindingBase() = default; + virtual ~RingMetricBinding() = default; - virtual void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase) const = 0; + virtual void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase, ipc::MiddlewareComms& comms, std::optional pid = {}) const = 0; virtual void Finalize() = 0; virtual void AddMetricStat(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) = 0; }; - // container to bind and type erase a single metric ring to one or more metrics - // (telemetry rings are always 1 metric per ring, but the frame ring serves many metrics) - template - class RingMetricBinding : public RingMetricBindingBase - { - public: - RingMetricBinding(); - ~RingMetricBinding(); - void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase) const override - { - // traverse ring once to handle all metrics - ring_.ForEachInTimestampRange(window.oldest, window.newest, [this](const S& sample) { - for (auto pMetric : needsFullTraversalMetricPtrs_) { - pMetric->AddSample(sample); - } - }); - for (const std::unique_ptr>& pMetric : metricPtrs_) { - const auto& requestedPoints = pMetric->GetRequestedSamplePoints(window); - if (!requestedPoints.empty()) { - sampledPtrs_.clear(); - if (!ring_.Empty()) { - for (auto point : requestedPoints) { - sampledPtrs_.push_back(&ring_.Nearest(point)); - } - pMetric->InputRequestedPointSamples(sampledPtrs_); - } - // if ring is empty, do not input point samples and the stats can fallback to last value or null/zero - } - pMetric->GatherToBlob(pBlobBase); - } - } - void AddMetricStat(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) override - { - } - void Finalize() override - { - needsFullTraversalMetricPtrs_.clear(); - for (const auto& metric : metricPtrs_) { - if (metric->NeedsFullTraversal()) { - needsFullTraversalMetricPtrs_.push_back(metric.get()); - } - } - } - private: - std::vector>> metricPtrs_; - std::vector metricIds_; - std::vector*> needsFullTraversalMetricPtrs_; - ipc::HistoryRing& ring_; - std::vector sampledPtrs_; - }; + std::unique_ptr MakeRingMetricBinding(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro); } From 856dbd9d09f84898412499e5c5a853126eb16169 Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 26 Jan 2026 14:42:43 +0900 Subject: [PATCH 150/205] boost::container::vector doesn't suffer like std::vector --- .../PresentMonMiddleware/DynamicMetric.h | 27 +++++-------------- .../RingMetricBinding.cpp | 8 ------ 2 files changed, 6 insertions(+), 29 deletions(-) diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h index 42e8c29f..9fd37695 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "DynamicQueryWindow.h" #include "DynamicStat.h" #include "../CommonUtilities/Exception.h" @@ -48,7 +49,6 @@ namespace pmon::mid template class DynamicMetricBinding : public DynamicMetric { - using SampleStorageT = std::conditional_t, uint8_t, T>; public: DynamicMetricBinding(PM_METRIC metric) : @@ -68,7 +68,7 @@ namespace pmon::mid const auto& value = sample.*MemberPtr; // if samples has reserved size, it is needed if (samples_.capacity()) { - samples_.push_back((SampleStorageT)value); + samples_.push_back(value); } for (auto* stat : needsUpdatePtrs_) { stat->AddSample(value); @@ -106,24 +106,9 @@ namespace pmon::mid { if (!needsSortedWindowPtrs_.empty()) { std::ranges::sort(samples_); - if constexpr (std::is_same_v) { - const size_t sampleCount = samples_.size(); - std::unique_ptr boolSamples; - if (sampleCount > 0) { - boolSamples = std::make_unique(sampleCount); - for (size_t i = 0; i < sampleCount; ++i) { - boolSamples[i] = samples_[i] != 0; - } - } - const std::span spanSamples{ boolSamples.get(), sampleCount }; - for (auto pStat : needsSortedWindowPtrs_) { - pStat->InputSortedSamples(spanSamples); - } - } - else { - for (auto pStat : needsSortedWindowPtrs_) { - pStat->InputSortedSamples(samples_); - } + const std::span spanSamples{ samples_.data(), samples_.size() }; + for (auto pStat : needsSortedWindowPtrs_) { + pStat->InputSortedSamples(spanSamples); } } // clear the sample sorting buffer for the next poll @@ -212,7 +197,7 @@ namespace pmon::mid } PM_METRIC metric_; - mutable std::vector samples_; + mutable boost::container::vector samples_; std::vector>> statPtrs_; std::vector*> needsUpdatePtrs_; std::vector*> needsSamplePtrs_; diff --git a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp index 414ee7d5..01854ef9 100644 --- a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp +++ b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp @@ -21,17 +21,9 @@ namespace pmon::mid // find the history ring for this metric(s) const ipc::HistoryRing* pRing = nullptr; if constexpr (std::is_same_v) { - if (!pid) { - throw util::Except(PM_STATUS_QUERY_MALFORMED, - "Frame metrics require a process id."); - } pRing = &comms.GetFrameDataStore(*pid).frameData; } else { - if (pid) { - throw util::Except(PM_STATUS_QUERY_MALFORMED, - "Telemetry metrics do not accept a process id."); - } using ValueType = typename S::value_type; if (deviceId_ == ipc::kSystemDeviceId) { pRing = &comms.GetSystemDataStore().telemetryData.FindRing(metricIds_.front()).at(arrayIndex_); From 27e68e7d3f652b53b35c396f508e3e593dfaf9ec Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 26 Jan 2026 16:13:23 +0900 Subject: [PATCH 151/205] dynamic query skeleton --- .../PresentMonMiddleware/DynamicQuery.cpp | 18 ++++++++++++++++++ .../PresentMonMiddleware/DynamicQuery.h | 13 +++++++++++-- .../PresentMonMiddleware/RingMetricBinding.h | 2 +- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp index 79c11df7..20ae7c86 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp @@ -1,2 +1,20 @@ #include "DynamicQuery.h" +namespace pmon::mid::todo +{ + PM_DYNAMIC_QUERY::PM_DYNAMIC_QUERY(std::span qels, ipc::MiddlewareComms& comms) + { + + } + + size_t PM_DYNAMIC_QUERY::GetBlobSize() const + { + return blobSize_; + } + + + void PM_DYNAMIC_QUERY::Poll(uint8_t* pBlobBase, ipc::MiddlewareComms& comms, std::optional pid) const + { + + } +} \ No newline at end of file diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h index d547542e..e125c03e 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h @@ -2,7 +2,9 @@ #include #include #include +#include #include +#include #include "../PresentMonAPI2/PresentMonAPI.h" #include "../ControlLib/CpuTelemetryInfo.h" #include "../ControlLib/PresentMonPowerTelemetry.h" @@ -16,6 +18,7 @@ namespace pmapi::intro namespace pmon::mid { class Middleware; + class RingMetricBinding; } namespace pmon::ipc @@ -29,9 +32,15 @@ namespace pmon::mid::todo struct PM_DYNAMIC_QUERY { public: - PM_DYNAMIC_QUERY(); - + PM_DYNAMIC_QUERY(std::span qels, ipc::MiddlewareComms& comms); + size_t GetBlobSize() const; + void Poll(uint8_t* pBlobBase, ipc::MiddlewareComms& comms, std::optional pid) const; private: + std::vector> ringMetricPtrs_; + size_t blobSize_; + // window parameters; these could theoretically be independent of query but current API couples them + double windowSizeMs_ = 0; + double metricOffsetMs_ = 0.; }; } diff --git a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h index edd97892..90a4926d 100644 --- a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h +++ b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h @@ -27,7 +27,7 @@ namespace pmon::mid public: virtual ~RingMetricBinding() = default; - virtual void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase, ipc::MiddlewareComms& comms, std::optional pid = {}) const = 0; + virtual void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase, ipc::MiddlewareComms& comms, std::optional pid) const = 0; virtual void Finalize() = 0; virtual void AddMetricStat(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) = 0; }; From f47bb87f02422abf2b3bb7e608a60cfc98140188 Mon Sep 17 00:00:00 2001 From: Chili Date: Mon, 26 Jan 2026 17:38:14 +0900 Subject: [PATCH 152/205] factoring out validation --- .../ConcreteMiddleware.cpp | 6 +- .../PresentMonMiddleware/DynamicMetric.h | 15 - .../PresentMonMiddleware/DynamicQuery.h | 1 + .../PresentMonMiddleware/DynamicStat.cpp | 93 +---- .../PresentMonMiddleware/FrameEventQuery.cpp | 115 +----- .../PresentMonMiddleware/FrameEventQuery.h | 3 +- .../PresentMonMiddleware.vcxproj | 4 +- .../PresentMonMiddleware.vcxproj.filters | 8 +- .../PresentMonMiddleware/QueryValidation.cpp | 382 ++++++++++++++++++ .../PresentMonMiddleware/QueryValidation.h | 19 + .../RingMetricBinding.cpp | 13 +- 11 files changed, 437 insertions(+), 222 deletions(-) create mode 100644 IntelPresentMon/PresentMonMiddleware/QueryValidation.cpp create mode 100644 IntelPresentMon/PresentMonMiddleware/QueryValidation.h diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index 46422550..d8c2b14b 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -34,6 +34,7 @@ #include "../CommonUtilities/log/GlogShim.h" #include "ActionClient.h" +#include "QueryValidation.h" namespace pmon::mid { @@ -271,6 +272,7 @@ namespace pmon::mid // get introspection data for reference // TODO: cache this data so it's not required to be generated every time auto& ispec = GetIntrospectionRoot(); + ValidateQueryElements(queryElements, PM_METRIC_TYPE_DYNAMIC, ispec, *pComms); // make the query object that will be managed by the handle auto pQuery = std::make_unique(); @@ -503,10 +505,6 @@ namespace pmon::mid //pQuery->accumCpuBits.set(static_cast(CpuTelemetryCapBits::cpu_power)); break; default: - if (metricView.GetType() == PM_METRIC_TYPE_FRAME_EVENT) { - pmlog_warn(std::format("ignoring frame event metric [{}] while building dynamic query", - metricView.Introspect().GetSymbol())).diag(); - } break; } diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h index 9fd37695..b65aebc3 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h @@ -121,11 +121,6 @@ namespace pmon::mid void AddStat(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) override { const auto metricView = intro.FindMetric(metric_); - if (!IsStatSupported_(qel.stat, metricView)) { - throw pmon::util::Except(PM_STATUS_QUERY_MALFORMED, - "Dynamic metric stat not supported by metric."); - } - const auto inType = GetSampleType_(); auto outType = SelectOutputType_(qel.stat, metricView.GetDataTypeInfo().GetPolledType()); qel.dataSize = (uint32_t)ipc::intro::GetDataTypeSize(outType); @@ -186,16 +181,6 @@ namespace pmon::mid return metricOutType; } - static bool IsStatSupported_(PM_STAT stat, const pmapi::intro::MetricView& metricView) - { - for (auto statInfo : metricView.GetStatInfo()) { - if (statInfo.GetStat() == stat) { - return true; - } - } - return false; - } - PM_METRIC metric_; mutable boost::container::vector samples_; std::vector>> statPtrs_; diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h index e125c03e..ad624bbc 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h @@ -5,6 +5,7 @@ #include #include #include +#include "RingMetricBinding.h" #include "../PresentMonAPI2/PresentMonAPI.h" #include "../ControlLib/CpuTelemetryInfo.h" #include "../ControlLib/PresentMonPowerTelemetry.h" diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp index 787f2221..3f288e76 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp +++ b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp @@ -3,7 +3,7 @@ #include "../CommonUtilities/Exception.h" #include "../Interprocess/source/PmStatusError.h" #include -#include +#include namespace ipc = pmon::ipc; namespace util = pmon::util; @@ -23,7 +23,6 @@ namespace pmon::mid uint64_t GetSamplePoint(const DynamicQueryWindow& win) const override { ThrowMalformed_("DynamicStat::GetSamplePoint unsupported for this stat"); - return 0; } void SetSampledValue(T) override { @@ -70,9 +69,6 @@ namespace pmon::mid } void GatherToBlob(uint8_t* pBase) const override { - if (this->outType_ != PM_DATA_TYPE_DOUBLE) { - this->ThrowMalformed_("DynamicStat average expects double output value"); - } double avg = 0.0; if (count_ > 0) { avg = sum_ / (double)count_; @@ -131,7 +127,7 @@ namespace pmon::mid *reinterpret_cast(pTarget) = hasValue_ ? (uint64_t)value_ : 0; break; default: - this->ThrowMalformed_("DynamicStat percentile expects double, int32, enum, or bool output value"); + assert(false); } } private: @@ -189,7 +185,7 @@ namespace pmon::mid *reinterpret_cast(pTarget) = hasValue_ ? (uint64_t)value_ : 0; break; default: - this->ThrowMalformed_("DynamicStat min/max expects double, int32, enum, or bool output value"); + assert(false); } } private: @@ -220,7 +216,7 @@ namespace pmon::mid case PM_STAT_MID_POINT: return win.oldest + (win.newest - win.oldest) / 2; default: - this->ThrowMalformed_("DynamicStat point expects point mode"); + assert(false); return win.newest; } } @@ -243,7 +239,7 @@ namespace pmon::mid *reinterpret_cast(pTarget) = hasValue_ ? (uint64_t)value_ : 0; break; default: - this->ThrowMalformed_("DynamicStat point expects double, int32, enum, or bool output value"); + assert(false); } } private: @@ -260,83 +256,9 @@ namespace pmon::mid namespace { - template - void ValidateInputType_(PM_DATA_TYPE inType) - { - if constexpr (std::is_same_v) { - if (inType != PM_DATA_TYPE_DOUBLE) { - throw util::Except(PM_STATUS_QUERY_MALFORMED, - "Unsupported dynamic stat input data type"); - } - } - else if constexpr (std::is_same_v) { - if (inType != PM_DATA_TYPE_INT32 && inType != PM_DATA_TYPE_ENUM) { - throw util::Except(PM_STATUS_QUERY_MALFORMED, - "Unsupported dynamic stat input data type"); - } - } - else if constexpr (std::is_same_v) { - if (inType != PM_DATA_TYPE_UINT64) { - throw util::Except(PM_STATUS_QUERY_MALFORMED, - "Unsupported dynamic stat input data type"); - } - } - else if constexpr (std::is_same_v) { - if (inType != PM_DATA_TYPE_BOOL) { - throw util::Except(PM_STATUS_QUERY_MALFORMED, - "Unsupported dynamic stat input data type"); - } - } - else { - throw util::Except(PM_STATUS_QUERY_MALFORMED, - "Unsupported dynamic stat input data type"); - } - } - - bool IsAvgStat_(PM_STAT stat) - { - return stat == PM_STAT_AVG || stat == PM_STAT_NON_ZERO_AVG; - } - - bool IsSupportedOutputType_(PM_DATA_TYPE outType, bool allowBool, bool allowUint64) - { - switch (outType) { - case PM_DATA_TYPE_DOUBLE: - case PM_DATA_TYPE_INT32: - case PM_DATA_TYPE_ENUM: - return true; - case PM_DATA_TYPE_BOOL: - return allowBool; - case PM_DATA_TYPE_UINT64: - return allowUint64; - default: - return false; - } - } - - void ValidateOutputType_(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType) - { - if (IsAvgStat_(stat)) { - if (outType != PM_DATA_TYPE_DOUBLE) { - throw util::Except(PM_STATUS_QUERY_MALFORMED, - "Dynamic stat average expects double output value"); - } - return; - } - - const bool allowBool = inType == PM_DATA_TYPE_BOOL; - const bool allowUint64 = inType == PM_DATA_TYPE_UINT64; - if (!IsSupportedOutputType_(outType, allowBool, allowUint64)) { - throw util::Except(PM_STATUS_QUERY_MALFORMED, - "Unsupported dynamic stat output data type"); - } - } - template std::unique_ptr> MakeDynamicStatTyped_(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes) { - ValidateOutputType_(stat, inType, outType); - switch (stat) { case PM_STAT_AVG: return std::make_unique>(inType, outType, offsetBytes, false); @@ -368,8 +290,8 @@ namespace pmon::mid case PM_STAT_MID_LERP: case PM_STAT_COUNT: default: - throw util::Except(PM_STATUS_QUERY_MALFORMED, - "Unsupported dynamic stat"); + assert(false); + return {}; } } } @@ -377,7 +299,6 @@ namespace pmon::mid template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes) { - ValidateInputType_(inType); return MakeDynamicStatTyped_(stat, inType, outType, offsetBytes); } diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index 75bfef6d..84b2263f 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -2,7 +2,6 @@ #include "FrameEventQuery.h" #include "Middleware.h" #include "../PresentMonAPIWrapperCommon/Introspection.h" -#include "../PresentMonAPIWrapperCommon/Exception.h" #include "../Interprocess/source/Interprocess.h" #include "../Interprocess/source/SystemDeviceId.h" #include "../Interprocess/source/IntrospectionHelpers.h" @@ -13,7 +12,9 @@ #include "../CommonUtilities/Memory.h" #include "../CommonUtilities/Qpc.h" #include "../CommonUtilities/mc/FrameMetricsMemberMap.h" +#include "QueryValidation.h" #include +#include #include #include #include @@ -29,10 +30,7 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, pmon:: middleware_{ middleware }, comms_{ comms } { - if (queryElements.empty()) { - pmlog_error("Frame query requires at least one query element").diag(); - throw Except(PM_STATUS_QUERY_MALFORMED, "Empty frame query"); - } + pmon::mid::ValidateQueryElements(queryElements, PM_METRIC_TYPE_FRAME_EVENT, introRoot, comms_); size_t blobCursor = 0; gatherCommands_.reserve(queryElements.size()); @@ -41,78 +39,9 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, pmon:: const auto metricView = introRoot.FindMetric(q.metric); const auto metricType = metricView.GetType(); const bool isStaticMetric = metricType == PM_METRIC_TYPE_STATIC; - if (!pmapi::intro::MetricTypeIsFrameEvent(metricType) && !isStaticMetric) { - pmlog_error("Non-frame metric used in frame query") - .pmwatch(metricView.Introspect().GetSymbol()).diag(); - throw Except(PM_STATUS_QUERY_MALFORMED, "Frame query contains non-frame metric"); - } - - if (q.stat != PM_STAT_NONE) { - pmlog_warn("Frame query stat should be NONE") - .pmwatch(metricView.Introspect().GetSymbol()) - .pmwatch((int)q.stat).diag(); - } const auto frameType = metricView.GetDataTypeInfo().GetFrameType(); const auto frameTypeSize = ipc::intro::GetDataTypeSize(frameType); - if (frameTypeSize == 0) { - pmlog_error("Unsupported frame query data type") - .pmwatch(metricView.Introspect().GetSymbol()).diag(); - throw Except(PM_STATUS_QUERY_MALFORMED, "Unsupported frame query data type"); - } - - if (q.deviceId != ipc::kUniversalDeviceId) { - try { - introRoot.FindDevice(q.deviceId); - } - catch (const pmapi::LookupException&) { - pmlog_error(util::ReportException("Failed to find device ID while registering frame query")) - .diag(); - throw Except(PM_STATUS_QUERY_MALFORMED, "Invalid device ID"); - } - } - - const auto deviceMetricInfo = [&]() -> std::optional { - for (auto info : metricView.GetDeviceMetricInfo()) { - if (info.GetDevice().GetId() == q.deviceId) { - return info; - } - } - return std::nullopt; - }(); - - if (!deviceMetricInfo.has_value()) { - if (!isStaticMetric || q.deviceId != ipc::kSystemDeviceId) { - pmlog_error("Metric not supported by device in frame query") - .pmwatch(metricView.Introspect().GetSymbol()) - .pmwatch(q.deviceId).diag(); - throw Except(PM_STATUS_QUERY_MALFORMED, "Metric not supported by device in frame query"); - } - if (q.arrayIndex != 0) { - pmlog_error("Frame query array index out of bounds") - .pmwatch(metricView.Introspect().GetSymbol()) - .pmwatch(q.arrayIndex) - .pmwatch(1).diag(); - throw Except(PM_STATUS_QUERY_MALFORMED, "Frame query array index out of bounds"); - } - } - else { - if (!deviceMetricInfo->IsAvailable()) { - pmlog_error("Metric not supported by device in frame query") - .pmwatch(metricView.Introspect().GetSymbol()) - .pmwatch(q.deviceId).diag(); - throw Except(PM_STATUS_QUERY_MALFORMED, "Metric not supported by device in frame query"); - } - - const auto arraySize = deviceMetricInfo->GetArraySize(); - if (q.arrayIndex >= arraySize) { - pmlog_error("Frame query array index out of bounds") - .pmwatch(metricView.Introspect().GetSymbol()) - .pmwatch(q.arrayIndex) - .pmwatch(arraySize).diag(); - throw Except(PM_STATUS_QUERY_MALFORMED, "Frame query array index out of bounds"); - } - } const auto alignment = ipc::intro::GetDataTypeAlignment(frameType); blobCursor = util::PadToAlignment(blobCursor, alignment); @@ -128,27 +57,10 @@ PM_FRAME_QUERY::PM_FRAME_QUERY(std::span queryElements, pmon:: if (isStaticMetric) { cmd.isStatic = true; } - else if (q.deviceId > 0 && q.deviceId <= ipc::kSystemDeviceId) { - const auto& teleMap = q.deviceId == ipc::kSystemDeviceId ? - comms_.GetSystemDataStore().telemetryData : - comms_.GetGpuDataStore(q.deviceId).telemetryData; - - if (teleMap.ArraySize(q.metric) == 0) { - pmlog_error("Telemetry ring missing for metric in frame query") - .pmwatch(metricView.Introspect().GetSymbol()) - .pmwatch(q.deviceId).diag(); - throw Except(PM_STATUS_QUERY_MALFORMED, "Telemetry ring missing for metric in frame query"); - } - } else if (q.deviceId == ipc::kUniversalDeviceId) { - cmd = MapQueryElementToFrameGatherCommand_(q, blobCursor, metricView); + cmd = MapQueryElementToFrameGatherCommand_(q, blobCursor, frameType); cmd.dataSize = (uint32_t)frameTypeSize; } - else { - pmlog_error("Invalid device id in frame query") - .pmwatch(q.deviceId).diag(); - throw Except(PM_STATUS_QUERY_MALFORMED, "Invalid device id in frame query"); - } q.dataOffset = blobCursor; q.dataSize = frameTypeSize; @@ -188,11 +100,11 @@ size_t PM_FRAME_QUERY::GetBlobSize() const return blobSize_; } -PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherCommand_(const PM_QUERY_ELEMENT& q, size_t blobByteCursor, const pmapi::intro::MetricView& metricView) +PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherCommand_(const PM_QUERY_ELEMENT& q, size_t blobByteCursor, PM_DATA_TYPE frameType) { GatherCommand_ cmd{ .metricId = q.metric, - .gatherType = metricView.GetDataTypeInfo().GetFrameType(), + .gatherType = frameType, .blobOffset = uint32_t(blobByteCursor), .frameMetricsOffset = std::numeric_limits::max(), .deviceId = q.deviceId, @@ -213,19 +125,8 @@ PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherComma return false; }, false); - if (!mapped) { - pmlog_error("Unexpected frame metric in frame query") - .pmwatch(metricView.Introspect().GetSymbol()) - .pmwatch(metricView.IntrospectType().GetSymbol()) - .pmwatch((int)q.metric).diag(); - throw Except(PM_STATUS_QUERY_MALFORMED, "Unexpected frame metric in frame query"); - } - if (cmd.frameMetricsOffset == std::numeric_limits::max()) { - pmlog_error("Frame metrics offset not set") - .pmwatch(metricView.Introspect().GetSymbol()) - .pmwatch((int)q.metric); - throw Except<>("Frame metrics offset not set in command mapping"); - } + assert(mapped); + assert(cmd.frameMetricsOffset != std::numeric_limits::max()); return cmd; } diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h index fb58b397..99bf042d 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.h @@ -8,7 +8,6 @@ namespace pmapi::intro { class Root; - class MetricView; } namespace pmon::mid @@ -55,7 +54,7 @@ struct PM_FRAME_QUERY bool isStatic = false; }; // functions - static GatherCommand_ MapQueryElementToFrameGatherCommand_(const PM_QUERY_ELEMENT& q, size_t blobCursor, const pmapi::intro::MetricView& metricView); + static GatherCommand_ MapQueryElementToFrameGatherCommand_(const PM_QUERY_ELEMENT& q, size_t blobCursor, PM_DATA_TYPE frameType); void GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* pBlobBytes, const pmon::util::metrics::FrameMetrics& frameMetrics) const; void GatherFromStatic_(const GatherCommand_& cmd, uint8_t* pBlobBytes, uint32_t processId) const; diff --git a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj index b843a59f..d05dbb4d 100644 --- a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj +++ b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj @@ -24,6 +24,7 @@ + @@ -35,6 +36,7 @@ + @@ -143,4 +145,4 @@ - \ No newline at end of file + diff --git a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters index 2c16949c..873fd144 100644 --- a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters +++ b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters @@ -54,6 +54,9 @@ Header Files + + Header Files + @@ -80,5 +83,8 @@ Source Files + + Source Files + - \ No newline at end of file + diff --git a/IntelPresentMon/PresentMonMiddleware/QueryValidation.cpp b/IntelPresentMon/PresentMonMiddleware/QueryValidation.cpp new file mode 100644 index 00000000..77a9a81b --- /dev/null +++ b/IntelPresentMon/PresentMonMiddleware/QueryValidation.cpp @@ -0,0 +1,382 @@ +// Copyright (C) 2017-2024 Intel Corporation +#include "QueryValidation.h" +#include "../PresentMonAPIWrapperCommon/Introspection.h" +#include "../PresentMonAPIWrapperCommon/Exception.h" +#include "../CommonUtilities/Exception.h" +#include "../CommonUtilities/Meta.h" +#include "../CommonUtilities/mc/FrameMetricsMemberMap.h" +#include "../CommonUtilities/log/Log.h" +#include "../Interprocess/source/Interprocess.h" +#include "../Interprocess/source/IntrospectionHelpers.h" +#include "../Interprocess/source/IntrospectionDataTypeMapping.h" +#include "../Interprocess/source/PmStatusError.h" +#include "../Interprocess/source/SystemDeviceId.h" +#include +#include +#include +#include + +namespace ipc = pmon::ipc; +namespace util = pmon::util; + +namespace +{ + void ThrowMalformed_(const char* msg) + { + throw util::Except(PM_STATUS_QUERY_MALFORMED, msg); + } + + bool IsStatSupported_(PM_STAT stat, const pmapi::intro::MetricView& metricView) + { + for (auto statInfo : metricView.GetStatInfo()) { + if (statInfo.GetStat() == stat) { + return true; + } + } + return false; + } + + bool IsDynamicStatSupported_(PM_STAT stat) + { + switch (stat) { + case PM_STAT_AVG: + case PM_STAT_NON_ZERO_AVG: + case PM_STAT_PERCENTILE_99: + case PM_STAT_PERCENTILE_95: + case PM_STAT_PERCENTILE_90: + case PM_STAT_PERCENTILE_01: + case PM_STAT_PERCENTILE_05: + case PM_STAT_PERCENTILE_10: + case PM_STAT_MAX: + case PM_STAT_MIN: + case PM_STAT_MID_POINT: + case PM_STAT_NEWEST_POINT: + case PM_STAT_OLDEST_POINT: + return true; + case PM_STAT_NONE: + case PM_STAT_MID_LERP: + case PM_STAT_COUNT: + default: + return false; + } + } + + bool IsAvgStat_(PM_STAT stat) + { + return stat == PM_STAT_AVG || stat == PM_STAT_NON_ZERO_AVG; + } + + bool IsSupportedDynamicInputType_(PM_DATA_TYPE inType) + { + switch (inType) { + case PM_DATA_TYPE_DOUBLE: + case PM_DATA_TYPE_INT32: + case PM_DATA_TYPE_ENUM: + case PM_DATA_TYPE_UINT64: + case PM_DATA_TYPE_BOOL: + return true; + default: + return false; + } + } + + bool IsSupportedDynamicOutputType_(PM_DATA_TYPE outType, bool allowBool, bool allowUint64) + { + switch (outType) { + case PM_DATA_TYPE_DOUBLE: + case PM_DATA_TYPE_INT32: + case PM_DATA_TYPE_ENUM: + return true; + case PM_DATA_TYPE_BOOL: + return allowBool; + case PM_DATA_TYPE_UINT64: + return allowUint64; + default: + return false; + } + } + + const char* ValidateDynamicStatTypes_(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType) + { + if (IsAvgStat_(stat)) { + if (outType != PM_DATA_TYPE_DOUBLE) { + return "Dynamic stat average expects double output value"; + } + return nullptr; + } + + const bool allowBool = inType == PM_DATA_TYPE_BOOL; + const bool allowUint64 = inType == PM_DATA_TYPE_UINT64; + if (!IsSupportedDynamicOutputType_(outType, allowBool, allowUint64)) { + return "Unsupported dynamic stat output data type"; + } + return nullptr; + } + + PM_DATA_TYPE SelectDynamicOutputType_(PM_STAT stat, PM_DATA_TYPE metricOutType) + { + if (IsAvgStat_(stat)) { + return PM_DATA_TYPE_DOUBLE; + } + return metricOutType; + } + + bool IsFrameMetricMapped_(PM_METRIC metric) + { + return util::DispatchEnumValue( + metric, + [&]() -> bool { + return util::metrics::HasFrameMetricMember; + }, + false); + } + + template + struct TelemetryRingValueChecker_ + { + static bool Invoke(PM_ENUM) + { + using ValueType = typename ipc::intro::DataTypeToStaticType::type; + return std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v; + } + static bool Default() + { + return false; + } + }; + + struct QueryKey_ + { + PM_METRIC metric; + uint32_t arrayIndex; + PM_STAT stat; + }; + + struct QueryKeyHasher_ + { + size_t operator()(const QueryKey_& key) const noexcept + { + uint64_t h = (uint64_t)key.metric; + h = (h * 1315423911u) ^ (uint64_t)key.arrayIndex; + h = (h * 1315423911u) ^ (uint64_t)key.stat; + return (size_t)h; + } + }; + + struct QueryKeyEqual_ + { + bool operator()(const QueryKey_& lhs, const QueryKey_& rhs) const noexcept + { + return lhs.metric == rhs.metric && + lhs.arrayIndex == rhs.arrayIndex && + lhs.stat == rhs.stat; + } + }; + + std::string GetStatSymbol_(const pmapi::intro::Root& introRoot, PM_STAT stat) + { + try { + return introRoot.FindEnumKey(PM_ENUM_STAT, (int)stat).GetSymbol(); + } + catch (...) { + return "UnknownStat"; + } + } + + bool IsSupportedTelemetryRingType_(PM_DATA_TYPE dataType, PM_ENUM enumId) + { + return ipc::intro::BridgeDataTypeWithEnum(dataType, enumId); + } +} + +namespace pmon::mid +{ + void ValidateQueryElements(std::span queryElements, PM_METRIC_TYPE queryType, + const pmapi::intro::Root& introRoot, const ipc::MiddlewareComms& comms) + { + const bool isDynamicQuery = queryType == PM_METRIC_TYPE_DYNAMIC; + const bool isFrameQuery = queryType == PM_METRIC_TYPE_FRAME_EVENT; + if (!isDynamicQuery && !isFrameQuery) { + ThrowMalformed_("Invalid query type for validation"); + } + + if (queryElements.empty()) { + pmlog_error("Query requires at least one query element").diag(); + ThrowMalformed_("Empty query"); + } + + std::unordered_map seenKeys; + for (size_t elementIndex = 0; elementIndex < queryElements.size(); ++elementIndex) { + const auto& q = queryElements[elementIndex]; + const auto metricView = introRoot.FindMetric(q.metric); + const auto statSymbol = GetStatSymbol_(introRoot, q.stat); + const auto LogAndThrow = [&](const char* msg) { + pmlog_error(msg) + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch(statSymbol) + .pmwatch((int)q.stat) + .pmwatch(q.arrayIndex) + .pmwatch(q.deviceId) + .pmwatch((uint64_t)elementIndex).diag(); + ThrowMalformed_(msg); + }; + + const QueryKey_ key{ + .metric = q.metric, + .arrayIndex = q.arrayIndex, + .stat = q.stat, + }; + if (auto it = seenKeys.find(key); it != seenKeys.end()) { + pmlog_error("Duplicate query element") + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch(statSymbol) + .pmwatch((int)q.stat) + .pmwatch(q.arrayIndex) + .pmwatch(q.deviceId) + .pmwatch((uint64_t)it->second) + .pmwatch((uint64_t)elementIndex).diag(); + ThrowMalformed_("Duplicate query element"); + } + else { + seenKeys.emplace(key, elementIndex); + } + + const auto metricType = metricView.GetType(); + const bool isStaticMetric = metricType == PM_METRIC_TYPE_STATIC; + + const bool metricTypeOk = isStaticMetric || (isDynamicQuery ? + pmapi::intro::MetricTypeIsDynamic(metricType) : + pmapi::intro::MetricTypeIsFrameEvent(metricType)); + if (!metricTypeOk) { + if (isDynamicQuery) { + LogAndThrow("Dynamic query contains non-dynamic metric"); + } + LogAndThrow("Frame query contains non-frame metric"); + } + + if (isFrameQuery) { + if (q.stat != PM_STAT_NONE) { + pmlog_warn("Frame query stat should be NONE") + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch((int)q.stat).diag(); + } + } + else { + if (isStaticMetric) { + if (q.stat != PM_STAT_NONE) { + LogAndThrow("Static metric in dynamic query requires NONE stat"); + } + } + else { + if (!IsStatSupported_(q.stat, metricView)) { + LogAndThrow("Dynamic metric stat not supported by metric"); + } + if (!IsDynamicStatSupported_(q.stat)) { + LogAndThrow("Dynamic metric stat not supported by implementation"); + } + } + } + + const auto typeInfo = metricView.GetDataTypeInfo(); + const auto frameType = typeInfo.GetFrameType(); + const auto polledType = typeInfo.GetPolledType(); + const auto queryDataType = isFrameQuery ? frameType : polledType; + if (ipc::intro::GetDataTypeSize(queryDataType) == 0) { + LogAndThrow("Unsupported query data type"); + } + + if (q.deviceId != ipc::kUniversalDeviceId) { + try { + introRoot.FindDevice(q.deviceId); + } + catch (const pmapi::LookupException&) { + LogAndThrow("Invalid device ID"); + } + } + + const auto deviceMetricInfo = [&]() -> std::optional { + for (auto info : metricView.GetDeviceMetricInfo()) { + if (info.GetDevice().GetId() == q.deviceId) { + return info; + } + } + return std::nullopt; + }(); + + if (!deviceMetricInfo.has_value()) { + if (!isStaticMetric || q.deviceId != ipc::kSystemDeviceId) { + LogAndThrow("Metric not supported by device in query"); + } + if (q.arrayIndex != 0) { + pmlog_error("Query array index out of bounds") + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch(statSymbol) + .pmwatch((int)q.stat) + .pmwatch(q.arrayIndex) + .pmwatch(q.deviceId) + .pmwatch((uint64_t)elementIndex) + .pmwatch(1).diag(); + ThrowMalformed_("Query array index out of bounds"); + } + } + else { + if (!deviceMetricInfo->IsAvailable()) { + LogAndThrow("Metric not supported by device in query"); + } + + const auto arraySize = deviceMetricInfo->GetArraySize(); + if (q.arrayIndex >= arraySize) { + pmlog_error("Query array index out of bounds") + .pmwatch(metricView.Introspect().GetSymbol()) + .pmwatch(statSymbol) + .pmwatch((int)q.stat) + .pmwatch(q.arrayIndex) + .pmwatch(q.deviceId) + .pmwatch((uint64_t)elementIndex) + .pmwatch(arraySize).diag(); + ThrowMalformed_("Query array index out of bounds"); + } + } + + if (isFrameQuery && !isStaticMetric && q.deviceId == ipc::kUniversalDeviceId) { + if (!IsFrameMetricMapped_(q.metric)) { + LogAndThrow("Unexpected frame metric in frame query"); + } + } + + if (!isStaticMetric && q.deviceId != ipc::kUniversalDeviceId) { + if (q.deviceId > 0 && q.deviceId <= ipc::kSystemDeviceId) { + const auto& teleMap = q.deviceId == ipc::kSystemDeviceId ? + comms.GetSystemDataStore().telemetryData : + comms.GetGpuDataStore(q.deviceId).telemetryData; + if (teleMap.ArraySize(q.metric) == 0) { + LogAndThrow("Telemetry ring missing for metric in query"); + } + } + else { + LogAndThrow("Invalid device id in query"); + } + } + + if (isDynamicQuery && !isStaticMetric) { + if (!IsSupportedDynamicInputType_(frameType)) { + LogAndThrow("Unsupported dynamic stat input data type"); + } + + const auto outType = SelectDynamicOutputType_(q.stat, polledType); + if (const auto err = ValidateDynamicStatTypes_(q.stat, frameType, outType)) { + LogAndThrow(err); + } + + if (q.deviceId != ipc::kUniversalDeviceId) { + if (!IsSupportedTelemetryRingType_(frameType, typeInfo.GetEnumId())) { + LogAndThrow("Unsupported telemetry ring data type for dynamic query"); + } + } + } + } + } +} diff --git a/IntelPresentMon/PresentMonMiddleware/QueryValidation.h b/IntelPresentMon/PresentMonMiddleware/QueryValidation.h new file mode 100644 index 00000000..58c42f09 --- /dev/null +++ b/IntelPresentMon/PresentMonMiddleware/QueryValidation.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include + +namespace pmapi::intro +{ + class Root; +} + +namespace pmon::ipc +{ + class MiddlewareComms; +} + +namespace pmon::mid +{ + void ValidateQueryElements(std::span queryElements, PM_METRIC_TYPE queryType, + const pmapi::intro::Root& introRoot, const ipc::MiddlewareComms& comms); +} diff --git a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp index 01854ef9..65d10870 100644 --- a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp +++ b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp @@ -2,6 +2,7 @@ #include "../Interprocess/source/Interprocess.h" #include "../Interprocess/source/IntrospectionDataTypeMapping.h" #include "../Interprocess/source/SystemDeviceId.h" +#include namespace pmon::mid { @@ -67,9 +68,9 @@ namespace pmon::mid if (!pMetric) { auto pNewMetric = MakeDynamicMetric(qel); pMetric = pNewMetric.get(); + assert(pMetric != nullptr); if (pMetric == nullptr) { - throw util::Except(PM_STATUS_QUERY_MALFORMED, - "Unsupported metric for dynamic query."); + return; } metricPtrs_.push_back(std::move(pNewMetric)); } @@ -117,14 +118,14 @@ namespace pmon::mid return std::make_unique>(qel); } else { - throw util::Except(PM_STATUS_QUERY_MALFORMED, - "Unsupported telemetry ring data type for dynamic query."); + assert(false); + return {}; } } static std::unique_ptr Default(PM_QUERY_ELEMENT&) { - throw util::Except(PM_STATUS_QUERY_MALFORMED, - "Unknown telemetry ring data type for dynamic query."); + assert(false); + return {}; } }; } From cde964e7b641acbfec75abd18eee9415a995c489 Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 27 Jan 2026 06:10:21 +0900 Subject: [PATCH 153/205] dynamic query draft complete --- .../PresentMonMiddleware/DynamicQuery.cpp | 99 ++++++++++++++++++- .../PresentMonMiddleware/DynamicQuery.h | 12 +-- .../PresentMonMiddleware/DynamicStat.cpp | 12 +-- .../RingMetricBinding.cpp | 2 +- .../PresentMonMiddleware/RingMetricBinding.h | 2 +- 5 files changed, 108 insertions(+), 19 deletions(-) diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp index 20ae7c86..a868fce7 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp @@ -1,10 +1,94 @@ #include "DynamicQuery.h" +#include "QueryValidation.h" +#include "../PresentMonAPIWrapperCommon/Introspection.h" +#include "../Interprocess/source/SystemDeviceId.h" +#include "../Interprocess/source/Interprocess.h" +#include "../CommonUtilities/Hash.h" +#include + +namespace +{ + struct TelemetryBindingKey_ + { + uint32_t deviceId; + PM_METRIC metric; + uint32_t arrayIndex; + + bool operator==(const TelemetryBindingKey_& other) const noexcept + { + return deviceId == other.deviceId && + metric == other.metric && + arrayIndex == other.arrayIndex; + } + }; +} + +namespace std +{ + template<> + struct hash + { + size_t operator()(const TelemetryBindingKey_& key) const noexcept + { + const size_t h0 = std::hash{}(key.deviceId); + const size_t h1 = std::hash{}((uint32_t)key.metric); + const size_t h2 = std::hash{}(key.arrayIndex); + return pmon::util::hash::HashCombine(pmon::util::hash::HashCombine(h0, h1), h2); + } + }; +} namespace pmon::mid::todo { PM_DYNAMIC_QUERY::PM_DYNAMIC_QUERY(std::span qels, ipc::MiddlewareComms& comms) { + const auto* introBase = comms.GetIntrospectionRoot(); + pmapi::intro::Root introRoot{ introBase, [](const PM_INTROSPECTION_ROOT*) {} }; + pmon::mid::ValidateQueryElements(qels, PM_METRIC_TYPE_DYNAMIC, introRoot, comms); + + std::unordered_map telemetryBindings; + RingMetricBinding* frameBinding = nullptr; + size_t blobCursor = 0; + for (auto& qel : qels) { + RingMetricBinding* binding = nullptr; + if (qel.deviceId == ipc::kUniversalDeviceId) { + binding = frameBinding; + if (!binding) { + auto bindingPtr = MakeRingMetricBinding(qel, introRoot); + binding = bindingPtr.get(); + frameBinding = bindingPtr.get(); + ringMetricPtrs_.push_back(std::move(bindingPtr)); + } + } + else { + const TelemetryBindingKey_ key{ + .deviceId = qel.deviceId, + .metric = qel.metric, + .arrayIndex = qel.arrayIndex, + }; + if (auto it = telemetryBindings.find(key); it != telemetryBindings.end()) { + binding = it->second; + } + else { + auto bindingPtr = MakeRingMetricBinding(qel, introRoot); + binding = bindingPtr.get(); + ringMetricPtrs_.push_back(std::move(bindingPtr)); + telemetryBindings.emplace(key, binding); + } + } + + qel.dataOffset = blobCursor; + binding->AddMetricStat(qel, introRoot); + blobCursor = qel.dataOffset + qel.dataSize; + } + + for (auto& binding : ringMetricPtrs_) { + binding->Finalize(); + } + + // make sure blob sizes are multiple of 16 bytes for blob array alignment purposes + blobSize_ = util::PadToAlignment(blobCursor, 16u); } size_t PM_DYNAMIC_QUERY::GetBlobSize() const @@ -12,9 +96,18 @@ namespace pmon::mid::todo return blobSize_; } - - void PM_DYNAMIC_QUERY::Poll(uint8_t* pBlobBase, ipc::MiddlewareComms& comms, std::optional pid) const + DynamicQueryWindow PM_DYNAMIC_QUERY::GenerateQueryWindow_(uint64_t nowTimestamp) const { + return DynamicQueryWindow(); + } + + void PM_DYNAMIC_QUERY::Poll(uint8_t* pBlobBase, ipc::MiddlewareComms& comms, std::optional pid, + uint64_t nowTimestamp) const + { + const auto window = GenerateQueryWindow_(nowTimestamp); + for (auto& pRing : ringMetricPtrs_) { + pRing->Poll(window, pBlobBase, comms, pid); + } } -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h index ad624bbc..2e9f09d5 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h @@ -6,15 +6,11 @@ #include #include #include "RingMetricBinding.h" +#include "DynamicQueryWindow.h" #include "../PresentMonAPI2/PresentMonAPI.h" #include "../ControlLib/CpuTelemetryInfo.h" #include "../ControlLib/PresentMonPowerTelemetry.h" -namespace pmapi::intro -{ - class Root; - class MetricView; -} namespace pmon::mid { @@ -35,8 +31,12 @@ namespace pmon::mid::todo public: PM_DYNAMIC_QUERY(std::span qels, ipc::MiddlewareComms& comms); size_t GetBlobSize() const; - void Poll(uint8_t* pBlobBase, ipc::MiddlewareComms& comms, std::optional pid) const; + void Poll(uint8_t* pBlobBase, ipc::MiddlewareComms& comms, std::optional pid, uint64_t nowTimestamp) const; + private: + // functions + DynamicQueryWindow GenerateQueryWindow_(uint64_t nowTimestamp) const; + // data std::vector> ringMetricPtrs_; size_t blobSize_; // window parameters; these could theoretically be independent of query but current API couples them diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp index 3f288e76..922b1a87 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp +++ b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp @@ -18,19 +18,19 @@ namespace pmon::mid public: void AddSample(T) override { - ThrowMalformed_("DynamicStat::AddSample unsupported for this stat"); + throw util::Except(PM_STATUS_QUERY_MALFORMED, "DynamicStat::AddSample unsupported for this stat"); } uint64_t GetSamplePoint(const DynamicQueryWindow& win) const override { - ThrowMalformed_("DynamicStat::GetSamplePoint unsupported for this stat"); + throw util::Except(PM_STATUS_QUERY_MALFORMED, "DynamicStat::GetSamplePoint unsupported for this stat"); } void SetSampledValue(T) override { - ThrowMalformed_("DynamicStat::SetSampledValue unsupported for this stat"); + throw util::Except(PM_STATUS_QUERY_MALFORMED, "DynamicStat::SetSampledValue unsupported for this stat"); } void InputSortedSamples(std::span) override { - ThrowMalformed_("DynamicStat::InputSortedSamples unsupported for this stat"); + throw util::Except(PM_STATUS_QUERY_MALFORMED, "DynamicStat::InputSortedSamples unsupported for this stat"); } protected: DynamicStatBase_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes) @@ -38,10 +38,6 @@ namespace pmon::mid outType_{ outType }, offsetBytes_{ offsetBytes } {} - void ThrowMalformed_(const char* msg) const - { - throw util::Except(PM_STATUS_QUERY_MALFORMED, msg); - } PM_DATA_TYPE inType_ = PM_DATA_TYPE_DOUBLE; PM_DATA_TYPE outType_ = PM_DATA_TYPE_DOUBLE; size_t offsetBytes_ = 0; diff --git a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp index 65d10870..58a5b796 100644 --- a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp +++ b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp @@ -17,7 +17,7 @@ namespace pmon::mid metricIds_{ { qel.metric } } {} void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase, ipc::MiddlewareComms& comms, - std::optional pid) const override + const std::optional& pid) const override { // find the history ring for this metric(s) const ipc::HistoryRing* pRing = nullptr; diff --git a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h index 90a4926d..214aa8e5 100644 --- a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h +++ b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h @@ -27,7 +27,7 @@ namespace pmon::mid public: virtual ~RingMetricBinding() = default; - virtual void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase, ipc::MiddlewareComms& comms, std::optional pid) const = 0; + virtual void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase, ipc::MiddlewareComms& comms, const std::optional& pid) const = 0; virtual void Finalize() = 0; virtual void AddMetricStat(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) = 0; }; From a9a18464a1f9d6fd5a650b5cddf458c92ed3d95e Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 27 Jan 2026 07:06:17 +0900 Subject: [PATCH 154/205] frame source locator function --- .../ConcreteMiddleware.cpp | 23 +++++++++++-------- .../PresentMonMiddleware/ConcreteMiddleware.h | 4 ++++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index d8c2b14b..345d1a33 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -114,6 +114,7 @@ namespace pmon::mid presentMonStreamClients.emplace(targetPid, std::make_unique(std::move(res.nsmFileName), false)); } + // TODO: error when already tracking auto sourceIter = frameMetricsSources.find(targetPid); if (sourceIter == frameMetricsSources.end()) { frameMetricsSources.emplace(targetPid, @@ -1318,20 +1319,12 @@ static void ReportMetrics( { const auto framesToCopy = numFrames; numFrames = 0; - - auto sourceIter = frameMetricsSources.find(processId); - if (sourceIter == frameMetricsSources.end() || sourceIter->second == nullptr) { - pmlog_error("Frame metrics source for process {} doesn't exist. Please call pmStartStream to initialize the client.").diag(); - throw Except(std::format("Failed to find frame metrics source for pid {} in ConsumeFrameEvents", processId)); - } - if (framesToCopy == 0) { return; } - auto&& [storePid, source] = *sourceIter; // TODO: consider making consume return one frame at a time (eliminate need for heap alloc) - auto frames = source->Consume(framesToCopy); + auto frames = GetFrameMetricSource_(processId).Consume(framesToCopy); assert(frames.size() <= framesToCopy); for (const auto& frameMetrics : frames) { pQuery->GatherToBlob(pBlob, processId, frameMetrics); @@ -1923,6 +1916,18 @@ static void ReportMetrics( } } + FrameMetricsSource& ConcreteMiddleware::GetFrameMetricSource_(uint32_t pid) const + { + if (auto it = frameMetricsSources.find(pid); + it == frameMetricsSources.end() || it->second == nullptr) { + pmlog_error("Frame metrics source for process {} doesn't exist. Call pmStartTracking to initialize the client.").diag(); + throw Except(std::format("Failed to find frame metrics source for pid {}", pid)); + } + else { + return *it->second; + } + } + // This code currently doesn't support the copying of multiple swap chains. If a second swap chain // is encountered it will update the numSwapChains to the correct number and then copy the swap // chain frame information with the most presents. If the client does happen to specify two swap diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h index b148d7bd..cb3a3dd5 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h @@ -191,6 +191,7 @@ namespace pmon::mid uint32_t StartEtlLogging() override; std::string FinishEtlLogging(uint32_t etlLogSessionHandle) override; private: + // functions PmNsmFrameData* GetFrameDataStart(StreamClient* client, uint64_t& index, uint64_t dataOffset, uint64_t& queryFrameDataDelta, double& windowSampleSizeMs); uint64_t GetAdjustedQpc(uint64_t current_qpc, uint64_t frame_data_qpc, uint64_t queryMetricsOffset, LARGE_INTEGER frequency, uint64_t& queryFrameDataDelta); bool DecrementIndex(NamedSharedMem* nsm_view, uint64_t& index); @@ -211,6 +212,9 @@ namespace pmon::mid void SaveMetricCache(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob); void CopyMetricCacheToBlob(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob); + FrameMetricsSource& GetFrameMetricSource_(uint32_t pid) const; + + // data std::optional GetCachedGpuInfoIndex(uint32_t deviceId); const pmapi::intro::Root& GetIntrospectionRoot(); From 23d637d6a18d732e398818433068e2d67e8d3653 Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 27 Jan 2026 10:59:37 +0900 Subject: [PATCH 155/205] 2nd draft dynamic query w/ frame source complete --- .../PresentMonMiddleware/DynamicMetric.h | 2 - .../PresentMonMiddleware/DynamicQuery.cpp | 10 +- .../PresentMonMiddleware/DynamicQuery.h | 3 +- .../FrameMetricsSource.cpp | 142 ++++++++++- .../PresentMonMiddleware/FrameMetricsSource.h | 49 ++++ .../RingMetricBinding.cpp | 237 +++++++++++------- .../PresentMonMiddleware/RingMetricBinding.h | 8 +- 7 files changed, 352 insertions(+), 99 deletions(-) diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h index b65aebc3..cbed6f7c 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h @@ -54,8 +54,6 @@ namespace pmon::mid : metric_{ metric } { - static_assert(std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v, - "DynamicMetricBinding only supports double, int32_t, uint64_t, and bool sample types."); } PM_METRIC GetMetricId() const override diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp index a868fce7..e864f9cd 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp @@ -55,7 +55,7 @@ namespace pmon::mid::todo if (qel.deviceId == ipc::kUniversalDeviceId) { binding = frameBinding; if (!binding) { - auto bindingPtr = MakeRingMetricBinding(qel, introRoot); + auto bindingPtr = MakeFrameMetricBinding(qel); binding = bindingPtr.get(); frameBinding = bindingPtr.get(); ringMetricPtrs_.push_back(std::move(bindingPtr)); @@ -71,7 +71,7 @@ namespace pmon::mid::todo binding = it->second; } else { - auto bindingPtr = MakeRingMetricBinding(qel, introRoot); + auto bindingPtr = MakeTelemetryRingMetricBinding(qel, introRoot); binding = bindingPtr.get(); ringMetricPtrs_.push_back(std::move(bindingPtr)); telemetryBindings.emplace(key, binding); @@ -102,12 +102,12 @@ namespace pmon::mid::todo } - void PM_DYNAMIC_QUERY::Poll(uint8_t* pBlobBase, ipc::MiddlewareComms& comms, std::optional pid, - uint64_t nowTimestamp) const + void PM_DYNAMIC_QUERY::Poll(uint8_t* pBlobBase, ipc::MiddlewareComms& comms, + uint64_t nowTimestamp, FrameMetricsSource* frameSource) const { const auto window = GenerateQueryWindow_(nowTimestamp); for (auto& pRing : ringMetricPtrs_) { - pRing->Poll(window, pBlobBase, comms, pid); + pRing->Poll(window, pBlobBase, comms, frameSource); } } } diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h index 2e9f09d5..e3af51e8 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h @@ -31,7 +31,8 @@ namespace pmon::mid::todo public: PM_DYNAMIC_QUERY(std::span qels, ipc::MiddlewareComms& comms); size_t GetBlobSize() const; - void Poll(uint8_t* pBlobBase, ipc::MiddlewareComms& comms, std::optional pid, uint64_t nowTimestamp) const; + void Poll(uint8_t* pBlobBase, ipc::MiddlewareComms& comms, + uint64_t nowTimestamp, FrameMetricsSource* frameSource) const; private: // functions diff --git a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp index e49390d4..e0132371 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp @@ -46,6 +46,104 @@ namespace pmon::mid } } + bool SwapChainState::Empty() const + { + return metrics_.empty(); + } + + size_t SwapChainState::Size() const + { + return metrics_.size(); + } + + const util::metrics::FrameMetrics& SwapChainState::At(size_t index) const + { + return metrics_[index]; + } + + size_t SwapChainState::LowerBoundIndex(uint64_t timestamp) const + { + return BoundIndex_(timestamp, BoundKind_::Lower); + } + + size_t SwapChainState::UpperBoundIndex(uint64_t timestamp) const + { + return BoundIndex_(timestamp, BoundKind_::Upper); + } + + size_t SwapChainState::NearestIndex(uint64_t timestamp) const + { + const size_t count = Size(); + if (count == 0) { + return 0; + } + + size_t index = LowerBoundIndex(timestamp); + if (index >= count) { + return count - 1; + } + + if (index > 0) { + const uint64_t nextTimestamp = TimestampOf_(At(index)); + const uint64_t prevTimestamp = TimestampOf_(At(index - 1)); + const uint64_t prevDelta = timestamp - prevTimestamp; + const uint64_t nextDelta = nextTimestamp - timestamp; + if (prevDelta <= nextDelta) { + --index; + } + } + + return index; + } + + size_t SwapChainState::CountInTimestampRange(uint64_t start, uint64_t end) const + { + const size_t count = Size(); + if (count == 0) { + return 0; + } + + const size_t first = LowerBoundIndex(start); + const size_t last = UpperBoundIndex(end); + if (last < first) { + return 0; + } + return last - first; + } + + size_t SwapChainState::BoundIndex_(uint64_t timestamp, BoundKind_ kind) const + { + const size_t count = Size(); + size_t lo = 0; + size_t hi = count; + while (lo < hi) { + const size_t mid = lo + (hi - lo) / 2; + const uint64_t midTimestamp = TimestampOf_(At(mid)); + if (kind == BoundKind_::Lower) { + if (midTimestamp < timestamp) { + lo = mid + 1; + } + else { + hi = mid; + } + } + else { + if (midTimestamp <= timestamp) { + lo = mid + 1; + } + else { + hi = mid; + } + } + } + return lo; + } + + uint64_t SwapChainState::TimestampOf_(const util::metrics::FrameMetrics& metrics) + { + return metrics.presentStartQpc; + } + void SwapChainState::PushMetrics_(const util::metrics::FrameMetrics& metrics) { if (metrics_.full() && cursor_ > 0) { @@ -126,10 +224,15 @@ namespace pmon::mid ring.MarkNextRead(nextFrameSerial_); } + void FrameMetricsSource::Update() + { + ProcessNewFrames_(); + } + std::vector FrameMetricsSource::Consume(size_t maxFrames) { std::vector output; - ProcessNewFrames_(); + Update(); if (maxFrames == 0) { return output; @@ -167,6 +270,43 @@ namespace pmon::mid return output; } + const SwapChainState* FrameMetricsSource::GetActiveSwapChainState_(uint64_t start, uint64_t end) const + { + const SwapChainState* selectedState = nullptr; + uint64_t selectedCount = 0; + + for (const auto& [address, state] : swapChains_) { + if (state.Empty()) { + continue; + } + const size_t count = state.CountInTimestampRange(start, end); + if (selectedState == nullptr || + count > selectedCount) { + selectedState = &state; + selectedCount = count; + } + } + + return selectedState; + } + + const util::metrics::FrameMetrics* FrameMetricsSource::FindNearestActive(uint64_t start, uint64_t end, uint64_t timestamp) const + { + const auto* state = GetActiveSwapChainState_(start, end); + if (state == nullptr || state->Empty()) { + return nullptr; + } + + const size_t index = state->NearestIndex(timestamp); + return &state->At(index); + } + + bool FrameMetricsSource::HasActiveSwapChainSamples(uint64_t start, uint64_t end) const + { + const auto* state = GetActiveSwapChainState_(start, end); + return state != nullptr && !state->Empty(); + } + const util::QpcConverter& FrameMetricsSource::GetQpcConverter() const { assert(qpcConverter_); diff --git a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h index 160a3fec..ab5be271 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h +++ b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include "../CommonUtilities/Qpc.h" @@ -19,8 +20,43 @@ namespace pmon::mid const util::metrics::FrameMetrics& Peek() const; void ConsumeNext(); void ProcessFrame(const util::metrics::FrameData& frame, util::QpcConverter& qpc); + bool Empty() const; + size_t Size() const; + const util::metrics::FrameMetrics& At(size_t index) const; + size_t LowerBoundIndex(uint64_t timestamp) const; + size_t UpperBoundIndex(uint64_t timestamp) const; + size_t NearestIndex(uint64_t timestamp) const; + size_t CountInTimestampRange(uint64_t start, uint64_t end) const; + template + size_t ForEachInTimestampRange(uint64_t start, uint64_t end, F&& func) const + { + const size_t count = Size(); + if (count == 0) { + return 0; + } + + size_t index = LowerBoundIndex(start); + size_t visited = 0; + for (; index < count; ++index) { + const auto& metrics = At(index); + if (TimestampOf_(metrics) > end) { + break; + } + std::forward(func)(metrics); + ++visited; + } + return visited; + } private: + enum class BoundKind_ + { + Lower, + Upper + }; + + size_t BoundIndex_(uint64_t timestamp, BoundKind_ kind) const; + static uint64_t TimestampOf_(const util::metrics::FrameMetrics& metrics); void ClampCursor_(); void PushMetrics_(const util::metrics::FrameMetrics& metrics); @@ -40,11 +76,24 @@ namespace pmon::mid FrameMetricsSource(FrameMetricsSource&&) = delete; FrameMetricsSource& operator=(FrameMetricsSource&&) = delete; + void Update(); std::vector Consume(size_t maxFrames); + template + size_t ForEachInActiveTimestampRange(uint64_t start, uint64_t end, F&& func) const + { + const auto* pSwap = GetActiveSwapChainState_(start, end); + if (pSwap == nullptr) { + return 0; + } + return pSwap->ForEachInTimestampRange(start, end, std::forward(func)); + } + const util::metrics::FrameMetrics* FindNearestActive(uint64_t start, uint64_t end, uint64_t timestamp) const; + bool HasActiveSwapChainSamples(uint64_t start, uint64_t end) const; const util::QpcConverter& GetQpcConverter() const; private: void ProcessNewFrames_(); + const SwapChainState* GetActiveSwapChainState_(uint64_t start, uint64_t end) const; ipc::MiddlewareComms& comms_; const ipc::FrameDataStore* pStore_ = nullptr; diff --git a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp index 58a5b796..fb14a5f6 100644 --- a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp +++ b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp @@ -1,111 +1,171 @@ #include "RingMetricBinding.h" +#include "FrameMetricsSource.h" #include "../Interprocess/source/Interprocess.h" #include "../Interprocess/source/IntrospectionDataTypeMapping.h" #include "../Interprocess/source/SystemDeviceId.h" #include +#include namespace pmon::mid { - template - class RingMetricBindingBound : public RingMetricBinding + namespace { - public: - RingMetricBindingBound(const PM_QUERY_ELEMENT& qel) - : - deviceId_{ qel.deviceId }, - arrayIndex_{ qel.arrayIndex }, - metricIds_{ { qel.metric } } - {} - void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase, ipc::MiddlewareComms& comms, - const std::optional& pid) const override + template + inline constexpr bool IsTelemetryRingValue_ = + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v; + + template + class RingMetricBindingBase_ : public RingMetricBinding { - // find the history ring for this metric(s) - const ipc::HistoryRing* pRing = nullptr; - if constexpr (std::is_same_v) { - pRing = &comms.GetFrameDataStore(*pid).frameData; - } - else { - using ValueType = typename S::value_type; - if (deviceId_ == ipc::kSystemDeviceId) { - pRing = &comms.GetSystemDataStore().telemetryData.FindRing(metricIds_.front()).at(arrayIndex_); + public: + void AddMetricStat(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) override + { + DynamicMetric* pMetric = nullptr; + // if metric already exists, we add the stat to it + if (auto it = std::ranges::find(metricPtrs_, + qel.metric, [](const auto& p) { return p->GetMetricId(); }); it != metricPtrs_.end()) { + pMetric = it->get(); } - else { - pRing = &comms.GetGpuDataStore(deviceId_).telemetryData.FindRing(metricIds_.front()).at(arrayIndex_); + + // if metric doesn't exist, we must make it + if (!pMetric) { + auto pNewMetric = MakeDynamicMetric(qel); + pMetric = pNewMetric.get(); + assert(pMetric != nullptr); + if (pMetric == nullptr) { + return; + } + metricPtrs_.push_back(std::move(pNewMetric)); } + + // now add the stat + pMetric->AddStat(qel, intro); } - // traverse ring once to handle all metrics - pRing->ForEachInTimestampRange(window.oldest, window.newest, [this](const S& sample) { - for (auto pMetric : needsFullTraversalMetricPtrs_) { - pMetric->AddSample(sample); + + void Finalize() override + { + needsFullTraversalMetricPtrs_.clear(); + for (const auto& metric : metricPtrs_) { + if (metric->NeedsFullTraversal()) { + needsFullTraversalMetricPtrs_.push_back(metric.get()); + } } - }); - // handle metrics having point-sampled stats - for (const std::unique_ptr>& pMetric : metricPtrs_) { - const auto& requestedPoints = pMetric->GetRequestedSamplePoints(window); - if (!requestedPoints.empty()) { - sampledPtrs_.clear(); - if (!pRing->Empty()) { - for (auto point : requestedPoints) { - sampledPtrs_.push_back(&pRing->Nearest(point)); + } + + protected: + template + void ProcessSamples_(const DynamicQueryWindow& window, uint8_t* pBlobBase, + ForEachFunc&& forEachFunc, NearestFunc&& nearestFunc, bool hasSamples) const + { + forEachFunc(window.oldest, window.newest, [this](const S& sample) { + for (auto pMetric : needsFullTraversalMetricPtrs_) { + pMetric->AddSample(sample); + } + }); + + for (const std::unique_ptr>& pMetric : metricPtrs_) { + const auto& requestedPoints = pMetric->GetRequestedSamplePoints(window); + if (!requestedPoints.empty()) { + sampledPtrs_.clear(); + if (hasSamples) { + for (auto point : requestedPoints) { + const S* samplePtr = nearestFunc(point); + if (samplePtr == nullptr) { + sampledPtrs_.clear(); + break; + } + sampledPtrs_.push_back(samplePtr); + } + if (sampledPtrs_.size() == requestedPoints.size()) { + pMetric->InputRequestedPointSamples(sampledPtrs_); + } } - pMetric->InputRequestedPointSamples(sampledPtrs_); + // if no samples, do not input point samples and the stats can fallback to last value or null/zero } - // if ring is empty, do not input point samples and the stats can fallback to last value or null/zero + pMetric->GatherToBlob(pBlobBase); } - pMetric->GatherToBlob(pBlobBase); } - } - void AddMetricStat(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) override + + std::vector>> metricPtrs_; + std::vector*> needsFullTraversalMetricPtrs_; + mutable std::vector sampledPtrs_; + }; + + template + class TelemetryRingMetricBinding_ : public RingMetricBindingBase_ { - DynamicMetric* pMetric = nullptr; - // if metric already exists, we add the stat to it - if (auto it = std::ranges::find(metricPtrs_, - qel.metric, [](const auto& p) { return p->GetMetricId(); }); it != metricPtrs_.end()) { - pMetric = it->get(); + public: + explicit TelemetryRingMetricBinding_(const PM_QUERY_ELEMENT& qel) + : + deviceId_{ qel.deviceId }, + arrayIndex_{ qel.arrayIndex }, + metricId_{ qel.metric } + { } - // if metric doesn't exist, we must make it - if (!pMetric) { - auto pNewMetric = MakeDynamicMetric(qel); - pMetric = pNewMetric.get(); - assert(pMetric != nullptr); - if (pMetric == nullptr) { - return; + void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase, ipc::MiddlewareComms& comms, + FrameMetricsSource* pFrameSource) const override + { + (void)pFrameSource; + + const ipc::HistoryRing* pRing = nullptr; + using ValueType = typename S::value_type; + if (deviceId_ == ipc::kSystemDeviceId) { + pRing = &comms.GetSystemDataStore().telemetryData.FindRing(metricId_).at(arrayIndex_); + } + else { + pRing = &comms.GetGpuDataStore(deviceId_).telemetryData.FindRing(metricId_).at(arrayIndex_); } - metricPtrs_.push_back(std::move(pNewMetric)); + + auto forEachFunc = [pRing](uint64_t start, uint64_t end, auto&& func) { + pRing->ForEachInTimestampRange(start, end, std::forward(func)); + }; + auto nearestFunc = [pRing](uint64_t point) -> const S* { + return &pRing->Nearest(point); + }; + + this->ProcessSamples_(window, pBlobBase, forEachFunc, nearestFunc, !pRing->Empty()); } - // now add the stat - pMetric->AddStat(qel, intro); - } - void Finalize() override + private: + uint32_t deviceId_; + uint32_t arrayIndex_; + PM_METRIC metricId_; + }; + + class FrameMetricBinding_ : public RingMetricBindingBase_ { - needsFullTraversalMetricPtrs_.clear(); - for (const auto& metric : metricPtrs_) { - if (metric->NeedsFullTraversal()) { - needsFullTraversalMetricPtrs_.push_back(metric.get()); - } + public: + explicit FrameMetricBinding_(const PM_QUERY_ELEMENT&) + { } - } - private: - // device id only required for telemetry ring lookup - uint32_t deviceId_; - // index currently only applies to telemetry ring lookup - uint32_t arrayIndex_; - std::vector>> metricPtrs_; - std::vector metricIds_; - std::vector*> needsFullTraversalMetricPtrs_; - mutable std::vector sampledPtrs_; - }; - namespace - { - template - inline constexpr bool IsTelemetryRingValue_ = - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v; + void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase, ipc::MiddlewareComms& comms, + FrameMetricsSource* pFrameSource) const override + { + (void)comms; + + if (pFrameSource == nullptr) { + throw pmon::util::Except(PM_STATUS_FAILURE, + "Frame metrics source missing for dynamic query."); + } + + pFrameSource->Update(); + + auto forEachFunc = [pFrameSource](uint64_t start, uint64_t end, auto&& func) { + pFrameSource->ForEachInActiveTimestampRange(start, end, std::forward(func)); + }; + auto nearestFunc = [pFrameSource, &window](uint64_t point) -> const util::metrics::FrameMetrics* { + return pFrameSource->FindNearestActive(window.oldest, window.newest, point); + }; + + const bool hasSamples = pFrameSource->HasActiveSwapChainSamples(window.oldest, window.newest); + this->ProcessSamples_(window, pBlobBase, forEachFunc, nearestFunc, hasSamples); + } + }; template struct TelemetryRingBindingBridger_ @@ -115,13 +175,14 @@ namespace pmon::mid using ValueType = typename ipc::intro::DataTypeToStaticType
::type; if constexpr (IsTelemetryRingValue_) { using SampleType = ipc::TelemetrySample; - return std::make_unique>(qel); + return std::make_unique>(qel); } else { assert(false); return {}; } } + static std::unique_ptr Default(PM_QUERY_ELEMENT&) { assert(false); @@ -130,13 +191,13 @@ namespace pmon::mid }; } - std::unique_ptr MakeRingMetricBinding(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) + std::unique_ptr MakeFrameMetricBinding(PM_QUERY_ELEMENT& qel) + { + return std::make_unique(qel); + } + + std::unique_ptr MakeTelemetryRingMetricBinding(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) { - // handle frame metric store case - if (qel.deviceId == ipc::kUniversalDeviceId) { - return std::make_unique>(qel); - } - // handle telemetry metric store case const auto metricView = intro.FindMetric(qel.metric); const auto typeInfo = metricView.GetDataTypeInfo(); return ipc::intro::BridgeDataTypeWithEnum( diff --git a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h index 214aa8e5..c63618fc 100644 --- a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h +++ b/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h @@ -20,6 +20,8 @@ namespace pmon::ipc namespace pmon::mid { + class FrameMetricsSource; + // container to bind and type erase a metric ring static type to one or more metrics // (telemetry rings are always 1 metric per ring, but the frame ring serves many metrics) class RingMetricBinding @@ -27,10 +29,12 @@ namespace pmon::mid public: virtual ~RingMetricBinding() = default; - virtual void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase, ipc::MiddlewareComms& comms, const std::optional& pid) const = 0; + virtual void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase, ipc::MiddlewareComms& comms, + FrameMetricsSource* pFrameSource) const = 0; virtual void Finalize() = 0; virtual void AddMetricStat(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) = 0; }; - std::unique_ptr MakeRingMetricBinding(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro); + std::unique_ptr MakeFrameMetricBinding(PM_QUERY_ELEMENT& qel); + std::unique_ptr MakeTelemetryRingMetricBinding(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro); } From 3938a0aca73efc6c74aa0c4542fc94b3eb626292 Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 27 Jan 2026 12:02:53 +0900 Subject: [PATCH 156/205] remove legacy code from middleware --- .../ConcreteMiddleware.cpp | 1962 +---------------- .../PresentMonMiddleware/ConcreteMiddleware.h | 193 +- .../PresentMonMiddleware/DynamicQuery.cpp | 114 +- .../PresentMonMiddleware/DynamicQuery.h | 56 +- 4 files changed, 89 insertions(+), 2236 deletions(-) diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index 345d1a33..375af512 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -46,6 +46,7 @@ namespace pmon::mid static const uint32_t kMaxRespBufferSize = 4096; static const uint64_t kClientFrameDeltaQPCThreshold = 50000000; static constexpr size_t kFrameMetricsPerSwapChainCapacity = 4096u; + ConcreteMiddleware::ConcreteMiddleware(std::optional pipeNameOverride) { const auto pipeName = pipeNameOverride.transform(&std::string::c_str) @@ -63,39 +64,24 @@ namespace pmon::mid throw util::Except(PM_STATUS_PIPE_ERROR); } - clientProcessId = GetCurrentProcessId(); - // connect to the shm server pComms = ipc::MakeMiddlewareComms(pActionClient->GetShmPrefix(), pActionClient->GetShmSalt()); - // Get the introspection data + // Get and cache the introspection data try { - auto& ispec = GetIntrospectionRoot(); - - uint32_t gpuAdapterId = 0; - auto deviceView = ispec.GetDevices(); - for (auto dev : deviceView) { - if (dev.GetType() == PM_DEVICE_TYPE_GRAPHICS_ADAPTER) - { - cachedGpuInfo.push_back({ dev.GetVendor(), dev.GetName(), dev.GetId(), gpuAdapterId, 0., 0, 0 }); - gpuAdapterId++; - } - } + auto& ispec = GetIntrospectionRoot_(); } catch (...) { pmlog_error(ReportException("Problem acquiring introspection data")); throw; } - - // Update the static GPU metric data from the service - GetStaticGpuMetrics(); - GetStaticCpuMetrics(); } ConcreteMiddleware::~ConcreteMiddleware() = default; const PM_INTROSPECTION_ROOT* ConcreteMiddleware::GetIntrospectionData() { + // TODO: consider updating cache or otherwise connecting to middleware intro cache here return pComms->GetIntrospectionRoot(); } @@ -104,16 +90,11 @@ namespace pmon::mid free(const_cast(pRoot)); } + // TODO: rename => tracking PM_STATUS ConcreteMiddleware::StartStreaming(uint32_t targetPid) { try { auto res = pActionClient->DispatchSync(StartTracking::Params{ targetPid }); - // Initialize client in client map using returned nsm name - auto iter = presentMonStreamClients.find(targetPid); - if (iter == presentMonStreamClients.end()) { - presentMonStreamClients.emplace(targetPid, - std::make_unique(std::move(res.nsmFileName), false)); - } // TODO: error when already tracking auto sourceIter = frameMetricsSources.find(targetPid); if (sourceIter == frameMetricsSources.end()) { @@ -139,12 +120,7 @@ namespace pmon::mid .isPlayback = true, .isBackpressured = isBackpressured }); - // Initialize client in client map using returned nsm name - auto iter = presentMonStreamClients.find(targetPid); - if (iter == presentMonStreamClients.end()) { - presentMonStreamClients.emplace(targetPid, - std::make_unique(std::move(res.nsmFileName), false)); - } + // TODO: error when already tracking auto sourceIter = frameMetricsSources.find(targetPid); if (sourceIter == frameMetricsSources.end()) { frameMetricsSources.emplace(targetPid, @@ -161,21 +137,13 @@ namespace pmon::mid return PM_STATUS_SUCCESS; } + // TODO: rename => tracking PM_STATUS ConcreteMiddleware::StopStreaming(uint32_t targetPid) { try { + // TODO: error when not tracking (returns 0 not 1) + frameMetricsSources.erase(targetPid); pActionClient->DispatchSync(StopTracking::Params{ targetPid }); - // Remove client from map of clients - auto iter = presentMonStreamClients.find(targetPid); - if (iter != presentMonStreamClients.end()) { - presentMonStreamClients.erase(std::move(iter)); - } - auto sourceIter = frameMetricsSources.find(targetPid); - if (sourceIter != frameMetricsSources.end()) { - frameMetricsSources.erase(std::move(sourceIter)); - } - // Remove the input to frame start data for this process. - mPclI2FsManager.RemoveProcess(targetPid); } catch (...) { const auto code = util::GeneratePmStatus(); @@ -183,56 +151,11 @@ namespace pmon::mid return code; } - pmlog_info(std::format("Stop tracking pid [{}]", targetPid)).diag(); + pmlog_info(std::format("Stopped tracking pid [{}]", targetPid)).diag(); return PM_STATUS_SUCCESS; } - void ConcreteMiddleware::GetStaticCpuMetrics() - { - try { - auto metrics = pActionClient->DispatchSync(GetStaticCpuMetrics::Params{}); - - const auto cpuNameLower = str::ToLower(metrics.cpuName); - PM_DEVICE_VENDOR deviceVendor; - if (cpuNameLower.contains("intel")) { - deviceVendor = PM_DEVICE_VENDOR_INTEL; - } - else if (cpuNameLower.contains("amd")) { - deviceVendor = PM_DEVICE_VENDOR_AMD; - } - else { - deviceVendor = PM_DEVICE_VENDOR_UNKNOWN; - } - - cachedCpuInfo.push_back({ - .deviceVendor = deviceVendor, - .deviceName = std::move(metrics.cpuName), - .cpuPowerLimit = metrics.cpuPowerLimit - }); - } - catch (...) { - const auto code = util::GeneratePmStatus(); - pmlog_error(util::ReportException()).code(code).diag(); - } - } - - std::string ConcreteMiddleware::GetProcessName(uint32_t processId) - { - HANDLE handle = NULL; - std::string processName = ""; - char path[MAX_PATH]; - DWORD numChars = sizeof(path); - handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, processId); - if (handle) { - if (QueryFullProcessImageNameA(handle, 0, path, &numChars)) { - processName = PathFindFileNameA(path); - } - CloseHandle(handle); - } - return processName; - } - - const pmapi::intro::Root& mid::ConcreteMiddleware::GetIntrospectionRoot() + const pmapi::intro::Root& mid::ConcreteMiddleware::GetIntrospectionRoot_() { if (!pIntroRoot) { pmlog_info("Creating and cacheing introspection root object").diag(); @@ -269,1014 +192,12 @@ namespace pmon::mid } PM_DYNAMIC_QUERY* ConcreteMiddleware::RegisterDynamicQuery(std::span queryElements, double windowSizeMs, double metricOffsetMs) - { - // get introspection data for reference - // TODO: cache this data so it's not required to be generated every time - auto& ispec = GetIntrospectionRoot(); - ValidateQueryElements(queryElements, PM_METRIC_TYPE_DYNAMIC, ispec, *pComms); - - // make the query object that will be managed by the handle - auto pQuery = std::make_unique(); - std::optional cachedGpuInfoIndex; - - uint64_t offset = 0u; - for (auto& qe : queryElements) { - // A device of zero is NOT a graphics adapter; system is not GPU either - if (qe.deviceId != 0 && qe.deviceId != ipc::kSystemDeviceId) { - // If we have already set a device id in this query, check to - // see if it's the same device id as previously set. Currently - // we don't support querying multiple gpu devices in the one - // query - if (cachedGpuInfoIndex.has_value()) { - const auto cachedDeviceId = cachedGpuInfo[cachedGpuInfoIndex.value()].deviceId; - if (cachedDeviceId != qe.deviceId) { - pmlog_error(std::format("Multiple GPU devices not allowed in single query ({} and {})", - cachedDeviceId, qe.deviceId)).diag(); - throw Except("Multiple GPU devices not allowed in single query"); - } - } - else { - // Go through the cached Gpus and see which device the client - // wants - if (auto i = rn::find(cachedGpuInfo, qe.deviceId, &DeviceInfo::deviceId); - i != cachedGpuInfo.end()) { - cachedGpuInfoIndex = uint32_t(i - cachedGpuInfo.begin()); - } - else { - pmlog_error(std::format("unable to find device id [{}] while building dynamic query", qe.deviceId)).diag(); - // TODO: shouldn't we throw here? - } - } - } - - auto metricView = ispec.FindMetric(qe.metric); - switch (qe.metric) { - case PM_METRIC_APPLICATION: - case PM_METRIC_SWAP_CHAIN_ADDRESS: - case PM_METRIC_PRESENT_MODE: - case PM_METRIC_PRESENT_RUNTIME: - case PM_METRIC_PRESENT_FLAGS: - case PM_METRIC_SYNC_INTERVAL: - case PM_METRIC_ALLOWS_TEARING: - case PM_METRIC_FRAME_TYPE: - case PM_METRIC_CPU_START_QPC: - case PM_METRIC_CPU_BUSY: - case PM_METRIC_CPU_WAIT: - case PM_METRIC_CPU_FRAME_TIME: - case PM_METRIC_GPU_LATENCY: - case PM_METRIC_GPU_BUSY: - case PM_METRIC_GPU_WAIT: - case PM_METRIC_GPU_TIME: - case PM_METRIC_DISPLAY_LATENCY: - case PM_METRIC_DISPLAYED_TIME: - case PM_METRIC_ANIMATION_ERROR: - case PM_METRIC_PRESENTED_FPS: - case PM_METRIC_APPLICATION_FPS: - case PM_METRIC_DISPLAYED_FPS: - case PM_METRIC_DROPPED_FRAMES: - case PM_METRIC_CLICK_TO_PHOTON_LATENCY: - case PM_METRIC_ALL_INPUT_TO_PHOTON_LATENCY: - case PM_METRIC_INSTRUMENTED_LATENCY: - case PM_METRIC_PRESENT_START_QPC: - case PM_METRIC_BETWEEN_PRESENTS: - case PM_METRIC_IN_PRESENT_API: - case PM_METRIC_BETWEEN_DISPLAY_CHANGE: - case PM_METRIC_UNTIL_DISPLAYED: - case PM_METRIC_RENDER_PRESENT_LATENCY: - case PM_METRIC_BETWEEN_SIMULATION_START: - case PM_METRIC_PC_LATENCY: - case PM_METRIC_DISPLAYED_FRAME_TIME: - //case PM_METRIC_INSTRUMENTED_RENDER_DISPLAY_LATENCY: - pQuery->accumFpsData = true; - break; - case PM_METRIC_GPU_POWER: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_power)); - break; - case PM_METRIC_GPU_VOLTAGE: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_voltage)); - break; - case PM_METRIC_GPU_FREQUENCY: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_frequency)); - break; - case PM_METRIC_GPU_TEMPERATURE: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_temperature)); - break; - case PM_METRIC_GPU_UTILIZATION: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_utilization)); - break; - case PM_METRIC_GPU_RENDER_COMPUTE_UTILIZATION: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_render_compute_utilization)); - break; - case PM_METRIC_GPU_MEDIA_UTILIZATION: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_media_utilization)); - break; - case PM_METRIC_GPU_MEM_POWER: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::vram_power)); - break; - case PM_METRIC_GPU_MEM_VOLTAGE: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::vram_voltage)); - break; - case PM_METRIC_GPU_MEM_FREQUENCY: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::vram_frequency)); - break; - case PM_METRIC_GPU_MEM_EFFECTIVE_FREQUENCY: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::vram_effective_frequency)); - break; - case PM_METRIC_GPU_MEM_TEMPERATURE: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::vram_temperature)); - break; - case PM_METRIC_GPU_MEM_USED: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_mem_used)); - break; - case PM_METRIC_GPU_MEM_UTILIZATION: - // Gpu mem utilization is derived from mem size and mem used. - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_mem_used)); - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_mem_size)); - break; - case PM_METRIC_GPU_MEM_WRITE_BANDWIDTH: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_mem_write_bandwidth)); - break; - case PM_METRIC_GPU_MEM_READ_BANDWIDTH: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_mem_read_bandwidth)); - break; - case PM_METRIC_GPU_POWER_LIMITED: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_power_limited)); - break; - case PM_METRIC_GPU_TEMPERATURE_LIMITED: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_temperature_limited)); - break; - case PM_METRIC_GPU_CURRENT_LIMITED: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_current_limited)); - break; - case PM_METRIC_GPU_VOLTAGE_LIMITED: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_voltage_limited)); - break; - case PM_METRIC_GPU_UTILIZATION_LIMITED: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_utilization_limited)); - break; - case PM_METRIC_GPU_MEM_POWER_LIMITED: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::vram_power_limited)); - break; - case PM_METRIC_GPU_MEM_TEMPERATURE_LIMITED: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::vram_temperature_limited)); - break; - case PM_METRIC_GPU_MEM_CURRENT_LIMITED: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::vram_current_limited)); - break; - case PM_METRIC_GPU_MEM_VOLTAGE_LIMITED: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::vram_voltage_limited)); - break; - case PM_METRIC_GPU_MEM_UTILIZATION_LIMITED: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::vram_utilization_limited)); - break; - case PM_METRIC_GPU_FAN_SPEED: - switch (qe.arrayIndex) - { - case 0: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::fan_speed_0)); - break; - case 1: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::fan_speed_1)); - break; - case 2: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::fan_speed_2)); - break; - case 3: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::fan_speed_3)); - break; - case 4: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::fan_speed_4)); - break; - } - break; - case PM_METRIC_GPU_FAN_SPEED_PERCENT: - switch (qe.arrayIndex) - { - case 0: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::max_fan_speed_0)); - break; - case 1: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::max_fan_speed_1)); - break; - case 2: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::max_fan_speed_2)); - break; - case 3: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::max_fan_speed_3)); - break; - case 4: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::max_fan_speed_4)); - break; - } - break; - case PM_METRIC_GPU_EFFECTIVE_FREQUENCY: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_effective_frequency)); - break; - case PM_METRIC_GPU_VOLTAGE_REGULATOR_TEMPERATURE: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_voltage_regulator_temperature)); - break; - case PM_METRIC_GPU_MEM_EFFECTIVE_BANDWIDTH: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_mem_effective_bandwidth)); - break; - case PM_METRIC_GPU_OVERVOLTAGE_PERCENT: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_overvoltage_percent)); - break; - case PM_METRIC_GPU_TEMPERATURE_PERCENT: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_temperature_percent)); - break; - case PM_METRIC_GPU_POWER_PERCENT: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_power_percent)); - break; - case PM_METRIC_GPU_CARD_POWER: - pQuery->accumGpuBits.set(static_cast(GpuTelemetryCapBits::gpu_card_power)); - break; - case PM_METRIC_CPU_UTILIZATION: - pQuery->accumCpuBits.set(static_cast(CpuTelemetryCapBits::cpu_utilization)); - break; - case PM_METRIC_CPU_POWER: - pQuery->accumCpuBits.set(static_cast(CpuTelemetryCapBits::cpu_power)); - break; - case PM_METRIC_CPU_TEMPERATURE: - pQuery->accumCpuBits.set(static_cast(CpuTelemetryCapBits::cpu_temperature)); - break; - case PM_METRIC_CPU_FREQUENCY: - pQuery->accumCpuBits.set(static_cast(CpuTelemetryCapBits::cpu_frequency)); - break; - case PM_METRIC_CPU_CORE_UTILITY: - //pQuery->accumCpuBits.set(static_cast(CpuTelemetryCapBits::cpu_power)); - break; - default: - break; - } - - qe.dataOffset = offset; - qe.dataSize = GetDataTypeSize(metricView.GetDataTypeInfo().GetPolledType()); - offset += qe.dataSize; - } - - pQuery->metricOffsetMs = metricOffsetMs; - pQuery->windowSizeMs = windowSizeMs; - pQuery->elements = std::vector{ queryElements.begin(), queryElements.end() }; - pQuery->queryCacheSize = pQuery->elements[std::size(pQuery->elements) - 1].dataOffset + pQuery->elements[std::size(pQuery->elements) - 1].dataSize; - if (cachedGpuInfoIndex.has_value()) - { - pQuery->cachedGpuInfoIndex = cachedGpuInfoIndex.value(); - } - - return pQuery.release(); - } - -namespace { - -struct FakePMTraceSession { - double mMilliSecondsPerTimestamp = 0.0; - - double TimestampDeltaToMilliSeconds(uint64_t qpcDelta) const - { - return mMilliSecondsPerTimestamp * qpcDelta; - } - - double TimestampDeltaToUnsignedMilliSeconds(uint64_t qpcFrom, uint64_t qpcTo) const { - return qpcFrom == 0 || qpcTo <= qpcFrom ? 0.0 : TimestampDeltaToMilliSeconds(qpcTo - qpcFrom); - } - - double TimestampDeltaToMilliSeconds(uint64_t qpcFrom, uint64_t qpcTo) const - { - return qpcFrom == 0 || qpcTo == 0 || qpcFrom == qpcTo ? 0.0 : - qpcTo > qpcFrom ? TimestampDeltaToMilliSeconds(qpcTo - qpcFrom) : - -TimestampDeltaToMilliSeconds(qpcFrom - qpcTo); - } -}; - -// Copied from: PresentMon/PresentMon.hpp -// Metrics computed per-frame. Duration and Latency metrics are in milliseconds. -struct FrameMetrics { - double mMsBetweenPresents; - double mMsInPresentApi; - double mMsUntilDisplayed; - double mMsBetweenDisplayChange; - double mMsUntilRenderComplete; - - uint64_t mCPUStart; - double mCPUBusy; - double mCPUWait; - double mGPULatency; - double mGPUBusy; - double mVideoBusy; - double mGPUWait; - double mDisplayLatency; - double mDisplayedTime; - double mAnimationError; - double mClickToPhotonLatency; - double mAllInputPhotonLatency; - FrameType mFrameType; - double mInstrumentedDisplayLatency; - double mPcLatency; - double mMsBetweenSimStarts; - - double mInstrumentedRenderLatency; - double mInstrumentedSleep; - double mInstrumentedGpuLatency; - double mInstrumentedReadyTimeToDisplayLatency; -}; - -// Copied from: PresentMon/OutputThread.cpp -void UpdateChain( - fpsSwapChainData* chain, - PmNsmPresentEvent const& p) -{ - if (p.FinalState == PresentResult::Presented) { - if (p.DisplayedCount > 0) { - if (p.Displayed_FrameType[p.DisplayedCount - 1] == FrameType::NotSet || - p.Displayed_FrameType[p.DisplayedCount - 1] == FrameType::Application) { - // If the chain animation error source has been set to either - // app provider or PCL latency then set the last displayed simulation start time and the - // first app simulation start time based on the animation error source type. - if (chain->mAnimationErrorSource == AnimationErrorSource::AppProvider) { - chain->mLastDisplayedSimStartTime = p.AppSimStartTime; - chain->mLastDisplayedAppScreenTime = p.Displayed_ScreenTime[p.DisplayedCount - 1]; - } else if (chain->mAnimationErrorSource == AnimationErrorSource::PCLatency) { - // In the case of PCLatency only set values if pcl sim start time is not zero. - if (p.PclSimStartTime != 0) { - chain->mLastDisplayedSimStartTime = p.PclSimStartTime; - chain->mLastDisplayedAppScreenTime = p.Displayed_ScreenTime[p.DisplayedCount - 1]; - } - } else { - // Currently sourcing animation error from CPU start time, however check - // to see if we have a valid app provider or PCL sim start time and set the - // new animation source and set the first app sim start time - if (p.AppSimStartTime != 0) { - chain->mAnimationErrorSource = AnimationErrorSource::AppProvider; - chain->mLastDisplayedSimStartTime = p.AppSimStartTime; - chain->mLastDisplayedAppScreenTime = p.Displayed_ScreenTime[p.DisplayedCount - 1]; - } else if (p.PclSimStartTime != 0) { - chain->mAnimationErrorSource = AnimationErrorSource::PCLatency; - chain->mLastDisplayedSimStartTime = p.PclSimStartTime; - chain->mLastDisplayedAppScreenTime = p.Displayed_ScreenTime[p.DisplayedCount - 1]; - } else { - if (chain->mLastAppPresentIsValid == true) { - chain->mLastDisplayedSimStartTime = chain->mLastAppPresent.PresentStartTime + - chain->mLastAppPresent.TimeInPresent; - } - chain->mLastDisplayedAppScreenTime = p.Displayed_ScreenTime[p.DisplayedCount - 1]; - } - } - } - } - // TODO: This used to be p.Displayed_ScreenTime[0]. That seems incorrect. - uint64_t lastDisplayedScreenTime = p.DisplayedCount == 0 ? 0 : - p.Displayed_ScreenTime[p.DisplayedCount - 1]; - - // IntelPresentMon specifics: - if (chain->display_count == 0) { - chain->display_0_screen_time = lastDisplayedScreenTime; - } - chain->mLastDisplayedScreenTime = lastDisplayedScreenTime; - chain->display_count += 1; + return nullptr; } - if (p.DisplayedCount > 0) { - if (p.Displayed_FrameType[p.DisplayedCount - 1] == FrameType::NotSet || - p.Displayed_FrameType[p.DisplayedCount - 1] == FrameType::Application) { - chain->mLastAppPresent = p; - chain->mLastAppPresentIsValid = true; - } - } else { - // If the displayed count is zero, assuming this is an application - // present - chain->mLastAppPresent = p; - chain->mLastAppPresentIsValid = true; - } - - // Set chain->mLastSimStartTime to either p->PclSimStartTime or p->AppSimStartTime depending on - // if either are not zero. If both are zero, do not set. - if (p.PclSimStartTime != 0) { - chain->mLastSimStartTime = p.PclSimStartTime; - } else if (p.AppSimStartTime != 0) { - chain->mLastSimStartTime = p.AppSimStartTime; - } - - chain->mLastPresent = p; - chain->mLastPresentIsValid = true; - chain->mIncludeFrameData = true; -} - -// Copied from: PresentMon/OutputThread.cpp -static void ReportMetricsHelper( - FakePMTraceSession const& pmSession, - InputToFsManager& pclI2FsManager, - fpsSwapChainData* chain, - PmNsmPresentEvent* p, - PmNsmPresentEvent const* nextDisplayedPresent) -{ - // Figure out what display index to start processing. - // - // The following cases are expected: - // p.Displayed empty and nextDisplayedPresent == nullptr: process p as not displayed - // p.Displayed with size N and nextDisplayedPresent == nullptr: process p.Displayed[0..N-2] as displayed, postponing N-1 - // p.Displayed with size N and nextDisplayedPresent != nullptr: process p.Displayed[N-1] as displayed - auto displayCount = p->DisplayedCount; - bool displayed = p->FinalState == PresentResult::Presented && displayCount > 0; - size_t displayIndex = displayed && nextDisplayedPresent != nullptr ? displayCount - 1 : 0; - - // Figure out what display index to attribute cpu work, gpu work, animation error, and input - // latency to. Start looking from the current display index. - size_t appIndex = std::numeric_limits::max(); - if (displayCount > 0) { - for (size_t i = displayIndex; i < displayCount; ++i) { - if (p->Displayed_FrameType[i] == FrameType::NotSet || - p->Displayed_FrameType[i] == FrameType::Application) { - appIndex = i; - break; - } - } - } else { - // If there are no displayed frames - appIndex = 0; - } - - do { - // PB = PresentStartTime - // PE = PresentEndTime - // D = ScreenTime - // - // chain->mLastPresent: PB--PE----D - // p: | PB--PE----D - // ... | | | | PB--PE - // nextDisplayedPresent: | | | | PB--PE----D - // | | | | | - // mCPUStart/mCPUBusy: |------->| | | | - // mCPUWait: |-->| | | - // mDisplayLatency: |----------------->| | - // mDisplayedTime: |---------------------->| - - // Lookup the ScreenTime and next ScreenTime - uint64_t screenTime = 0; - uint64_t nextScreenTime = 0; - if (displayed) { - screenTime = p->Displayed_ScreenTime[displayIndex]; - - if (displayIndex + 1 < displayCount) { - nextScreenTime = p->Displayed_ScreenTime[displayIndex + 1]; - } else if (nextDisplayedPresent != nullptr) { - nextScreenTime = nextDisplayedPresent->Displayed_ScreenTime[0]; - } else { - return; - } - } - - double msGPUDuration = 0.0; - - FrameMetrics metrics; - metrics.mCPUStart = 0; - - // Calculate these metrics for every present - metrics.mMsBetweenPresents = chain->mLastPresentIsValid == false ? 0 : - pmSession.TimestampDeltaToUnsignedMilliSeconds(chain->mLastPresent.PresentStartTime, p->PresentStartTime); - metrics.mMsInPresentApi = pmSession.TimestampDeltaToMilliSeconds(p->TimeInPresent); - metrics.mMsUntilRenderComplete = pmSession.TimestampDeltaToMilliSeconds(p->PresentStartTime, p->ReadyTime); - - if (chain->mLastAppPresentIsValid == true) { - if (chain->mLastAppPresent.AppPropagatedPresentStartTime != 0) { - metrics.mCPUStart = chain->mLastAppPresent.AppPropagatedPresentStartTime + chain->mLastAppPresent.AppPropagatedTimeInPresent; - } - else { - metrics.mCPUStart = chain->mLastAppPresent.PresentStartTime + chain->mLastAppPresent.TimeInPresent; - } - } else { - metrics.mCPUStart = chain->mLastPresentIsValid == false ? 0 : chain->mLastPresent.PresentStartTime + p->TimeInPresent; - } - - if (displayIndex == appIndex) { - if (p->AppPropagatedPresentStartTime != 0) { - msGPUDuration = pmSession.TimestampDeltaToUnsignedMilliSeconds(p->AppPropagatedGPUStartTime, p->AppPropagatedReadyTime); - metrics.mCPUBusy = pmSession.TimestampDeltaToUnsignedMilliSeconds(metrics.mCPUStart, p->AppPropagatedPresentStartTime); - metrics.mCPUWait = pmSession.TimestampDeltaToMilliSeconds(p->AppPropagatedTimeInPresent); - metrics.mGPULatency = pmSession.TimestampDeltaToUnsignedMilliSeconds(metrics.mCPUStart, p->AppPropagatedGPUStartTime); - metrics.mGPUBusy = pmSession.TimestampDeltaToMilliSeconds(p->AppPropagatedGPUDuration); - metrics.mVideoBusy = pmSession.TimestampDeltaToMilliSeconds(p->AppPropagatedGPUVideoDuration); - metrics.mGPUWait = std::max(0.0, msGPUDuration - metrics.mGPUBusy); - } else { - msGPUDuration = pmSession.TimestampDeltaToUnsignedMilliSeconds(p->GPUStartTime, p->ReadyTime); - metrics.mCPUBusy = pmSession.TimestampDeltaToUnsignedMilliSeconds(metrics.mCPUStart, p->PresentStartTime); - metrics.mCPUWait = pmSession.TimestampDeltaToMilliSeconds(p->TimeInPresent); - metrics.mGPULatency = pmSession.TimestampDeltaToUnsignedMilliSeconds(metrics.mCPUStart, p->GPUStartTime); - metrics.mGPUBusy = pmSession.TimestampDeltaToMilliSeconds(p->GPUDuration); - metrics.mVideoBusy = pmSession.TimestampDeltaToMilliSeconds(p->GPUVideoDuration); - metrics.mGPUWait = std::max(0.0, msGPUDuration - metrics.mGPUBusy); - } - // Need both AppSleepStart and AppSleepEnd to calculate XellSleep - metrics.mInstrumentedSleep = (p->AppSleepEndTime == 0 || p->AppSleepStartTime == 0) ? 0 : - pmSession.TimestampDeltaToUnsignedMilliSeconds(p->AppSleepStartTime, p->AppSleepEndTime); - // If there isn't a valid sleep end time use the sim start time - auto instrumentedStartTime = p->AppSleepEndTime != 0 ? p->AppSleepEndTime : p->AppSimStartTime; - // If neither the sleep end time or sim start time is valid, there is no - // way to calculate the Xell Gpu latency - metrics.mInstrumentedGpuLatency = instrumentedStartTime == 0 ? 0 : - pmSession.TimestampDeltaToUnsignedMilliSeconds(instrumentedStartTime, p->GPUStartTime); - - // If we have both a valid pcl sim start time and a valid app sim start time, we use the pcl sim start time. - if (p->PclSimStartTime != 0) { - metrics.mMsBetweenSimStarts = pmSession.TimestampDeltaToUnsignedMilliSeconds(chain->mLastSimStartTime, p->PclSimStartTime); - } - else if (p->AppSimStartTime != 0) { - metrics.mMsBetweenSimStarts = pmSession.TimestampDeltaToUnsignedMilliSeconds(chain->mLastSimStartTime, p->AppSimStartTime); - } - } else { - metrics.mCPUBusy = 0; - metrics.mCPUWait = 0; - metrics.mGPULatency = 0; - metrics.mGPUBusy = 0; - metrics.mVideoBusy = 0; - metrics.mGPUWait = 0; - metrics.mInstrumentedSleep = 0; - metrics.mInstrumentedGpuLatency = 0; - metrics.mMsBetweenSimStarts = 0; - } - - // If the frame was displayed regardless of how it was produced, calculate the following - // metrics - if (displayed) { - metrics.mDisplayLatency = pmSession.TimestampDeltaToUnsignedMilliSeconds(metrics.mCPUStart, screenTime); - metrics.mDisplayedTime = pmSession.TimestampDeltaToUnsignedMilliSeconds(screenTime, nextScreenTime); - metrics.mMsUntilDisplayed = pmSession.TimestampDeltaToUnsignedMilliSeconds(p->PresentStartTime, screenTime); - metrics.mMsBetweenDisplayChange = chain->mLastDisplayedScreenTime == 0 ? 0 : - pmSession.TimestampDeltaToUnsignedMilliSeconds(chain->mLastDisplayedScreenTime, screenTime); - - // If AppRenderSubmitStart is valid calculate the render latency - metrics.mInstrumentedRenderLatency = p->AppRenderSubmitStartTime == 0 ? 0 : - pmSession.TimestampDeltaToUnsignedMilliSeconds(p->AppRenderSubmitStartTime, screenTime); - metrics.mInstrumentedReadyTimeToDisplayLatency = pmSession.TimestampDeltaToUnsignedMilliSeconds(p->ReadyTime, screenTime); - // If there isn't a valid sleep end time use the sim start time - auto InstrumentedStartTime = p->AppSleepEndTime != 0 ? p->AppSleepEndTime : p->AppSimStartTime; - // If neither the sleep end time or sim start time is valid, there is no - // way to calculate the Xell Gpu latency - metrics.mInstrumentedDisplayLatency = InstrumentedStartTime == 0 ? 0 : - pmSession.TimestampDeltaToUnsignedMilliSeconds(InstrumentedStartTime, screenTime); - - metrics.mPcLatency = 0.f; - // Check to see if we have a valid pc latency sim start time - if (p->PclSimStartTime != 0) { - if (p->PclInputPingTime == 0) { - if (chain->mAccumulatedInput2FrameStartTime != 0) { - // This frame was displayed but we don't have a pc latency input time. However, there is accumulated time - // so there is a pending input that will now hit the screen. Add in the time from the last not - // displayed pc simulation start to this frame's pc simulation start. - chain->mAccumulatedInput2FrameStartTime += - pmSession.TimestampDeltaToUnsignedMilliSeconds(chain->mLastReceivedNotDisplayedPclSimStart, p->PclSimStartTime); - // Add all of the accumlated time to the average input to frame start time. - pclI2FsManager.AddI2FsValueForProcess( - p->ProcessId, - chain->mLastReceivedNotDisplayedPclInputTime, - chain->mAccumulatedInput2FrameStartTime); - // Reset the tracking variables for when we have a dropped frame with a pc latency input - chain->mAccumulatedInput2FrameStartTime = 0.f; - chain->mLastReceivedNotDisplayedPclSimStart = 0; - chain->mLastReceivedNotDisplayedPclInputTime = 0; - } - } else { - pclI2FsManager.AddI2FsValueForProcess( - p->ProcessId, - p->PclInputPingTime, - pmSession.TimestampDeltaToUnsignedMilliSeconds(p->PclInputPingTime, p->PclSimStartTime)); - } - } - // If we have a non-zero average input to frame start time and a PC Latency simulation - // start time calculate the PC Latency - auto i2Fs = pclI2FsManager.GetI2FsForProcess(p->ProcessId); - auto simStartTime = p->PclSimStartTime != 0 ? p->PclSimStartTime : chain->mLastSimStartTime; - if (i2Fs != 0.f && simStartTime != 0) { - metrics.mPcLatency = i2Fs + pmSession.TimestampDeltaToMilliSeconds(simStartTime, screenTime); - } - } else { - metrics.mDisplayLatency = 0; - metrics.mDisplayedTime = 0; - metrics.mMsUntilDisplayed = 0; - metrics.mMsBetweenDisplayChange = 0; - metrics.mInstrumentedRenderLatency = 0; - metrics.mInstrumentedReadyTimeToDisplayLatency = 0; - metrics.mInstrumentedDisplayLatency = 0; - metrics.mPcLatency = 0; - if (p->PclSimStartTime != 0) { - if (p->PclInputPingTime != 0) { - // This frame was dropped but we have valid pc latency input and simulation start - // times. Calculate the initial input to sim start time. - chain->mAccumulatedInput2FrameStartTime = - pmSession.TimestampDeltaToUnsignedMilliSeconds(p->PclInputPingTime, p->PclSimStartTime); - chain->mLastReceivedNotDisplayedPclInputTime = p->PclInputPingTime; - } else if (chain->mAccumulatedInput2FrameStartTime != 0.f) { - // This frame was also dropped and there is no pc latency input time. However, since we have - // accumulated time this means we have a pending input that has had multiple dropped frames - // and has not yet hit the screen. Calculate the time between the last not displayed sim start and - // this sim start and add it to our accumulated total - chain->mAccumulatedInput2FrameStartTime += - pmSession.TimestampDeltaToUnsignedMilliSeconds(chain->mLastReceivedNotDisplayedPclSimStart, p->PclSimStartTime); - } - chain->mLastReceivedNotDisplayedPclSimStart = p->PclSimStartTime; - } - } - - // The following metrics use both the frame's displayed and origin information. - metrics.mClickToPhotonLatency = 0; - metrics.mAllInputPhotonLatency = 0; - metrics.mAnimationError = 0; - - if (displayIndex == appIndex) { - if (displayed) { - // For all input device metrics check to see if there were any previous device input times - // that were attached to a dropped frame and if so use the last received times for the - // metric calculations - auto updatedInputTime = chain->mLastReceivedNotDisplayedAllInputTime == 0 ? 0 : - pmSession.TimestampDeltaToUnsignedMilliSeconds(chain->mLastReceivedNotDisplayedAllInputTime, screenTime); - metrics.mAllInputPhotonLatency = p->InputTime == 0 ? updatedInputTime : - pmSession.TimestampDeltaToUnsignedMilliSeconds(p->InputTime, screenTime); - - updatedInputTime = chain->mLastReceivedNotDisplayedMouseClickTime == 0 ? 0 : - pmSession.TimestampDeltaToUnsignedMilliSeconds(chain->mLastReceivedNotDisplayedMouseClickTime, screenTime); - metrics.mClickToPhotonLatency = p->MouseClickTime == 0 ? updatedInputTime : - pmSession.TimestampDeltaToUnsignedMilliSeconds(p->MouseClickTime, screenTime); - - // Reset all last received device times - chain->mLastReceivedNotDisplayedAllInputTime = 0; - chain->mLastReceivedNotDisplayedMouseClickTime = 0; - - // Next calculate the animation error and animation time. First calculate the simulation - // start time. Simulation start can be either an app provided sim start time via the provider or - // PCL stats or, if not present,the cpu start. - uint64_t simStartTime = 0; - if (chain->mAnimationErrorSource == AnimationErrorSource::PCLatency) { - // If the pcl latency is the source of the animation error then use the pcl sim start time. - simStartTime = p->PclSimStartTime; - } - else if (chain->mAnimationErrorSource == AnimationErrorSource::AppProvider) { - // If the app provider is the source of the animation error then use the app sim start time. - simStartTime = p->AppSimStartTime; - } - else if (chain->mAnimationErrorSource == AnimationErrorSource::CpuStart) { - // If the cpu start time is the source of the animation error then use the cpu start time. - simStartTime = metrics.mCPUStart; - } - - if (chain->mLastDisplayedSimStartTime != 0) { - // If the simulation start time is less than the last displayed simulation start time it means - // we are transitioning to app provider events. - if (simStartTime > chain->mLastDisplayedSimStartTime) { - metrics.mAnimationError = pmSession.TimestampDeltaToMilliSeconds(screenTime - chain->mLastDisplayedAppScreenTime, - simStartTime - chain->mLastDisplayedSimStartTime); - chain->mAnimationError.push_back(std::abs(metrics.mAnimationError)); - } - } - } - else { - if (p->InputTime != 0) { - chain->mLastReceivedNotDisplayedAllInputTime = p->InputTime; - } - if (p->MouseClickTime != 0) { - chain->mLastReceivedNotDisplayedMouseClickTime = p->MouseClickTime; - } - } - } - - if (p->DisplayedCount == 0) { - metrics.mFrameType = FrameType::NotSet; - } else { - metrics.mFrameType = p->Displayed_FrameType[displayIndex]; - } - - // Push back all Present() information regardless of the source and if - // the present was displayed - chain->mMsBetweenPresents.push_back(metrics.mMsBetweenPresents); - chain->mMsInPresentApi.push_back(metrics.mMsInPresentApi); - chain->mMsUntilRenderComplete.push_back(metrics.mMsUntilRenderComplete); - - // Push back application based metrics regardless if the present was displayed - if (displayIndex == appIndex) { - chain->mCPUBusy .push_back(metrics.mCPUBusy); - chain->mCPUWait .push_back(metrics.mCPUWait); - chain->mGPULatency .push_back(metrics.mGPULatency); - chain->mGPUBusy .push_back(metrics.mGPUBusy); - chain->mVideoBusy .push_back(metrics.mVideoBusy); - chain->mGPUWait .push_back(metrics.mGPUWait); - chain->mInstrumentedSleep .push_back(metrics.mInstrumentedSleep); - chain->mInstrumentedGpuLatency.push_back(metrics.mInstrumentedGpuLatency); - } - - // Push back all Display() information regardless of the source - if (displayed) { - chain->mDisplayLatency .push_back(metrics.mDisplayLatency); - chain->mDisplayedTime .push_back(metrics.mDisplayedTime); - chain->mMsUntilDisplayed .push_back(metrics.mMsUntilDisplayed); - chain->mDropped .push_back(0.0); - if (metrics.mMsBetweenDisplayChange != 0) { - // Only push back the mMsBetweenDisplayChange if it is non-zero. - // mMsBetweenDisplayChange will be zero on the first use of the incoming - // swap chain parameter. - chain->mMsBetweenDisplayChange.push_back(metrics.mMsBetweenDisplayChange); - } - } else { - chain->mDropped .push_back(1.0); - } - - if (displayed) { - if (chain->mAppDisplayedTime.empty() || displayIndex == appIndex) { - chain->mAppDisplayedTime.push_back(metrics.mDisplayedTime); - } - else { - chain->mAppDisplayedTime.back() += metrics.mDisplayedTime; - } - } else { - chain->mDropped .push_back(1.0); - } - - if (displayed && displayIndex == appIndex) { - if (metrics.mAllInputPhotonLatency != 0) { - chain->mAllInputToPhotonLatency.push_back(metrics.mAllInputPhotonLatency); - } - if (metrics.mClickToPhotonLatency != 0) { - chain->mClickToPhotonLatency.push_back(metrics.mClickToPhotonLatency); - } - if (metrics.mInstrumentedRenderLatency != 0) { - chain->mInstrumentedRenderLatency.push_back(metrics.mInstrumentedRenderLatency); - } - if (metrics.mInstrumentedDisplayLatency != 0) { - chain->mInstrumentedDisplayLatency.push_back(metrics.mInstrumentedDisplayLatency); - } - if (metrics.mInstrumentedReadyTimeToDisplayLatency != 0) { - chain->mInstrumentedReadyTimeToDisplayLatency.push_back(metrics.mInstrumentedReadyTimeToDisplayLatency); - } - if (metrics.mPcLatency != 0) { - chain->mMsPcLatency.push_back(metrics.mPcLatency); - } - } - - displayIndex += 1; - } while (displayIndex < displayCount); - - UpdateChain(chain, *p); -} - -static void ReportMetrics( - FakePMTraceSession const& pmSession, - InputToFsManager& pclI2FsManager, - fpsSwapChainData* chain, - PmNsmPresentEvent* p) -{ - // For the chain's first present, we just initialize mLastPresent to give a baseline for the - // first frame. - if (!chain->mLastPresentIsValid) { - UpdateChain(chain, *p); - return; - } - - // If chain->mPendingPresents is non-empty, then it contains a displayed present followed by - // some number of discarded presents. If the displayed present has multiple Displayed entries, - // all but the last have already been handled. - // - // If p is displayed, then we can complete all pending presents, and complete any flips in p - // except for the last one, but then we have to add p to the pending list to wait for the next - // displayed frame. - // - // If p is not displayed, we can process it now unless it is blocked behind an earlier present - // waiting for the next displayed one, in which case we need to add it to the pending list as - // well. - if (p->FinalState == PresentResult::Presented) { - for (auto& p2 : chain->mPendingPresents) { - ReportMetricsHelper(pmSession, pclI2FsManager, chain, &p2, p); - } - ReportMetricsHelper(pmSession, pclI2FsManager, chain, p, nullptr); - chain->mPendingPresents.clear(); - chain->mPendingPresents.push_back(*p); - } else { - if (chain->mPendingPresents.empty()) { - ReportMetricsHelper(pmSession, pclI2FsManager, chain, p, nullptr); - } else { - chain->mPendingPresents.push_back(*p); - } - } -} - -} - void ConcreteMiddleware::PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t* numSwapChains) { - std::unordered_map swapChainData; - std::unordered_map metricInfo; - bool allMetricsCalculated = false; - bool fpsMetricsCalculated = false; - - if (*numSwapChains == 0) { - return; - } - - if (pQuery->cachedGpuInfoIndex.has_value()) - { - if (pQuery->cachedGpuInfoIndex.value() != currentGpuInfoIndex) - { - // Set the adapter id - SetActiveGraphicsAdapter(cachedGpuInfo[pQuery->cachedGpuInfoIndex.value()].deviceId); - // Set the current index to the queried one - currentGpuInfoIndex = pQuery->cachedGpuInfoIndex.value(); - } - } - - auto iter = presentMonStreamClients.find(processId); - if (iter == presentMonStreamClients.end()) { - return; - } - - // Get the named shared memory associated with the stream client - StreamClient* client = iter->second.get(); - auto nsm_view = client->GetNamedSharedMemView(); - auto nsm_hdr = nsm_view->GetHeader(); - if (!nsm_hdr->process_active) { - // TODO: Do we want to inform the client if the server has destroyed the - // named shared memory? - // Server destroyed the named shared memory due to process exit. Destroy the - // mapped view from client side. - //StopStreamProcess(process_id); - //return PM_STATUS::PM_STATUS_PROCESS_NOT_EXIST; - return; - } - - uint64_t index = 0; - double adjusted_window_size_in_ms = pQuery->windowSizeMs; - auto result = queryFrameDataDeltas.emplace(std::pair(std::pair(pQuery, processId), uint64_t())); - auto queryToFrameDataDelta = &result.first->second; - - PmNsmFrameData* frame_data = GetFrameDataStart(client, index, SecondsDeltaToQpc(pQuery->metricOffsetMs/1000., client->GetQpcFrequency()), *queryToFrameDataDelta, adjusted_window_size_in_ms); - if (frame_data == nullptr) { - pmlog_warn("Filling cached data in dynamic metric poll due to nullptr from GetFrameDataStart").diag(); - CopyMetricCacheToBlob(pQuery, processId, pBlob); - return; - } - - // Calculate the end qpc based on the current frame's qpc and - // requested window size coverted to a qpc - // then loop from the most recent frame data until we either run out of data or - // we meet the window size requirements sent in by the client - uint64_t end_qpc = - frame_data->present_event.PresentStartTime - - SecondsDeltaToQpc(adjusted_window_size_in_ms/1000., client->GetQpcFrequency()); - - std::vector frames; - while (frame_data->present_event.PresentStartTime > end_qpc) { - frames.push_back(frame_data); - - // Get the index of the next frame - if (DecrementIndex(nsm_view, index) == false) { - // We have run out of data to process, time to go - break; - } - frame_data = client->ReadFrameByIdx(index, true); - if (frame_data == nullptr) { - break; - } - } - - FakePMTraceSession pmSession; - pmSession.mMilliSecondsPerTimestamp = 1000.0 / client->GetQpcFrequency().QuadPart; - - for (const auto& frame_data : frames | std::views::reverse) { - if (pQuery->accumFpsData) - { - auto result = swapChainData.emplace( - frame_data->present_event.SwapChainAddress, fpsSwapChainData()); - auto swap_chain = &result.first->second; - - auto presentEvent = &frame_data->present_event; - auto chain = swap_chain; - - // The following code block copied from: PresentMon/OutputThread.cpp - if (chain->mLastPresentIsValid) { - ReportMetrics(pmSession, mPclI2FsManager, chain, presentEvent); - } else { - pmon::mid::UpdateChain(chain, *presentEvent); - } - // end - } - - for (size_t i = 0; i < pQuery->accumGpuBits.size(); ++i) { - if (pQuery->accumGpuBits[i]) - { - GetGpuMetricData(i, frame_data->power_telemetry, metricInfo); - } - } - - for (size_t i = 0; i < pQuery->accumCpuBits.size(); ++i) { - if (pQuery->accumCpuBits[i]) - { - GetCpuMetricData(i, frame_data->cpu_telemetry, metricInfo); - } - } - } - - CalculateMetrics(pQuery, processId, pBlob, numSwapChains, client->GetQpcFrequency(), swapChainData, metricInfo); - } - - std::optional ConcreteMiddleware::GetCachedGpuInfoIndex(uint32_t deviceId) - { - for (std::size_t i = 0; i < cachedGpuInfo.size(); ++i) - { - if (cachedGpuInfo[i].deviceId == deviceId) - { - if (cachedGpuInfo[i].adapterId.has_value()) - return cachedGpuInfo[i].adapterId.value(); - else { - return std::nullopt; - } - } - } - - return std::nullopt; - } - - void ConcreteMiddleware::CopyStaticMetricData(PM_METRIC metric, uint32_t deviceId, uint8_t* pBlob, uint64_t blobOffset, size_t sizeInBytes) - { - switch (metric) - { - case PM_METRIC_CPU_NAME: - { - strncpy_s(reinterpret_cast(&pBlob[blobOffset]), sizeInBytes, cachedCpuInfo[0].deviceName.c_str(), _TRUNCATE); - } - break; - case PM_METRIC_CPU_VENDOR: - { - auto& output = reinterpret_cast(pBlob[blobOffset]); - output = cachedCpuInfo[0].deviceVendor; - } - break; - case PM_METRIC_CPU_POWER_LIMIT: - { - auto& output = reinterpret_cast(pBlob[blobOffset]); - output = cachedCpuInfo[0].cpuPowerLimit.has_value() ? cachedCpuInfo[0].cpuPowerLimit.value() : 0.; - } - break; - case PM_METRIC_GPU_NAME: - { - auto index = GetCachedGpuInfoIndex(deviceId); - if (index.has_value()) - { - strncpy_s(reinterpret_cast(&pBlob[blobOffset]), sizeInBytes, cachedGpuInfo[index.value()].deviceName.c_str(), _TRUNCATE); - } - } - break; - case PM_METRIC_GPU_VENDOR: - { - auto index = GetCachedGpuInfoIndex(deviceId); - auto& output = reinterpret_cast(pBlob[blobOffset]); - output = index.has_value() ? cachedGpuInfo[index.value()].deviceVendor : PM_DEVICE_VENDOR_UNKNOWN; - } - break; - case PM_METRIC_GPU_MEM_MAX_BANDWIDTH: - { - auto& output = reinterpret_cast(pBlob[blobOffset]); - auto index = GetCachedGpuInfoIndex(deviceId); - if (index.has_value()) - { - output = cachedGpuInfo[index.value()].gpuMemoryMaxBandwidth.has_value() ? - cachedGpuInfo[index.value()].gpuMemoryMaxBandwidth.value() : 0.; - } - else - { - output = 0.; - } - } - break; - case PM_METRIC_GPU_MEM_SIZE: - { - auto& output = reinterpret_cast(pBlob[blobOffset]); - auto index = GetCachedGpuInfoIndex(deviceId); - if (index.has_value()) - { - output = cachedGpuInfo[index.value()].gpuMemorySize.has_value() ? - static_cast(cachedGpuInfo[index.value()].gpuMemorySize.value()) : 0.; - } - else - { - output = 0.; - } - } - break; - case PM_METRIC_GPU_SUSTAINED_POWER_LIMIT: - { - auto& output = reinterpret_cast(pBlob[blobOffset]); - auto index = GetCachedGpuInfoIndex(deviceId); - if (index.has_value()) - { - output = cachedGpuInfo[index.value()].gpuSustainedPowerLimit.has_value() ? - cachedGpuInfo[index.value()].gpuSustainedPowerLimit.value() : 0.f; - } - else - { - output = 0.f; - } - } - break; - default: - break; - } - return; } void ConcreteMiddleware::PollStaticQuery(const PM_QUERY_ELEMENT& element, uint32_t processId, uint8_t* pBlob) @@ -1305,7 +226,7 @@ static void ReportMetrics( PM_FRAME_QUERY* mid::ConcreteMiddleware::RegisterFrameEventQuery(std::span queryElements, uint32_t& blobSize) { - auto pQuery = new PM_FRAME_QUERY{ queryElements, *this, *pComms, GetIntrospectionRoot() }; + auto pQuery = new PM_FRAME_QUERY{ queryElements, *this, *pComms, GetIntrospectionRoot_() }; blobSize = (uint32_t)pQuery->GetBlobSize(); return pQuery; } @@ -1349,573 +270,6 @@ static void ReportMetrics( return pActionClient->DispatchSync(FinishEtlLogging::Params{ etlLogSessionHandle }).etlFilePath; } - void ConcreteMiddleware::CalculateFpsMetric(fpsSwapChainData& swapChain, const PM_QUERY_ELEMENT& element, uint8_t* pBlob, LARGE_INTEGER qpcFrequency) - { - auto& output = reinterpret_cast(pBlob[element.dataOffset]); - - switch (element.metric) - { - case PM_METRIC_APPLICATION: - strncpy_s(reinterpret_cast(&pBlob[element.dataOffset]), 260, swapChain.mLastPresent.application, _TRUNCATE); - break; - case PM_METRIC_PRESENT_MODE: - reinterpret_cast(pBlob[element.dataOffset]) = (PM_PRESENT_MODE)swapChain.mLastPresent.PresentMode; - break; - case PM_METRIC_PRESENT_RUNTIME: - reinterpret_cast(pBlob[element.dataOffset]) = (PM_GRAPHICS_RUNTIME)swapChain.mLastPresent.Runtime; - break; - case PM_METRIC_PRESENT_FLAGS: - reinterpret_cast(pBlob[element.dataOffset]) = swapChain.mLastPresent.PresentFlags; - break; - case PM_METRIC_SYNC_INTERVAL: - reinterpret_cast(pBlob[element.dataOffset]) = swapChain.mLastPresent.SyncInterval; - break; - case PM_METRIC_ALLOWS_TEARING: - reinterpret_cast(pBlob[element.dataOffset]) = swapChain.mLastPresent.SupportsTearing; - break; - case PM_METRIC_FRAME_TYPE: - reinterpret_cast(pBlob[element.dataOffset]) = (PM_FRAME_TYPE)(swapChain.mLastPresent.DisplayedCount == 0 ? FrameType::NotSet : swapChain.mLastPresent.Displayed_FrameType[0]); - break; - case PM_METRIC_CPU_BUSY: - output = CalculateStatistic(swapChain.mCPUBusy, element.stat); - break; - case PM_METRIC_CPU_WAIT: - output = CalculateStatistic(swapChain.mCPUWait, element.stat); - break; - case PM_METRIC_CPU_FRAME_TIME: - { - std::vector frame_times(swapChain.mCPUBusy.size()); - for (size_t i = 0; i < swapChain.mCPUBusy.size(); ++i) { - frame_times[i] = swapChain.mCPUBusy[i] + swapChain.mCPUWait[i]; - } - output = CalculateStatistic(frame_times, element.stat); - break; - } - case PM_METRIC_GPU_LATENCY: - output = CalculateStatistic(swapChain.mGPULatency, element.stat); - break; - case PM_METRIC_GPU_BUSY: - output = CalculateStatistic(swapChain.mGPUBusy, element.stat); - break; - case PM_METRIC_GPU_WAIT: - output = CalculateStatistic(swapChain.mGPUWait, element.stat); - break; - case PM_METRIC_GPU_TIME: - { - std::vector gpu_duration(swapChain.mGPUBusy.size()); - for (size_t i = 0; i < swapChain.mGPUBusy.size(); ++i) { - gpu_duration[i] = swapChain.mGPUBusy[i] + swapChain.mGPUWait[i]; - } - output = CalculateStatistic(gpu_duration, element.stat); - break; - } - case PM_METRIC_DISPLAY_LATENCY: - output = CalculateStatistic(swapChain.mDisplayLatency, element.stat); - break; - case PM_METRIC_DISPLAYED_TIME: - output = CalculateStatistic(swapChain.mDisplayedTime, element.stat); - break; - case PM_METRIC_ANIMATION_ERROR: - output = CalculateStatistic(swapChain.mAnimationError, element.stat); - break; - case PM_METRIC_PRESENTED_FPS: - { - output = CalculateStatistic(swapChain.mMsBetweenPresents, element.stat, true); - output = output == 0 ? 0 : 1000.0 / output; - break; - } - case PM_METRIC_APPLICATION_FPS: - { - std::vector frame_times(swapChain.mCPUBusy.size()); - for (size_t i = 0; i < swapChain.mCPUBusy.size(); ++i) { - frame_times[i] = swapChain.mCPUBusy[i] + swapChain.mCPUWait[i]; - } - output = CalculateStatistic(frame_times, element.stat); - output = output == 0 ? 0 : 1000.0 / output; - break; - } - case PM_METRIC_DISPLAYED_FPS: - { - output = CalculateStatistic(swapChain.mMsBetweenDisplayChange, element.stat, true); - output = output == 0 ? 0 : 1000.0 / output; - break; - } - case PM_METRIC_DROPPED_FRAMES: - output = CalculateStatistic(swapChain.mDropped, element.stat); - break; - case PM_METRIC_CLICK_TO_PHOTON_LATENCY: - output = CalculateStatistic(swapChain.mClickToPhotonLatency, element.stat); - break; - case PM_METRIC_ALL_INPUT_TO_PHOTON_LATENCY: - output = CalculateStatistic(swapChain.mAllInputToPhotonLatency, element.stat); - break; - case PM_METRIC_INSTRUMENTED_LATENCY: - output = CalculateStatistic(swapChain.mInstrumentedDisplayLatency, element.stat); - break; - case PM_METRIC_BETWEEN_PRESENTS: - output = CalculateStatistic(swapChain.mMsBetweenPresents, element.stat); - break; - case PM_METRIC_IN_PRESENT_API: - output = CalculateStatistic(swapChain.mMsInPresentApi, element.stat); - break; - case PM_METRIC_UNTIL_DISPLAYED: - output = CalculateStatistic(swapChain.mMsUntilDisplayed, element.stat); - break; - case PM_METRIC_BETWEEN_DISPLAY_CHANGE: - output = CalculateStatistic(swapChain.mMsBetweenDisplayChange, element.stat); - break; - case PM_METRIC_RENDER_PRESENT_LATENCY: - output = CalculateStatistic(swapChain.mMsUntilRenderComplete, element.stat); - break; - case PM_METRIC_BETWEEN_SIMULATION_START: - output = CalculateStatistic(swapChain.mMsBetweenSimStarts, element.stat); - break; - case PM_METRIC_PC_LATENCY: - output = CalculateStatistic(swapChain.mMsPcLatency, element.stat); - break; - case PM_METRIC_PRESENTED_FRAME_TIME: - output = CalculateStatistic(swapChain.mMsBetweenPresents, element.stat); - break; - case PM_METRIC_DISPLAYED_FRAME_TIME: - output = CalculateStatistic(swapChain.mMsBetweenDisplayChange, element.stat); - break; - default: - output = 0.; - break; - } - } - - void ConcreteMiddleware::CalculateGpuCpuMetric(std::unordered_map& metricInfo, const PM_QUERY_ELEMENT& element, uint8_t* pBlob) - { - auto& output = reinterpret_cast(pBlob[element.dataOffset]); - output = 0.; - - auto it = metricInfo.find(element.metric); - if (it != metricInfo.end()) - { - MetricInfo& mi = it->second; - auto it2 = mi.data.find(element.arrayIndex); - if (it2 != mi.data.end()) - { - output = CalculateStatistic(it2->second, element.stat); - } - } - return; - } - - double ConcreteMiddleware::CalculateStatistic(std::vector& inData, PM_STAT stat, bool invert) const - { - if (inData.size() == 1) { - return inData[0]; - } - - if (inData.size() >= 1) { - switch (stat) { - case PM_STAT_NONE: - break; - case PM_STAT_AVG: - { - double sum = 0.0; - for (auto element : inData) { - sum += element; - } - return sum / inData.size(); - } - case PM_STAT_PERCENTILE_99: return CalculatePercentile(inData, 0.99, invert); - case PM_STAT_PERCENTILE_95: return CalculatePercentile(inData, 0.95, invert); - case PM_STAT_PERCENTILE_90: return CalculatePercentile(inData, 0.90, invert); - case PM_STAT_PERCENTILE_01: return CalculatePercentile(inData, 0.01, invert); - case PM_STAT_PERCENTILE_05: return CalculatePercentile(inData, 0.05, invert); - case PM_STAT_PERCENTILE_10: return CalculatePercentile(inData, 0.10, invert); - case PM_STAT_MAX: - { - double max = inData[0]; - for (size_t i = 1; i < inData.size(); ++i) { - if (invert) { - max = std::min(max, inData[i]); - } else { - max = std::max(max, inData[i]); - } - } - return max; - } - case PM_STAT_MIN: - { - double min = inData[0]; - for (size_t i = 1; i < inData.size(); ++i) { - if (invert) { - min = std::max(min, inData[i]); - } - else { - min = std::min(min, inData[i]); - } - } - return min; - } - case PM_STAT_MID_POINT: - { - size_t middle_index = inData.size() / 2; - return inData[middle_index]; - } - case PM_STAT_MID_LERP: - // TODO: Not yet implemented - break; - case PM_STAT_NEWEST_POINT: - // TODO: Not yet implemented - break; - case PM_STAT_OLDEST_POINT: - // TODO: Not yet implemented - break; - case PM_STAT_COUNT: - // TODO: Not yet implemented - break; - case PM_STAT_NON_ZERO_AVG: - { - double sum = 0.0; - size_t num = 0; - for (auto element : inData) { - sum += element; - num += element == 0.0 ? 0 : 1; - } - return num == 0 ? 0.0 : sum / num; - } - } - } - - return 0.0; - } - - // Calculate percentile using linear interpolation between the closet ranks - double ConcreteMiddleware::CalculatePercentile(std::vector& inData, double percentile, bool invert) const - { - if (invert) { - percentile = 1.0 - percentile; - } - percentile = std::min(std::max(percentile, 0.), 1.); - - double integral_part_as_double; - double fractpart = - modf(percentile * static_cast(inData.size()), - &integral_part_as_double); - - uint32_t idx = static_cast(integral_part_as_double); - if (idx >= inData.size() - 1) { - return CalculateStatistic(inData, PM_STAT_MAX); - } - - std::sort(inData.begin(), inData.end()); - return inData[idx] + (fractpart * (inData[idx + 1] - inData[idx])); - } - - PmNsmFrameData* ConcreteMiddleware::GetFrameDataStart(StreamClient* client, uint64_t& index, uint64_t queryMetricsDataOffset, uint64_t& queryFrameDataDelta, double& window_sample_size_in_ms) - { - - PmNsmFrameData* frame_data = nullptr; - index = 0; - if (client == nullptr) { - return nullptr; - } - - auto nsm_view = client->GetNamedSharedMemView(); - auto nsm_hdr = nsm_view->GetHeader(); - if (!nsm_hdr->process_active) { - return nullptr; - } - - index = client->GetLatestFrameIndex(); - frame_data = client->ReadFrameByIdx(index, true); - if (frame_data == nullptr) { - index = 0; - return nullptr; - } - - if (queryMetricsDataOffset == 0) { - // Client has not specified a metric offset. Return back the most - // most recent frame data - return frame_data; - } - - LARGE_INTEGER client_qpc = {}; - QueryPerformanceCounter(&client_qpc); - uint64_t adjusted_qpc = GetAdjustedQpc( - client_qpc.QuadPart, frame_data->present_event.PresentStartTime, - queryMetricsDataOffset, client->GetQpcFrequency(), queryFrameDataDelta); - - if (adjusted_qpc > frame_data->present_event.PresentStartTime) { - // Need to adjust the size of the window sample size - double ms_adjustment = - QpcDeltaToMs(adjusted_qpc - frame_data->present_event.PresentStartTime, - client->GetQpcFrequency()); - window_sample_size_in_ms = window_sample_size_in_ms - ms_adjustment; - if (window_sample_size_in_ms <= 0.0) { - return nullptr; - } - pmlog_dbg("Adjusting dynamic stats window due to possible excursion").pmwatch(ms_adjustment); - } - else { - // Find the frame with the appropriate time based on the adjusted - // qpc - for (;;) { - - if (DecrementIndex(nsm_view, index) == false) { - // Increment index to match up with the frame_data read below - index++; - break; - } - frame_data = client->ReadFrameByIdx(index, true); - if (frame_data == nullptr) { - return nullptr; - } - if (adjusted_qpc >= frame_data->present_event.PresentStartTime) { - break; - } - } - } - - return frame_data; - } - - uint64_t ConcreteMiddleware::GetAdjustedQpc(uint64_t current_qpc, uint64_t frame_data_qpc, uint64_t queryMetricsOffset, LARGE_INTEGER frequency, uint64_t& queryFrameDataDelta) { - // Calculate how far behind the frame data qpc is compared - // to the client qpc - uint64_t current_qpc_delta = current_qpc - frame_data_qpc; - if (queryFrameDataDelta == 0) { - queryFrameDataDelta = current_qpc_delta; - } - else { - if (_abs64(queryFrameDataDelta - current_qpc_delta) > - kClientFrameDeltaQPCThreshold) { - queryFrameDataDelta = current_qpc_delta; - } - } - - // Add in the client set metric offset in qpc ticks - return current_qpc - - (queryFrameDataDelta + queryMetricsOffset); - } - - bool ConcreteMiddleware::DecrementIndex(NamedSharedMem* nsm_view, uint64_t& index) { - - if (nsm_view == nullptr) { - return false; - } - - auto nsm_hdr = nsm_view->GetHeader(); - if (!nsm_hdr->process_active) { - return false; - } - - uint64_t current_max_entries = - (nsm_view->IsFull()) ? nsm_hdr->max_entries - 1 : nsm_hdr->tail_idx; - index = (index == 0) ? current_max_entries : index - 1; - if (index == nsm_hdr->head_idx) { - return false; - } - - return true; - } - - bool ConcreteMiddleware::GetGpuMetricData(size_t telemetry_item_bit, PresentMonPowerTelemetryInfo& power_telemetry_info, std::unordered_map& metricInfo) - { - bool validGpuMetric = true; - GpuTelemetryCapBits bit = - static_cast(telemetry_item_bit); - switch (bit) { - case GpuTelemetryCapBits::time_stamp: - // This is a valid telemetry cap bit but we do not produce metrics for - // it. - validGpuMetric = false; - break; - case GpuTelemetryCapBits::gpu_power: - metricInfo[PM_METRIC_GPU_POWER].data[0].emplace_back(power_telemetry_info.gpu_power_w); - break; - case GpuTelemetryCapBits::gpu_voltage: - metricInfo[PM_METRIC_GPU_VOLTAGE].data[0].emplace_back(power_telemetry_info.gpu_voltage_v); - break; - case GpuTelemetryCapBits::gpu_frequency: - metricInfo[PM_METRIC_GPU_FREQUENCY].data[0].emplace_back(power_telemetry_info.gpu_frequency_mhz); - break; - case GpuTelemetryCapBits::gpu_temperature: - metricInfo[PM_METRIC_GPU_TEMPERATURE].data[0].emplace_back(power_telemetry_info.gpu_temperature_c); - break; - case GpuTelemetryCapBits::gpu_utilization: - metricInfo[PM_METRIC_GPU_UTILIZATION].data[0].emplace_back(power_telemetry_info.gpu_utilization); - break; - case GpuTelemetryCapBits::gpu_render_compute_utilization: - metricInfo[PM_METRIC_GPU_RENDER_COMPUTE_UTILIZATION].data[0].emplace_back(power_telemetry_info.gpu_render_compute_utilization); - break; - case GpuTelemetryCapBits::gpu_media_utilization: - metricInfo[PM_METRIC_GPU_MEDIA_UTILIZATION].data[0].emplace_back(power_telemetry_info.gpu_media_utilization); - break; - case GpuTelemetryCapBits::vram_power: - metricInfo[PM_METRIC_GPU_MEM_POWER].data[0].emplace_back(power_telemetry_info.vram_power_w); - break; - case GpuTelemetryCapBits::vram_voltage: - metricInfo[PM_METRIC_GPU_MEM_VOLTAGE].data[0].emplace_back(power_telemetry_info.vram_voltage_v); - break; - case GpuTelemetryCapBits::vram_frequency: - metricInfo[PM_METRIC_GPU_MEM_FREQUENCY].data[0].emplace_back(power_telemetry_info.vram_frequency_mhz); - break; - case GpuTelemetryCapBits::vram_effective_frequency: - metricInfo[PM_METRIC_GPU_MEM_EFFECTIVE_FREQUENCY].data[0].emplace_back(power_telemetry_info.vram_effective_frequency_gbps); - break; - case GpuTelemetryCapBits::vram_temperature: - metricInfo[PM_METRIC_GPU_MEM_TEMPERATURE].data[0].emplace_back(power_telemetry_info.vram_temperature_c); - break; - case GpuTelemetryCapBits::fan_speed_0: - metricInfo[PM_METRIC_GPU_FAN_SPEED].data[0].emplace_back(power_telemetry_info.fan_speed_rpm[0]); - break; - case GpuTelemetryCapBits::fan_speed_1: - metricInfo[PM_METRIC_GPU_FAN_SPEED].data[1].emplace_back(power_telemetry_info.fan_speed_rpm[1]); - break; - case GpuTelemetryCapBits::fan_speed_2: - metricInfo[PM_METRIC_GPU_FAN_SPEED].data[2].emplace_back(power_telemetry_info.fan_speed_rpm[2]); - break; - case GpuTelemetryCapBits::fan_speed_3: - metricInfo[PM_METRIC_GPU_FAN_SPEED].data[3].emplace_back(power_telemetry_info.fan_speed_rpm[3]); - break; - case GpuTelemetryCapBits::fan_speed_4: - metricInfo[PM_METRIC_GPU_FAN_SPEED].data[4].emplace_back(power_telemetry_info.fan_speed_rpm[4]); - break; - case GpuTelemetryCapBits::max_fan_speed_0: - metricInfo[PM_METRIC_GPU_FAN_SPEED_PERCENT].data[0].emplace_back( - power_telemetry_info.fan_speed_rpm[0] / double(power_telemetry_info.max_fan_speed_rpm[0])); - break; - case GpuTelemetryCapBits::max_fan_speed_1: - metricInfo[PM_METRIC_GPU_FAN_SPEED_PERCENT].data[1].emplace_back( - power_telemetry_info.fan_speed_rpm[0] / double(power_telemetry_info.max_fan_speed_rpm[1])); - break; - case GpuTelemetryCapBits::max_fan_speed_2: - metricInfo[PM_METRIC_GPU_FAN_SPEED_PERCENT].data[2].emplace_back( - power_telemetry_info.fan_speed_rpm[0] / double(power_telemetry_info.max_fan_speed_rpm[2])); - break; - case GpuTelemetryCapBits::max_fan_speed_3: - metricInfo[PM_METRIC_GPU_FAN_SPEED_PERCENT].data[3].emplace_back( - power_telemetry_info.fan_speed_rpm[0] / double(power_telemetry_info.max_fan_speed_rpm[3])); - break; - case GpuTelemetryCapBits::max_fan_speed_4: - metricInfo[PM_METRIC_GPU_FAN_SPEED_PERCENT].data[4].emplace_back( - power_telemetry_info.fan_speed_rpm[0] / double(power_telemetry_info.max_fan_speed_rpm[4])); - break; - case GpuTelemetryCapBits::gpu_mem_used: - metricInfo[PM_METRIC_GPU_MEM_USED].data[0].emplace_back(static_cast(power_telemetry_info.gpu_mem_used_b)); - break; - case GpuTelemetryCapBits::gpu_mem_write_bandwidth: - metricInfo[PM_METRIC_GPU_MEM_WRITE_BANDWIDTH].data[0].emplace_back(power_telemetry_info.gpu_mem_write_bandwidth_bps); - break; - case GpuTelemetryCapBits::gpu_mem_read_bandwidth: - metricInfo[PM_METRIC_GPU_MEM_READ_BANDWIDTH].data[0].emplace_back(power_telemetry_info.gpu_mem_read_bandwidth_bps); - break; - case GpuTelemetryCapBits::gpu_power_limited: - metricInfo[PM_METRIC_GPU_POWER_LIMITED].data[0].emplace_back(power_telemetry_info.gpu_power_limited); - break; - case GpuTelemetryCapBits::gpu_temperature_limited: - metricInfo[PM_METRIC_GPU_TEMPERATURE_LIMITED].data[0].emplace_back(power_telemetry_info.gpu_temperature_limited); - break; - case GpuTelemetryCapBits::gpu_current_limited: - metricInfo[PM_METRIC_GPU_CURRENT_LIMITED].data[0].emplace_back(power_telemetry_info.gpu_current_limited); - break; - case GpuTelemetryCapBits::gpu_voltage_limited: - metricInfo[PM_METRIC_GPU_VOLTAGE_LIMITED].data[0].emplace_back(power_telemetry_info.gpu_voltage_limited); - break; - case GpuTelemetryCapBits::gpu_utilization_limited: - metricInfo[PM_METRIC_GPU_UTILIZATION_LIMITED].data[0].emplace_back(power_telemetry_info.gpu_utilization_limited); - break; - case GpuTelemetryCapBits::vram_power_limited: - metricInfo[PM_METRIC_GPU_MEM_POWER_LIMITED].data[0].emplace_back(power_telemetry_info.vram_power_limited); - break; - case GpuTelemetryCapBits::vram_temperature_limited: - metricInfo[PM_METRIC_GPU_MEM_TEMPERATURE_LIMITED].data[0].emplace_back(power_telemetry_info.vram_temperature_limited); - break; - case GpuTelemetryCapBits::vram_current_limited: - metricInfo[PM_METRIC_GPU_MEM_CURRENT_LIMITED].data[0].emplace_back(power_telemetry_info.vram_current_limited); - break; - case GpuTelemetryCapBits::vram_voltage_limited: - metricInfo[PM_METRIC_GPU_MEM_VOLTAGE_LIMITED].data[0].emplace_back(power_telemetry_info.vram_voltage_limited); - break; - case GpuTelemetryCapBits::vram_utilization_limited: - metricInfo[PM_METRIC_GPU_MEM_UTILIZATION_LIMITED].data[0].emplace_back(power_telemetry_info.vram_utilization_limited); - break; - case GpuTelemetryCapBits::gpu_effective_frequency: - metricInfo[PM_METRIC_GPU_EFFECTIVE_FREQUENCY].data[0].emplace_back(power_telemetry_info.gpu_effective_frequency_mhz); - break; - case GpuTelemetryCapBits::gpu_voltage_regulator_temperature: - metricInfo[PM_METRIC_GPU_VOLTAGE_REGULATOR_TEMPERATURE].data[0].emplace_back(power_telemetry_info.gpu_voltage_regulator_temperature_c); - break; - case GpuTelemetryCapBits::gpu_mem_effective_bandwidth: - metricInfo[PM_METRIC_GPU_MEM_EFFECTIVE_BANDWIDTH].data[0].emplace_back(power_telemetry_info.gpu_mem_effective_bandwidth_gbps); - break; - case GpuTelemetryCapBits::gpu_overvoltage_percent: - metricInfo[PM_METRIC_GPU_OVERVOLTAGE_PERCENT].data[0].emplace_back(power_telemetry_info.gpu_overvoltage_percent); - break; - case GpuTelemetryCapBits::gpu_temperature_percent: - metricInfo[PM_METRIC_GPU_TEMPERATURE_PERCENT].data[0].emplace_back(power_telemetry_info.gpu_temperature_percent); - break; - case GpuTelemetryCapBits::gpu_power_percent: - metricInfo[PM_METRIC_GPU_POWER_PERCENT].data[0].emplace_back(power_telemetry_info.gpu_power_percent); - break; - case GpuTelemetryCapBits::gpu_card_power: - metricInfo[PM_METRIC_GPU_CARD_POWER].data[0].emplace_back(power_telemetry_info.gpu_card_power_w); - break; - default: - validGpuMetric = false; - break; - } - return validGpuMetric; - } - - bool ConcreteMiddleware::GetCpuMetricData(size_t telemetryBit, CpuTelemetryInfo& cpuTelemetry, std::unordered_map& metricInfo) - { - bool validCpuMetric = true; - CpuTelemetryCapBits bit = - static_cast(telemetryBit); - switch (bit) { - case CpuTelemetryCapBits::cpu_utilization: - metricInfo[PM_METRIC_CPU_UTILIZATION].data[0].emplace_back(cpuTelemetry.cpu_utilization); - break; - case CpuTelemetryCapBits::cpu_power: - metricInfo[PM_METRIC_CPU_POWER].data[0].emplace_back(cpuTelemetry.cpu_power_w); - break; - case CpuTelemetryCapBits::cpu_temperature: - metricInfo[PM_METRIC_CPU_TEMPERATURE].data[0].emplace_back(cpuTelemetry.cpu_temperature); - break; - case CpuTelemetryCapBits::cpu_frequency: - metricInfo[PM_METRIC_CPU_FREQUENCY].data[0].emplace_back(cpuTelemetry.cpu_frequency); - break; - default: - validCpuMetric = false; - break; - } - - return validCpuMetric; - } - - void ConcreteMiddleware::SaveMetricCache(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob) - { - auto it = cachedMetricDatas.find(std::pair(pQuery, processId)); - if (it != cachedMetricDatas.end()) - { - auto& uniquePtr = it->second; - std::copy(pBlob, pBlob + pQuery->queryCacheSize, uniquePtr.get()); - } - else - { - auto dataArray = std::make_unique(pQuery->queryCacheSize); - std::copy(pBlob, pBlob + pQuery->queryCacheSize, dataArray.get()); - cachedMetricDatas.emplace(std::pair(pQuery, processId), std::move(dataArray)); - } - } - - void ConcreteMiddleware::CopyMetricCacheToBlob(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob) - { - auto it = cachedMetricDatas.find(std::pair(pQuery, processId)); - if (it != cachedMetricDatas.end()) - { - auto& uniquePtr = it->second; - std::copy(uniquePtr.get(), uniquePtr.get() + pQuery->queryCacheSize, pBlob); - } - } - FrameMetricsSource& ConcreteMiddleware::GetFrameMetricSource_(uint32_t pid) const { if (auto it = frameMetricsSources.find(pid); @@ -1927,294 +281,4 @@ static void ReportMetrics( return *it->second; } } - - // This code currently doesn't support the copying of multiple swap chains. If a second swap chain - // is encountered it will update the numSwapChains to the correct number and then copy the swap - // chain frame information with the most presents. If the client does happen to specify two swap - // chains this code will incorrectly copy the data. WIP. - void ConcreteMiddleware::CalculateMetrics(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t* numSwapChains, LARGE_INTEGER qpcFrequency, std::unordered_map& swapChainData, std::unordered_map& metricInfo) - { - // Find the swapchain with the most frame metrics - auto CalcGpuMemUtilization = [this, metricInfo](PM_STAT stat) - { - double output = 0.; - if (cachedGpuInfo[currentGpuInfoIndex].gpuMemorySize.has_value()) { - auto gpuMemSize = static_cast(cachedGpuInfo[currentGpuInfoIndex].gpuMemorySize.value()); - if (gpuMemSize != 0.) - { - std::vector memoryUtilization; - auto it = metricInfo.find(PM_METRIC_GPU_MEM_USED); - if (it != metricInfo.end()) { - auto memUsed = it->second; - auto memUsedVector = memUsed.data[0]; - for (auto memUsed : memUsedVector) { - memoryUtilization.push_back(100. * (memUsed / gpuMemSize)); - } - output = CalculateStatistic(memoryUtilization, stat); - } - } - } - return output; - }; - - uint32_t maxSwapChainPresents = 0; - uint32_t maxSwapChainPresentsIndex = 0; - uint32_t currentSwapChainIndex = 0; - for (auto& pair : swapChainData) { - auto& swapChain = pair.second; - auto numFrames = (uint32_t)swapChain.mCPUBusy.size(); - if (numFrames > maxSwapChainPresents) - { - maxSwapChainPresents = numFrames; - maxSwapChainPresentsIndex = currentSwapChainIndex; - } - currentSwapChainIndex++; - } - - currentSwapChainIndex = 0; - bool copyAllMetrics = true; - bool useCache = false; - bool allMetricsCalculated = false; - - // If the number of swap chains found in the frame data is greater than the number passed - // in update the passed in number to notify the client there is more data present than - // can be returned - if (swapChainData.size() > *numSwapChains) - { - *numSwapChains = static_cast(swapChainData.size()); - copyAllMetrics = false; - } - - // If the client chose to monitor frame information then this loop - // will calculate and store all metrics. - for (auto& pair : swapChainData) { - auto& swapChain = pair.second; - - // There are couple reasons where we will not be able to produce - // fps metric data. The first is if all of the frames are dropped. - // The second is if in the requested sample window there are - // no presents. - auto numFrames = (uint32_t)swapChain.mCPUBusy.size(); - if ((swapChain.display_count <= 1) && (numFrames == 0)) { - useCache = true; - pmlog_dbg("Filling cached data in dynamic metric poll") - .pmwatch(numFrames).pmwatch(swapChain.display_count).diag(); - break; - } - - // If we are unable to copy all of the metrics to the blob and the current swap - // chain isn't the one with the most presents, skip by it - if ((copyAllMetrics == false) && (currentSwapChainIndex != maxSwapChainPresentsIndex)) - { - continue; - } - for (auto& qe : pQuery->elements) { - switch (qe.metric) - { - case PM_METRIC_SWAP_CHAIN_ADDRESS: - { - auto& output = reinterpret_cast(pBlob[qe.dataOffset]); - output = pair.first; - } - break; - - case PM_METRIC_CPU_START_QPC: - case PM_METRIC_PRESENT_MODE: - case PM_METRIC_PRESENT_RUNTIME: - case PM_METRIC_PRESENT_FLAGS: - case PM_METRIC_SYNC_INTERVAL: - case PM_METRIC_ALLOWS_TEARING: - case PM_METRIC_FRAME_TYPE: - case PM_METRIC_GPU_LATENCY: - case PM_METRIC_GPU_WAIT: - case PM_METRIC_GPU_BUSY: - case PM_METRIC_DISPLAY_LATENCY: - case PM_METRIC_CLICK_TO_PHOTON_LATENCY: - case PM_METRIC_ALL_INPUT_TO_PHOTON_LATENCY: - case PM_METRIC_PRESENTED_FPS: - case PM_METRIC_APPLICATION_FPS: - case PM_METRIC_DISPLAYED_FPS: - case PM_METRIC_DROPPED_FRAMES: - case PM_METRIC_CPU_FRAME_TIME: - case PM_METRIC_CPU_BUSY: - case PM_METRIC_CPU_WAIT: - case PM_METRIC_GPU_TIME: - case PM_METRIC_DISPLAYED_TIME: - case PM_METRIC_ANIMATION_ERROR: - case PM_METRIC_APPLICATION: - case PM_METRIC_PRESENT_START_QPC: - case PM_METRIC_BETWEEN_PRESENTS: - case PM_METRIC_IN_PRESENT_API: - case PM_METRIC_BETWEEN_DISPLAY_CHANGE: - case PM_METRIC_UNTIL_DISPLAYED: - case PM_METRIC_RENDER_PRESENT_LATENCY: - case PM_METRIC_BETWEEN_SIMULATION_START: - case PM_METRIC_PC_LATENCY: - case PM_METRIC_INSTRUMENTED_LATENCY: - case PM_METRIC_PRESENTED_FRAME_TIME: - case PM_METRIC_DISPLAYED_FRAME_TIME: - CalculateFpsMetric(swapChain, qe, pBlob, qpcFrequency); - break; - case PM_METRIC_CPU_VENDOR: - case PM_METRIC_CPU_POWER_LIMIT: - case PM_METRIC_GPU_VENDOR: - case PM_METRIC_GPU_MEM_MAX_BANDWIDTH: - case PM_METRIC_GPU_MEM_SIZE: - case PM_METRIC_GPU_SUSTAINED_POWER_LIMIT: - CopyStaticMetricData(qe.metric, qe.deviceId, pBlob, qe.dataOffset); - break; - case PM_METRIC_CPU_NAME: - case PM_METRIC_GPU_NAME: - CopyStaticMetricData(qe.metric, qe.deviceId, pBlob, qe.dataOffset, 260); - break; - case PM_METRIC_GPU_MEM_UTILIZATION: - { - auto& output = reinterpret_cast(pBlob[qe.dataOffset]); - output = CalcGpuMemUtilization(qe.stat); - } - break; - default: - if (qe.dataSize == sizeof(double)) { - CalculateGpuCpuMetric(metricInfo, qe, pBlob); - } - break; - } - } - - allMetricsCalculated = true; - currentSwapChainIndex++; - } - - if (useCache == true) { - CopyMetricCacheToBlob(pQuery, processId, pBlob); - return; - } - - if (allMetricsCalculated == false) - { - for (auto& qe : pQuery->elements) - { - switch (qe.metric) - { - case PM_METRIC_GPU_POWER: - case PM_METRIC_GPU_FAN_SPEED: - case PM_METRIC_GPU_FAN_SPEED_PERCENT: - case PM_METRIC_GPU_VOLTAGE: - case PM_METRIC_GPU_FREQUENCY: - case PM_METRIC_GPU_TEMPERATURE: - case PM_METRIC_GPU_UTILIZATION: - case PM_METRIC_GPU_RENDER_COMPUTE_UTILIZATION: - case PM_METRIC_GPU_MEDIA_UTILIZATION: - case PM_METRIC_GPU_MEM_POWER: - case PM_METRIC_GPU_MEM_VOLTAGE: - case PM_METRIC_GPU_MEM_FREQUENCY: - case PM_METRIC_GPU_MEM_EFFECTIVE_FREQUENCY: - case PM_METRIC_GPU_MEM_TEMPERATURE: - case PM_METRIC_GPU_MEM_USED: - case PM_METRIC_GPU_MEM_WRITE_BANDWIDTH: - case PM_METRIC_GPU_MEM_READ_BANDWIDTH: - case PM_METRIC_GPU_POWER_LIMITED: - case PM_METRIC_GPU_TEMPERATURE_LIMITED: - case PM_METRIC_GPU_CURRENT_LIMITED: - case PM_METRIC_GPU_VOLTAGE_LIMITED: - case PM_METRIC_GPU_UTILIZATION_LIMITED: - case PM_METRIC_GPU_MEM_POWER_LIMITED: - case PM_METRIC_GPU_MEM_TEMPERATURE_LIMITED: - case PM_METRIC_GPU_MEM_CURRENT_LIMITED: - case PM_METRIC_GPU_MEM_VOLTAGE_LIMITED: - case PM_METRIC_GPU_MEM_UTILIZATION_LIMITED: - case PM_METRIC_CPU_UTILIZATION: - case PM_METRIC_CPU_POWER: - case PM_METRIC_CPU_TEMPERATURE: - case PM_METRIC_CPU_FREQUENCY: - case PM_METRIC_CPU_CORE_UTILITY: - case PM_METRIC_GPU_EFFECTIVE_FREQUENCY: - case PM_METRIC_GPU_VOLTAGE_REGULATOR_TEMPERATURE: - case PM_METRIC_GPU_MEM_EFFECTIVE_BANDWIDTH: - case PM_METRIC_GPU_OVERVOLTAGE_PERCENT: - case PM_METRIC_GPU_TEMPERATURE_PERCENT: - case PM_METRIC_GPU_POWER_PERCENT: - case PM_METRIC_GPU_CARD_POWER: - CalculateGpuCpuMetric(metricInfo, qe, pBlob); - break; - case PM_METRIC_CPU_VENDOR: - case PM_METRIC_CPU_POWER_LIMIT: - case PM_METRIC_GPU_VENDOR: - case PM_METRIC_GPU_MEM_MAX_BANDWIDTH: - case PM_METRIC_GPU_MEM_SIZE: - case PM_METRIC_GPU_SUSTAINED_POWER_LIMIT: - CopyStaticMetricData(qe.metric, qe.deviceId, pBlob, qe.dataOffset); - break; - case PM_METRIC_CPU_NAME: - case PM_METRIC_GPU_NAME: - CopyStaticMetricData(qe.metric, qe.deviceId, pBlob, qe.dataOffset, 260); - break; - case PM_METRIC_GPU_MEM_UTILIZATION: - { - auto& output = reinterpret_cast(pBlob[qe.dataOffset]); - output = CalcGpuMemUtilization(qe.stat); - break; - } - default: - break; - } - } - } - - // Save calculated metrics blob to cache - SaveMetricCache(pQuery, processId, pBlob); - } - - PM_STATUS ConcreteMiddleware::SetActiveGraphicsAdapter(uint32_t deviceId) - { - std::optional adapterIndex; - try { - // if the requested deviceId does not exist in the cache of gpu devices return error - adapterIndex = GetCachedGpuInfoIndex(deviceId); - if (!adapterIndex.has_value()) { - pmlog_error(std::format("Client specified invalid deviceId for adapter [{}]", deviceId)); - return PM_STATUS_INVALID_ADAPTER_ID; - } - - pActionClient->DispatchSync(SelectAdapter::Params{ (uint32_t)*adapterIndex }); - } - catch (...) { - const auto code = util::GeneratePmStatus(); - pmlog_error(util::ReportException()).code(code).diag(); - return code; - } - - pmlog_info(std::format("Active adapter change request completed, changed to [{}] (svc id) [{}] (API id)", *adapterIndex, deviceId)); - return PM_STATUS_SUCCESS; - } - - void ConcreteMiddleware::GetStaticGpuMetrics() - { - try { - const auto res = pActionClient->DispatchSync(EnumerateAdapters::Params{}); - - // check that adapter count matches introspection - if (res.adapters.size() != cachedGpuInfo.size()) { - pmlog_warn(std::format("Queried adapter count {} did not match count from introspection {}", - res.adapters.size(), cachedGpuInfo.size())).diag(); - } - - // For each cached gpu search through the returned adapter information and set the returned - // static gpu metrics where a match is found - for (auto& gpuInfo : cachedGpuInfo) { - for (auto& adapter : res.adapters) { - if (gpuInfo.adapterId == adapter.id) { - gpuInfo.gpuSustainedPowerLimit = adapter.gpuSustainedPowerLimit; - gpuInfo.gpuMemorySize = adapter.gpuMemorySize; - gpuInfo.gpuMemoryMaxBandwidth = adapter.gpuMemoryMaxBandwidth; - break; - } - } - } - } - catch (...) { - const auto code = util::GeneratePmStatus(); - pmlog_error(util::ReportException()).code(code).diag(); - } - } } diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h index cb3a3dd5..066e86bc 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h @@ -20,154 +20,6 @@ namespace pmon::mid { class FrameMetricsSource; - // Used to calculate correct start frame based on metric offset - struct MetricOffsetData { - uint64_t queryToFrameDataDelta = 0; - uint64_t metricOffset = 0; - }; - - class InputToFsManager { - public: - void AddI2FsValueForProcess(uint32_t processId, uint64_t timeStamp, double value) { - auto it = mProcessIdToI2FsValue.find(processId); - if (it == mProcessIdToI2FsValue.end()) { - auto firstEmaValue = pmon::util::CalculateEma( - 0., - value, - mEmaAlpha); - it = mProcessIdToI2FsValue.emplace(processId, I2FsValueContainer{ firstEmaValue, timeStamp }).first; - return; - } - if (timeStamp > it->second.timestamp) { - it->second.i2FsValue = pmon::util::CalculateEma( - it->second.i2FsValue, - value, - mEmaAlpha); - it->second.timestamp = timeStamp; - } - } - - // Retrieve the current input to frame start for a given process id. - double GetI2FsForProcess(uint32_t processId) const { - auto it = mProcessIdToI2FsValue.find(processId); - if (it == mProcessIdToI2FsValue.end()) { - return 0.f; - } - return it->second.i2FsValue; - } - - // Remove a process from the map if it�s no longer needed. - void RemoveProcess(uint32_t processId) { - mProcessIdToI2FsValue.erase(processId); - } - - private: - struct I2FsValueContainer { - double i2FsValue = 0.f; - uint64_t timestamp = 0; - }; - - double const mEmaAlpha = 0.1; - - std::map mProcessIdToI2FsValue; // Map from a process id to the current input to frame start value. - }; - - - // Copied from: PresentMon/PresentMon.hpp - // We store SwapChainData per process and per swapchain, where we maintain: - // - information on previous presents needed for console output or to compute metrics for upcoming - // presents, - // - pending presents whose metrics cannot be computed until future presents are received, - // - exponential averages of key metrics displayed in console output. - struct fpsSwapChainData { - // Pending presents waiting for the next displayed present. - std::vector mPendingPresents; - - // The most recent present that has been processed (e.g., output into CSV and/or used for frame - // statistics). - PmNsmPresentEvent mLastPresent; - // The most recent app present that has been processed (e.g., output into CSV and/or used for frame - // statistics). - PmNsmPresentEvent mLastAppPresent; - - bool mLastPresentIsValid = false; - bool mLastAppPresentIsValid = false; - - // Whether to include frame data in the next PresentEvent's FrameMetrics. - bool mIncludeFrameData = true; - - // IntelPresentMon specifics: - std::vector mCPUBusy; - std::vector mCPUWait; - std::vector mGPULatency; - std::vector mGPUBusy; - std::vector mVideoBusy; - std::vector mGPUWait; - std::vector mDisplayLatency; - std::vector mDisplayedTime; - std::vector mAppDisplayedTime; - std::vector mAnimationError; - std::vector mClickToPhotonLatency; - std::vector mAllInputToPhotonLatency; - std::vector mDropped; - std::vector mInstrumentedDisplayLatency; - std::vector mMsBetweenPresents; - std::vector mMsInPresentApi; - std::vector mMsUntilDisplayed; - std::vector mMsBetweenDisplayChange; - std::vector mMsUntilRenderComplete; - std::vector mMsBetweenSimStarts; - std::vector mMsPcLatency; - - std::vector mInstrumentedSleep; - std::vector mInstrumentedRenderLatency; - std::vector mInstrumentedGpuLatency; - std::vector mInstrumentedReadyTimeToDisplayLatency; - - // QPC of last received input data that did not make it to the screen due - // to the Present() being dropped - uint64_t mLastReceivedNotDisplayedAllInputTime = 0; - uint64_t mLastReceivedNotDisplayedMouseClickTime = 0; - // QPC of the last PC Latency simulation start - uint64_t mLastReceivedNotDisplayedPclSimStart = 0; - uint64_t mLastReceivedNotDisplayedPclInputTime = 0; - - // Animation error source. Start with CPU start QPC and switch if - // we receive a valid PCL or App Provider simulation start time. - AnimationErrorSource mAnimationErrorSource = AnimationErrorSource::CpuStart; - - // Accumulated PC latency input to frame start time due to the - // Present() being dropped - double mAccumulatedInput2FrameStartTime = 0.f; - - // begin/end screen times to optimize average calculation: - uint64_t mLastDisplayedScreenTime = 0; // The last presented frame's ScreenTime (qpc) - uint64_t mLastDisplayedAppScreenTime = 0; // The last presented app frame's ScreenTime (qpc) - uint64_t display_0_screen_time = 0; // The first presented frame's ScreenTime (qpc) - uint64_t mLastDisplayedSimStartTime = 0; // The simulation start of the last displayed frame - uint32_t display_count = 0; // The number of presented frames - // QPC of the last simulation start time iregardless of whether it was displayed or not - uint64_t mLastSimStartTime = 0; - }; - - struct DeviceInfo - { - PM_DEVICE_VENDOR deviceVendor; - std::string deviceName; - uint32_t deviceId; - std::optional adapterId; - std::optional gpuSustainedPowerLimit; - std::optional gpuMemorySize; - std::optional gpuMemoryMaxBandwidth; - std::optional cpuPowerLimit; - }; - - struct MetricInfo - { - // Map of array indices to associated data - std::unordered_map> data; - }; - class ConcreteMiddleware : public Middleware { public: @@ -192,52 +44,15 @@ namespace pmon::mid std::string FinishEtlLogging(uint32_t etlLogSessionHandle) override; private: // functions - PmNsmFrameData* GetFrameDataStart(StreamClient* client, uint64_t& index, uint64_t dataOffset, uint64_t& queryFrameDataDelta, double& windowSampleSizeMs); - uint64_t GetAdjustedQpc(uint64_t current_qpc, uint64_t frame_data_qpc, uint64_t queryMetricsOffset, LARGE_INTEGER frequency, uint64_t& queryFrameDataDelta); - bool DecrementIndex(NamedSharedMem* nsm_view, uint64_t& index); - PM_STATUS SetActiveGraphicsAdapter(uint32_t deviceId); - void GetStaticGpuMetrics(); - - void CalculateFpsMetric(fpsSwapChainData& swapChain, const PM_QUERY_ELEMENT& element, uint8_t* pBlob, LARGE_INTEGER qpcFrequency); - void CalculateGpuCpuMetric(std::unordered_map& metricInfo, const PM_QUERY_ELEMENT& element, uint8_t* pBlob); - double CalculateStatistic(std::vector& inData, PM_STAT stat, bool invert = false) const; - double CalculatePercentile(std::vector& inData, double percentile, bool invert) const; - bool GetGpuMetricData(size_t telemetry_item_bit, PresentMonPowerTelemetryInfo& power_telemetry_info, std::unordered_map& metricInfo); - bool GetCpuMetricData(size_t telemetryBit, CpuTelemetryInfo& cpuTelemetry, std::unordered_map& metricInfo); - void GetStaticCpuMetrics(); - std::string GetProcessName(uint32_t processId); - void CopyStaticMetricData(PM_METRIC metric, uint32_t deviceId, uint8_t* pBlob, uint64_t blobOffset, size_t sizeInBytes = 0); - - void CalculateMetrics(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t* numSwapChains, LARGE_INTEGER qpcFrequency, std::unordered_map& swapChainData, std::unordered_map& metricInfo); - void SaveMetricCache(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob); - void CopyMetricCacheToBlob(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob); - + const pmapi::intro::Root& GetIntrospectionRoot_(); FrameMetricsSource& GetFrameMetricSource_(uint32_t pid) const; - // data - std::optional GetCachedGpuInfoIndex(uint32_t deviceId); - - const pmapi::intro::Root& GetIntrospectionRoot(); - + // action client connection to service RPC std::shared_ptr pActionClient; - uint32_t clientProcessId = 0; - // Stream clients mapping to process id - std::map> presentMonStreamClients; - // Frame timing data for each process id - std::map frameTimingData; + // ipc shared memory for frame data, telemetry, and introspection std::unique_ptr pComms; - // Dynamic query handle to frame data delta - std::unordered_map, uint64_t> queryFrameDataDeltas; - // Dynamic query handle to cache data - std::unordered_map, std::unique_ptr> cachedMetricDatas; - std::vector cachedGpuInfo; - std::vector cachedCpuInfo; - uint32_t currentGpuInfoIndex = UINT32_MAX; - std::optional activeDevice; + // cache of marshalled introspection data std::unique_ptr pIntroRoot; - InputToFsManager mPclI2FsManager; - - // new members // Frame metrics sources mapped to process id std::map> frameMetricsSources; }; diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp index e864f9cd..7b46c7bd 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp @@ -6,6 +6,9 @@ #include "../CommonUtilities/Hash.h" #include +using namespace pmon; +using namespace mid; + namespace { struct TelemetryBindingKey_ @@ -38,76 +41,73 @@ namespace std }; } -namespace pmon::mid::todo +PM_DYNAMIC_QUERY::PM_DYNAMIC_QUERY(std::span qels, ipc::MiddlewareComms& comms) { - PM_DYNAMIC_QUERY::PM_DYNAMIC_QUERY(std::span qels, ipc::MiddlewareComms& comms) - { - const auto* introBase = comms.GetIntrospectionRoot(); - pmapi::intro::Root introRoot{ introBase, [](const PM_INTROSPECTION_ROOT*) {} }; - pmon::mid::ValidateQueryElements(qels, PM_METRIC_TYPE_DYNAMIC, introRoot, comms); + const auto* introBase = comms.GetIntrospectionRoot(); + pmapi::intro::Root introRoot{ introBase, [](const PM_INTROSPECTION_ROOT*) {} }; + pmon::mid::ValidateQueryElements(qels, PM_METRIC_TYPE_DYNAMIC, introRoot, comms); - std::unordered_map telemetryBindings; - RingMetricBinding* frameBinding = nullptr; + std::unordered_map telemetryBindings; + RingMetricBinding* frameBinding = nullptr; - size_t blobCursor = 0; - for (auto& qel : qels) { - RingMetricBinding* binding = nullptr; - if (qel.deviceId == ipc::kUniversalDeviceId) { - binding = frameBinding; - if (!binding) { - auto bindingPtr = MakeFrameMetricBinding(qel); - binding = bindingPtr.get(); - frameBinding = bindingPtr.get(); - ringMetricPtrs_.push_back(std::move(bindingPtr)); - } + size_t blobCursor = 0; + for (auto& qel : qels) { + RingMetricBinding* binding = nullptr; + if (qel.deviceId == ipc::kUniversalDeviceId) { + binding = frameBinding; + if (!binding) { + auto bindingPtr = MakeFrameMetricBinding(qel); + binding = bindingPtr.get(); + frameBinding = bindingPtr.get(); + ringMetricPtrs_.push_back(std::move(bindingPtr)); + } + } + else { + const TelemetryBindingKey_ key{ + .deviceId = qel.deviceId, + .metric = qel.metric, + .arrayIndex = qel.arrayIndex, + }; + if (auto it = telemetryBindings.find(key); it != telemetryBindings.end()) { + binding = it->second; } else { - const TelemetryBindingKey_ key{ - .deviceId = qel.deviceId, - .metric = qel.metric, - .arrayIndex = qel.arrayIndex, - }; - if (auto it = telemetryBindings.find(key); it != telemetryBindings.end()) { - binding = it->second; - } - else { - auto bindingPtr = MakeTelemetryRingMetricBinding(qel, introRoot); - binding = bindingPtr.get(); - ringMetricPtrs_.push_back(std::move(bindingPtr)); - telemetryBindings.emplace(key, binding); - } + auto bindingPtr = MakeTelemetryRingMetricBinding(qel, introRoot); + binding = bindingPtr.get(); + ringMetricPtrs_.push_back(std::move(bindingPtr)); + telemetryBindings.emplace(key, binding); } - - qel.dataOffset = blobCursor; - binding->AddMetricStat(qel, introRoot); - blobCursor = qel.dataOffset + qel.dataSize; } - for (auto& binding : ringMetricPtrs_) { - binding->Finalize(); - } - - // make sure blob sizes are multiple of 16 bytes for blob array alignment purposes - blobSize_ = util::PadToAlignment(blobCursor, 16u); + qel.dataOffset = blobCursor; + binding->AddMetricStat(qel, introRoot); + blobCursor = qel.dataOffset + qel.dataSize; } - size_t PM_DYNAMIC_QUERY::GetBlobSize() const - { - return blobSize_; + for (auto& binding : ringMetricPtrs_) { + binding->Finalize(); } - DynamicQueryWindow PM_DYNAMIC_QUERY::GenerateQueryWindow_(uint64_t nowTimestamp) const - { - return DynamicQueryWindow(); - } + // make sure blob sizes are multiple of 16 bytes for blob array alignment purposes + blobSize_ = util::PadToAlignment(blobCursor, 16u); +} +size_t PM_DYNAMIC_QUERY::GetBlobSize() const +{ + return blobSize_; +} - void PM_DYNAMIC_QUERY::Poll(uint8_t* pBlobBase, ipc::MiddlewareComms& comms, - uint64_t nowTimestamp, FrameMetricsSource* frameSource) const - { - const auto window = GenerateQueryWindow_(nowTimestamp); - for (auto& pRing : ringMetricPtrs_) { - pRing->Poll(window, pBlobBase, comms, frameSource); - } +DynamicQueryWindow PM_DYNAMIC_QUERY::GenerateQueryWindow_(uint64_t nowTimestamp) const +{ + return DynamicQueryWindow(); +} + + +void PM_DYNAMIC_QUERY::Poll(uint8_t* pBlobBase, ipc::MiddlewareComms& comms, + uint64_t nowTimestamp, FrameMetricsSource* frameSource) const +{ + const auto window = GenerateQueryWindow_(nowTimestamp); + for (auto& pRing : ringMetricPtrs_) { + pRing->Poll(window, pBlobBase, comms, frameSource); } } diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h index e3af51e8..a4bef322 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h @@ -8,60 +8,34 @@ #include "RingMetricBinding.h" #include "DynamicQueryWindow.h" #include "../PresentMonAPI2/PresentMonAPI.h" -#include "../ControlLib/CpuTelemetryInfo.h" -#include "../ControlLib/PresentMonPowerTelemetry.h" namespace pmon::mid { - class Middleware; class RingMetricBinding; } namespace pmon::ipc { class MiddlewareComms; - class TelemetryMap; } -namespace pmon::mid::todo -{ - struct PM_DYNAMIC_QUERY - { - public: - PM_DYNAMIC_QUERY(std::span qels, ipc::MiddlewareComms& comms); - size_t GetBlobSize() const; - void Poll(uint8_t* pBlobBase, ipc::MiddlewareComms& comms, - uint64_t nowTimestamp, FrameMetricsSource* frameSource) const; - - private: - // functions - DynamicQueryWindow GenerateQueryWindow_(uint64_t nowTimestamp) const; - // data - std::vector> ringMetricPtrs_; - size_t blobSize_; - // window parameters; these could theoretically be independent of query but current API couples them - double windowSizeMs_ = 0; - double metricOffsetMs_ = 0.; - }; -} - -// TODO: legacy: to be deleted struct PM_DYNAMIC_QUERY { - std::vector elements; - size_t GetBlobSize() const - { - return elements.back().dataOffset + elements.back().dataSize; - } - // Data used to track what should be accumulated - bool accumFpsData = false; - std::bitset(GpuTelemetryCapBits::gpu_telemetry_count)> accumGpuBits; - std::bitset(CpuTelemetryCapBits::cpu_telemetry_count)> accumCpuBits; - // Data used to calculate the requested metrics - double windowSizeMs = 0; - double metricOffsetMs = 0.; - size_t queryCacheSize = 0; - std::optional cachedGpuInfoIndex; +public: + PM_DYNAMIC_QUERY(std::span qels, pmon::ipc::MiddlewareComms& comms); + size_t GetBlobSize() const; + void Poll(uint8_t* pBlobBase, pmon::ipc::MiddlewareComms& comms, + uint64_t nowTimestamp, pmon::mid::FrameMetricsSource* frameSource) const; + +private: + // functions + pmon::mid::DynamicQueryWindow GenerateQueryWindow_(uint64_t nowTimestamp) const; + // data + std::vector> ringMetricPtrs_; + size_t blobSize_; + // window parameters; these could theoretically be independent of query but current API couples them + double windowSizeMs_ = 0; + double metricOffsetMs_ = 0.; }; From 799d5b84f8855666dc23961e47e58d13f3f12a8c Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 28 Jan 2026 10:04:11 +0900 Subject: [PATCH 157/205] dynamic queries running --- .../ConcreteMiddleware.cpp | 15 +++- .../PresentMonMiddleware/DynamicQuery.cpp | 14 +++- .../PresentMonMiddleware/DynamicQuery.h | 10 ++- .../PresentMonMiddleware/DynamicStat.cpp | 81 ++++++++++++++++--- 4 files changed, 100 insertions(+), 20 deletions(-) diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index 375af512..37d59460 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -191,13 +191,24 @@ namespace pmon::mid return PM_STATUS_SUCCESS; } - PM_DYNAMIC_QUERY* ConcreteMiddleware::RegisterDynamicQuery(std::span queryElements, double windowSizeMs, double metricOffsetMs) + PM_DYNAMIC_QUERY* ConcreteMiddleware::RegisterDynamicQuery(std::span queryElements, + double windowSizeMs, double metricOffsetMs) { - return nullptr; + pmlog_info("Registering dynamic query").pmwatch(queryElements.size()).pmwatch(windowSizeMs).pmwatch(metricOffsetMs); + const auto qpcPeriod = util::GetTimestampPeriodSeconds(); + return new PM_DYNAMIC_QUERY{ queryElements, windowSizeMs, metricOffsetMs, qpcPeriod, *pComms }; } void ConcreteMiddleware::PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t* numSwapChains) { + // TODO: implement multi-swap handling + // locate frame metric source for target process if required + FrameMetricsSource* pFrameSource = nullptr; + if (processId) { + pFrameSource = &GetFrameMetricSource_(processId); + } + // execute the dynamic poll operation + pQuery->Poll(pBlob, *pComms, (uint64_t)util::GetCurrentTimestamp(), pFrameSource); } void ConcreteMiddleware::PollStaticQuery(const PM_QUERY_ELEMENT& element, uint32_t processId, uint8_t* pBlob) diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp index 7b46c7bd..ab0a01a3 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp @@ -5,6 +5,7 @@ #include "../Interprocess/source/Interprocess.h" #include "../CommonUtilities/Hash.h" #include +#include using namespace pmon; using namespace mid; @@ -41,7 +42,11 @@ namespace std }; } -PM_DYNAMIC_QUERY::PM_DYNAMIC_QUERY(std::span qels, ipc::MiddlewareComms& comms) +PM_DYNAMIC_QUERY::PM_DYNAMIC_QUERY(std::span qels, double windowSizeMs, + double windowOffsetMs, double qpcPeriodSeconds, ipc::MiddlewareComms& comms) + : + windowSizeQpc_{ int64_t((windowSizeMs / 1000.) / qpcPeriodSeconds) }, + windowOffsetQpc_{ int64_t((windowOffsetMs / 1000.) / qpcPeriodSeconds) } { const auto* introBase = comms.GetIntrospectionRoot(); pmapi::intro::Root introRoot{ introBase, [](const PM_INTROSPECTION_ROOT*) {} }; @@ -97,12 +102,13 @@ size_t PM_DYNAMIC_QUERY::GetBlobSize() const return blobSize_; } -DynamicQueryWindow PM_DYNAMIC_QUERY::GenerateQueryWindow_(uint64_t nowTimestamp) const +DynamicQueryWindow PM_DYNAMIC_QUERY::GenerateQueryWindow_(int64_t nowTimestamp) const { - return DynamicQueryWindow(); + const auto newest = nowTimestamp - windowOffsetQpc_; + const auto oldest = newest - windowSizeQpc_; + return { .oldest = uint64_t(oldest), .newest = uint64_t(newest)}; } - void PM_DYNAMIC_QUERY::Poll(uint8_t* pBlobBase, ipc::MiddlewareComms& comms, uint64_t nowTimestamp, FrameMetricsSource* frameSource) const { diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h index a4bef322..32503f6b 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h @@ -8,6 +8,7 @@ #include "RingMetricBinding.h" #include "DynamicQueryWindow.h" #include "../PresentMonAPI2/PresentMonAPI.h" +#include "../CommonUtilities/Qpc.h" namespace pmon::mid @@ -23,19 +24,20 @@ namespace pmon::ipc struct PM_DYNAMIC_QUERY { public: - PM_DYNAMIC_QUERY(std::span qels, pmon::ipc::MiddlewareComms& comms); + PM_DYNAMIC_QUERY(std::span qels, double windowSizeMs, double windowOffsetMs, + double qpcPeriodSeconds, pmon::ipc::MiddlewareComms& comms); size_t GetBlobSize() const; void Poll(uint8_t* pBlobBase, pmon::ipc::MiddlewareComms& comms, uint64_t nowTimestamp, pmon::mid::FrameMetricsSource* frameSource) const; private: // functions - pmon::mid::DynamicQueryWindow GenerateQueryWindow_(uint64_t nowTimestamp) const; + pmon::mid::DynamicQueryWindow GenerateQueryWindow_(int64_t nowTimestamp) const; // data std::vector> ringMetricPtrs_; size_t blobSize_; // window parameters; these could theoretically be independent of query but current API couples them - double windowSizeMs_ = 0; - double metricOffsetMs_ = 0.; + int64_t windowSizeQpc_ = 0; + int64_t windowOffsetQpc_ = 0; }; diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp index 922b1a87..76e6aa73 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp +++ b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp @@ -2,8 +2,10 @@ #include "DynamicQueryWindow.h" #include "../CommonUtilities/Exception.h" #include "../Interprocess/source/PmStatusError.h" +#include "../../PresentData/PresentMonTraceConsumer.hpp" #include #include +#include namespace ipc = pmon::ipc; namespace util = pmon::util; @@ -12,6 +14,48 @@ namespace pmon::mid { namespace detail { + template + struct SampleAdapter_ + { + static bool HasValue(const T&) + { + return true; + } + static bool IsZero(const T& val) + { + return val == (T)0; + } + static double ToDouble(const T& val) + { + return (double)val; + } + static uint64_t ToUint64(const T& val) + { + return (uint64_t)val; + } + }; + + template + struct SampleAdapter_> + { + static bool HasValue(const std::optional& val) + { + return val.has_value(); + } + static bool IsZero(const std::optional& val) + { + return !val.has_value() || *val == (U)0; + } + static double ToDouble(const std::optional& val) + { + return val.has_value() ? (double)*val : 0.0; + } + static uint64_t ToUint64(const std::optional& val) + { + return val.has_value() ? (uint64_t)*val : 0; + } + }; + template class DynamicStatBase_ : public DynamicStat { @@ -57,10 +101,13 @@ namespace pmon::mid bool NeedsSortedWindow() const override { return false; } void AddSample(T val) override { - if (skipZero_ && val == (T)0) { + if (!SampleAdapter_::HasValue(val)) { return; } - sum_ += (double)val; + if (skipZero_ && SampleAdapter_::IsZero(val)) { + return; + } + sum_ += SampleAdapter_::ToDouble(val); ++count_; } void GatherToBlob(uint8_t* pBase) const override @@ -92,17 +139,22 @@ namespace pmon::mid bool NeedsSortedWindow() const override { return true; } void InputSortedSamples(std::span sortedSamples) override { - if (sortedSamples.empty()) { + size_t firstValid = 0; + while (firstValid < sortedSamples.size() && !SampleAdapter_::HasValue(sortedSamples[firstValid])) { + ++firstValid; + } + const size_t validCount = sortedSamples.size() - firstValid; + if (validCount == 0) { value_ = 0.0; hasValue_ = false; return; } const double clamped = std::clamp(percentile_, 0.0, 1.0); - const size_t last = sortedSamples.size() - 1; + const size_t last = validCount - 1; const double position = clamped * (double)last; const size_t index = (size_t)(position + 0.5); - value_ = (double)sortedSamples[index]; + value_ = SampleAdapter_::ToDouble(sortedSamples[firstValid + index]); hasValue_ = true; } void GatherToBlob(uint8_t* pBase) const override @@ -146,7 +198,10 @@ namespace pmon::mid bool NeedsSortedWindow() const override { return false; } void AddSample(T val) override { - const double doubleVal = (double)val; + if (!SampleAdapter_::HasValue(val)) { + return; + } + const double doubleVal = SampleAdapter_::ToDouble(val); if (!hasValue_) { value_ = doubleVal; hasValue_ = true; @@ -219,7 +274,8 @@ namespace pmon::mid void GatherToBlob(uint8_t* pBase) const override { auto* pTarget = pBase + this->offsetBytes_; - const double doubleVal = (double)value_; + // TODO: consider less conversion/laundering (ToDouble then ToXXX) + const double doubleVal = SampleAdapter_::ToDouble(value_); switch (this->outType_) { case PM_DATA_TYPE_DOUBLE: *reinterpret_cast(pTarget) = hasValue_ ? doubleVal : 0.0; @@ -232,7 +288,7 @@ namespace pmon::mid *reinterpret_cast(pTarget) = hasValue_ ? doubleVal != 0.0 : false; break; case PM_DATA_TYPE_UINT64: - *reinterpret_cast(pTarget) = hasValue_ ? (uint64_t)value_ : 0; + *reinterpret_cast(pTarget) = hasValue_ ? SampleAdapter_::ToUint64(value_) : 0; break; default: assert(false); @@ -242,11 +298,11 @@ namespace pmon::mid void SetSampledValue(T val) override { value_ = val; - hasValue_ = true; + hasValue_ = SampleAdapter_::HasValue(val); } PM_STAT mode_ = PM_STAT_MID_POINT; bool hasValue_ = false; - T value_ = (T)0; + T value_{}; }; } @@ -300,6 +356,11 @@ namespace pmon::mid template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); + template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); + template std::unique_ptr>> MakeDynamicStat>(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); + template std::unique_ptr> MakeDynamicStat<::PresentMode>(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); + template std::unique_ptr> MakeDynamicStat<::Runtime>(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); + template std::unique_ptr> MakeDynamicStat<::FrameType>(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); } From f85c63f6ba7646c29e9a45579e5dba4b07ef9f81 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 28 Jan 2026 12:12:36 +0900 Subject: [PATCH 158/205] preventing result carryover in stats between polls --- .../ConcreteMiddleware.cpp | 2 +- .../PresentMonMiddleware/DynamicStat.cpp | 139 ++++++++---------- 2 files changed, 59 insertions(+), 82 deletions(-) diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index 37d59460..90667e97 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -194,7 +194,7 @@ namespace pmon::mid PM_DYNAMIC_QUERY* ConcreteMiddleware::RegisterDynamicQuery(std::span queryElements, double windowSizeMs, double metricOffsetMs) { - pmlog_info("Registering dynamic query").pmwatch(queryElements.size()).pmwatch(windowSizeMs).pmwatch(metricOffsetMs); + pmlog_dbg("Registering dynamic query").pmwatch(queryElements.size()).pmwatch(windowSizeMs).pmwatch(metricOffsetMs); const auto qpcPeriod = util::GetTimestampPeriodSeconds(); return new PM_DYNAMIC_QUERY{ queryElements, windowSizeMs, metricOffsetMs, qpcPeriod, *pComms }; } diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp index 76e6aa73..f3d21f86 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp +++ b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp @@ -14,6 +14,7 @@ namespace pmon::mid { namespace detail { + // TODO: consider ways of obviating this adapter construct template struct SampleAdapter_ { @@ -56,6 +57,32 @@ namespace pmon::mid } }; + template + void WriteOptionalValueToBlob_(uint8_t* pBase, size_t offsetBytes, PM_DATA_TYPE outType, const std::optional& value) + { + auto* pTarget = pBase + offsetBytes; + const double doubleVal = value ? SampleAdapter_::ToDouble(*value) : 0.0; + const uint64_t uint64Val = value ? SampleAdapter_::ToUint64(*value) : 0; + switch (outType) { + case PM_DATA_TYPE_DOUBLE: + *reinterpret_cast(pTarget) = doubleVal; + break; + case PM_DATA_TYPE_INT32: + case PM_DATA_TYPE_ENUM: + *reinterpret_cast(pTarget) = value ? (int32_t)doubleVal : 0; + break; + case PM_DATA_TYPE_BOOL: + *reinterpret_cast(pTarget) = value ? doubleVal != 0.0 : false; + break; + case PM_DATA_TYPE_UINT64: + *reinterpret_cast(pTarget) = value ? uint64Val : 0; + break; + default: + pmlog_error("Unhandled data type case").pmwatch((int)outType); + assert(false); + } + } + template class DynamicStatBase_ : public DynamicStat { @@ -112,17 +139,19 @@ namespace pmon::mid } void GatherToBlob(uint8_t* pBase) const override { - double avg = 0.0; + std::optional avg; if (count_ > 0) { avg = sum_ / (double)count_; } - auto* pTarget = pBase + this->offsetBytes_; - *reinterpret_cast(pTarget) = avg; + WriteOptionalValueToBlob_(pBase, this->offsetBytes_, this->outType_, avg); + // reset for the next poll + sum_ = 0; + count_ = 0; } private: bool skipZero_ = false; - double sum_ = 0.0; - size_t count_ = 0; + mutable double sum_ = 0.0; + mutable size_t count_ = 0; }; template @@ -139,49 +168,30 @@ namespace pmon::mid bool NeedsSortedWindow() const override { return true; } void InputSortedSamples(std::span sortedSamples) override { + // TODO: review interaction of optional values with percentile sorted buffer size_t firstValid = 0; while (firstValid < sortedSamples.size() && !SampleAdapter_::HasValue(sortedSamples[firstValid])) { ++firstValid; } const size_t validCount = sortedSamples.size() - firstValid; if (validCount == 0) { - value_ = 0.0; - hasValue_ = false; return; } - const double clamped = std::clamp(percentile_, 0.0, 1.0); const size_t last = validCount - 1; - const double position = clamped * (double)last; + const double position = percentile_ * (double)last; const size_t index = (size_t)(position + 0.5); value_ = SampleAdapter_::ToDouble(sortedSamples[firstValid + index]); - hasValue_ = true; } void GatherToBlob(uint8_t* pBase) const override { - auto* pTarget = pBase + this->offsetBytes_; - switch (this->outType_) { - case PM_DATA_TYPE_DOUBLE: - *reinterpret_cast(pTarget) = hasValue_ ? value_ : 0.0; - break; - case PM_DATA_TYPE_INT32: - case PM_DATA_TYPE_ENUM: - *reinterpret_cast(pTarget) = hasValue_ ? (int32_t)value_ : 0; - break; - case PM_DATA_TYPE_BOOL: - *reinterpret_cast(pTarget) = hasValue_ ? value_ != 0.0 : false; - break; - case PM_DATA_TYPE_UINT64: - *reinterpret_cast(pTarget) = hasValue_ ? (uint64_t)value_ : 0; - break; - default: - assert(false); - } + WriteOptionalValueToBlob_(pBase, this->offsetBytes_, this->outType_, value_); + // reset for the next poll + value_.reset(); } private: double percentile_ = 0.0; - double value_ = 0.0; - bool hasValue_ = false; + mutable std::optional value_; }; template @@ -202,47 +212,30 @@ namespace pmon::mid return; } const double doubleVal = SampleAdapter_::ToDouble(val); - if (!hasValue_) { + if (!value_) { value_ = doubleVal; - hasValue_ = true; return; } if (isMax_) { - if (doubleVal > value_) { + if (doubleVal > *value_) { value_ = doubleVal; } } else { - if (doubleVal < value_) { + if (doubleVal < *value_) { value_ = doubleVal; } } } void GatherToBlob(uint8_t* pBase) const override { - auto* pTarget = pBase + this->offsetBytes_; - switch (this->outType_) { - case PM_DATA_TYPE_DOUBLE: - *reinterpret_cast(pTarget) = hasValue_ ? value_ : 0.0; - break; - case PM_DATA_TYPE_INT32: - case PM_DATA_TYPE_ENUM: - *reinterpret_cast(pTarget) = hasValue_ ? (int32_t)value_ : 0; - break; - case PM_DATA_TYPE_BOOL: - *reinterpret_cast(pTarget) = hasValue_ ? value_ != 0.0 : false; - break; - case PM_DATA_TYPE_UINT64: - *reinterpret_cast(pTarget) = hasValue_ ? (uint64_t)value_ : 0; - break; - default: - assert(false); - } + WriteOptionalValueToBlob_(pBase, this->offsetBytes_, this->outType_, value_); + // reset min/max + value_.reset(); } private: bool isMax_ = false; - double value_ = 0.0; - bool hasValue_ = false; + mutable std::optional value_; }; template @@ -267,42 +260,25 @@ namespace pmon::mid case PM_STAT_MID_POINT: return win.oldest + (win.newest - win.oldest) / 2; default: + + pmlog_error("Unhandled point stat case").pmwatch((int)mode_); assert(false); return win.newest; } } - void GatherToBlob(uint8_t* pBase) const override - { - auto* pTarget = pBase + this->offsetBytes_; - // TODO: consider less conversion/laundering (ToDouble then ToXXX) - const double doubleVal = SampleAdapter_::ToDouble(value_); - switch (this->outType_) { - case PM_DATA_TYPE_DOUBLE: - *reinterpret_cast(pTarget) = hasValue_ ? doubleVal : 0.0; - break; - case PM_DATA_TYPE_INT32: - case PM_DATA_TYPE_ENUM: - *reinterpret_cast(pTarget) = hasValue_ ? (int32_t)doubleVal : 0; - break; - case PM_DATA_TYPE_BOOL: - *reinterpret_cast(pTarget) = hasValue_ ? doubleVal != 0.0 : false; - break; - case PM_DATA_TYPE_UINT64: - *reinterpret_cast(pTarget) = hasValue_ ? SampleAdapter_::ToUint64(value_) : 0; - break; - default: - assert(false); - } - } - private: void SetSampledValue(T val) override { value_ = val; - hasValue_ = SampleAdapter_::HasValue(val); } + void GatherToBlob(uint8_t* pBase) const override + { + WriteOptionalValueToBlob_(pBase, this->offsetBytes_, this->outType_, value_); + // reset for the next poll + value_.reset(); + } + private: PM_STAT mode_ = PM_STAT_MID_POINT; - bool hasValue_ = false; - T value_{}; + mutable std::optional value_; }; } @@ -342,6 +318,7 @@ namespace pmon::mid case PM_STAT_MID_LERP: case PM_STAT_COUNT: default: + pmlog_error("Unhandled stat case").pmwatch((int)stat); assert(false); return {}; } From f9eba92716d6d5eece7c923df0bf3bbaccaace80 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 28 Jan 2026 16:00:32 +0900 Subject: [PATCH 159/205] filling static metrics into dynamic queries --- .../ConcreteMiddleware.cpp | 4 +- .../PresentMonMiddleware/DynamicMetric.h | 5 +- .../PresentMonMiddleware/DynamicQuery.cpp | 24 +++-- .../PresentMonMiddleware/DynamicQuery.h | 10 +- ...ingMetricBinding.cpp => MetricBinding.cpp} | 94 ++++++++++++++++--- .../{RingMetricBinding.h => MetricBinding.h} | 14 +-- .../PresentMonMiddleware.vcxproj | 4 +- .../PresentMonMiddleware.vcxproj.filters | 4 +- 8 files changed, 119 insertions(+), 40 deletions(-) rename IntelPresentMon/PresentMonMiddleware/{RingMetricBinding.cpp => MetricBinding.cpp} (68%) rename IntelPresentMon/PresentMonMiddleware/{RingMetricBinding.h => MetricBinding.h} (61%) diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index 90667e97..f4c5a123 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -196,7 +196,7 @@ namespace pmon::mid { pmlog_dbg("Registering dynamic query").pmwatch(queryElements.size()).pmwatch(windowSizeMs).pmwatch(metricOffsetMs); const auto qpcPeriod = util::GetTimestampPeriodSeconds(); - return new PM_DYNAMIC_QUERY{ queryElements, windowSizeMs, metricOffsetMs, qpcPeriod, *pComms }; + return new PM_DYNAMIC_QUERY{ queryElements, windowSizeMs, metricOffsetMs, qpcPeriod, *pComms, *this }; } void ConcreteMiddleware::PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t* numSwapChains) @@ -208,7 +208,7 @@ namespace pmon::mid pFrameSource = &GetFrameMetricSource_(processId); } // execute the dynamic poll operation - pQuery->Poll(pBlob, *pComms, (uint64_t)util::GetCurrentTimestamp(), pFrameSource); + pQuery->Poll(pBlob, *pComms, (uint64_t)util::GetCurrentTimestamp(), pFrameSource, processId); } void ConcreteMiddleware::PollStaticQuery(const PM_QUERY_ELEMENT& element, uint32_t processId, uint8_t* pBlob) diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h index cbed6f7c..c51b1eb1 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h @@ -14,6 +14,7 @@ #include "../CommonUtilities/Meta.h" #include "../CommonUtilities/Memory.h" #include "../CommonUtilities/mc/FrameMetricsMemberMap.h" +#include "../CommonUtilities/log/Log.h" #include "../Interprocess/source/IntrospectionHelpers.h" #include "../Interprocess/source/PmStatusError.h" #include "../PresentMonAPIWrapperCommon/Introspection.h" @@ -209,8 +210,10 @@ namespace pmon::mid using MemberType = typename MemberInfo::MemberType; return std::make_unique>(Metric); } + pmlog_error("Cannot make dynamic metric for").pmwatch((int)Metric); return {}; - }, std::unique_ptr>{} + }, + std::unique_ptr>{} ); } } diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp index ab0a01a3..65efad99 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp @@ -43,7 +43,7 @@ namespace std } PM_DYNAMIC_QUERY::PM_DYNAMIC_QUERY(std::span qels, double windowSizeMs, - double windowOffsetMs, double qpcPeriodSeconds, ipc::MiddlewareComms& comms) + double windowOffsetMs, double qpcPeriodSeconds, ipc::MiddlewareComms& comms, pmon::mid::Middleware& middleware) : windowSizeQpc_{ int64_t((windowSizeMs / 1000.) / qpcPeriodSeconds) }, windowOffsetQpc_{ int64_t((windowOffsetMs / 1000.) / qpcPeriodSeconds) } @@ -52,13 +52,21 @@ PM_DYNAMIC_QUERY::PM_DYNAMIC_QUERY(std::span qels, double wind pmapi::intro::Root introRoot{ introBase, [](const PM_INTROSPECTION_ROOT*) {} }; pmon::mid::ValidateQueryElements(qels, PM_METRIC_TYPE_DYNAMIC, introRoot, comms); - std::unordered_map telemetryBindings; - RingMetricBinding* frameBinding = nullptr; + std::unordered_map telemetryBindings; + MetricBinding* frameBinding = nullptr; size_t blobCursor = 0; for (auto& qel : qels) { - RingMetricBinding* binding = nullptr; - if (qel.deviceId == ipc::kUniversalDeviceId) { + MetricBinding* binding = nullptr; + const auto metricView = introRoot.FindMetric(qel.metric); + const auto metricType = metricView.GetType(); + const bool isStaticMetric = metricType == PM_METRIC_TYPE_STATIC; + if (isStaticMetric) { + auto bindingPtr = MakeStaticMetricBinding(qel, middleware); + binding = bindingPtr.get(); + ringMetricPtrs_.push_back(std::move(bindingPtr)); + } + else if (qel.deviceId == ipc::kUniversalDeviceId) { binding = frameBinding; if (!binding) { auto bindingPtr = MakeFrameMetricBinding(qel); @@ -77,7 +85,7 @@ PM_DYNAMIC_QUERY::PM_DYNAMIC_QUERY(std::span qels, double wind binding = it->second; } else { - auto bindingPtr = MakeTelemetryRingMetricBinding(qel, introRoot); + auto bindingPtr = MakeTelemetryMetricBinding(qel, introRoot); binding = bindingPtr.get(); ringMetricPtrs_.push_back(std::move(bindingPtr)); telemetryBindings.emplace(key, binding); @@ -110,10 +118,10 @@ DynamicQueryWindow PM_DYNAMIC_QUERY::GenerateQueryWindow_(int64_t nowTimestamp) } void PM_DYNAMIC_QUERY::Poll(uint8_t* pBlobBase, ipc::MiddlewareComms& comms, - uint64_t nowTimestamp, FrameMetricsSource* frameSource) const + uint64_t nowTimestamp, FrameMetricsSource* frameSource, uint32_t processId) const { const auto window = GenerateQueryWindow_(nowTimestamp); for (auto& pRing : ringMetricPtrs_) { - pRing->Poll(window, pBlobBase, comms, frameSource); + pRing->Poll(window, pBlobBase, comms, frameSource, processId); } } diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h index 32503f6b..e1b65089 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h @@ -5,7 +5,7 @@ #include #include #include -#include "RingMetricBinding.h" +#include "MetricBinding.h" #include "DynamicQueryWindow.h" #include "../PresentMonAPI2/PresentMonAPI.h" #include "../CommonUtilities/Qpc.h" @@ -13,7 +13,7 @@ namespace pmon::mid { - class RingMetricBinding; + class MetricBinding; } namespace pmon::ipc @@ -25,16 +25,16 @@ struct PM_DYNAMIC_QUERY { public: PM_DYNAMIC_QUERY(std::span qels, double windowSizeMs, double windowOffsetMs, - double qpcPeriodSeconds, pmon::ipc::MiddlewareComms& comms); + double qpcPeriodSeconds, pmon::ipc::MiddlewareComms& comms, pmon::mid::Middleware& middleware); size_t GetBlobSize() const; void Poll(uint8_t* pBlobBase, pmon::ipc::MiddlewareComms& comms, - uint64_t nowTimestamp, pmon::mid::FrameMetricsSource* frameSource) const; + uint64_t nowTimestamp, pmon::mid::FrameMetricsSource* frameSource, uint32_t processId) const; private: // functions pmon::mid::DynamicQueryWindow GenerateQueryWindow_(int64_t nowTimestamp) const; // data - std::vector> ringMetricPtrs_; + std::vector> ringMetricPtrs_; size_t blobSize_; // window parameters; these could theoretically be independent of query but current API couples them int64_t windowSizeQpc_ = 0; diff --git a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp b/IntelPresentMon/PresentMonMiddleware/MetricBinding.cpp similarity index 68% rename from IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp rename to IntelPresentMon/PresentMonMiddleware/MetricBinding.cpp index fb14a5f6..58704832 100644 --- a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.cpp +++ b/IntelPresentMon/PresentMonMiddleware/MetricBinding.cpp @@ -1,8 +1,11 @@ -#include "RingMetricBinding.h" +#include "MetricBinding.h" #include "FrameMetricsSource.h" +#include "Middleware.h" #include "../Interprocess/source/Interprocess.h" +#include "../Interprocess/source/IntrospectionHelpers.h" #include "../Interprocess/source/IntrospectionDataTypeMapping.h" #include "../Interprocess/source/SystemDeviceId.h" +#include "../CommonUtilities/Memory.h" #include #include @@ -18,7 +21,7 @@ namespace pmon::mid std::is_same_v; template - class RingMetricBindingBase_ : public RingMetricBinding + class MetricBindingBase_ : public MetricBinding { public: void AddMetricStat(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) override @@ -95,10 +98,10 @@ namespace pmon::mid }; template - class TelemetryRingMetricBinding_ : public RingMetricBindingBase_ + class TelemetryMetricBinding_ : public MetricBindingBase_ { public: - explicit TelemetryRingMetricBinding_(const PM_QUERY_ELEMENT& qel) + explicit TelemetryMetricBinding_(const PM_QUERY_ELEMENT& qel) : deviceId_{ qel.deviceId }, arrayIndex_{ qel.arrayIndex }, @@ -107,9 +110,10 @@ namespace pmon::mid } void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase, ipc::MiddlewareComms& comms, - FrameMetricsSource* pFrameSource) const override + FrameMetricsSource* pFrameSource, uint32_t processId) const override { (void)pFrameSource; + (void)processId; const ipc::HistoryRing* pRing = nullptr; using ValueType = typename S::value_type; @@ -136,7 +140,7 @@ namespace pmon::mid PM_METRIC metricId_; }; - class FrameMetricBinding_ : public RingMetricBindingBase_ + class FrameMetricBinding_ : public MetricBindingBase_ { public: explicit FrameMetricBinding_(const PM_QUERY_ELEMENT&) @@ -144,9 +148,10 @@ namespace pmon::mid } void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase, ipc::MiddlewareComms& comms, - FrameMetricsSource* pFrameSource) const override + FrameMetricsSource* pFrameSource, uint32_t processId) const override { (void)comms; + (void)processId; if (pFrameSource == nullptr) { throw pmon::util::Except(PM_STATUS_FAILURE, @@ -167,15 +172,71 @@ namespace pmon::mid } }; + class StaticMetricBinding_ : public MetricBinding + { + public: + StaticMetricBinding_(Middleware& middleware, const PM_QUERY_ELEMENT& qel) + : + middleware_{ middleware }, + metricId_{ qel.metric }, + deviceId_{ qel.deviceId }, + arrayIndex_{ qel.arrayIndex } + { + } + + void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase, ipc::MiddlewareComms& comms, + FrameMetricsSource* pFrameSource, uint32_t processId) const override + { + (void)window; + (void)comms; + (void)pFrameSource; + + const PM_QUERY_ELEMENT element{ + .metric = metricId_, + .stat = PM_STAT_NONE, + .deviceId = deviceId_, + .arrayIndex = arrayIndex_, + .dataOffset = dataOffset_, + .dataSize = dataSize_, + }; + + middleware_.PollStaticQuery(element, processId, pBlobBase + dataOffset_); + } + + void Finalize() override + { + } + + void AddMetricStat(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) override + { + const auto metricView = intro.FindMetric(qel.metric); + const auto dataType = metricView.GetDataTypeInfo().GetPolledType(); + const auto dataSize = ipc::intro::GetDataTypeSize(dataType); + qel.dataSize = (uint64_t)dataSize; + qel.dataOffset = (uint64_t)util::PadToAlignment((size_t)qel.dataOffset, dataSize); + + dataOffset_ = qel.dataOffset; + dataSize_ = qel.dataSize; + } + + private: + Middleware& middleware_; + PM_METRIC metricId_; + uint32_t deviceId_; + uint32_t arrayIndex_; + uint64_t dataOffset_ = 0; + uint64_t dataSize_ = 0; + }; + template - struct TelemetryRingBindingBridger_ + struct TelemetryBindingBridger_ { - static std::unique_ptr Invoke(PM_ENUM, PM_QUERY_ELEMENT& qel) + static std::unique_ptr Invoke(PM_ENUM, PM_QUERY_ELEMENT& qel) { using ValueType = typename ipc::intro::DataTypeToStaticType
::type; if constexpr (IsTelemetryRingValue_) { using SampleType = ipc::TelemetrySample; - return std::make_unique>(qel); + return std::make_unique>(qel); } else { assert(false); @@ -183,7 +244,7 @@ namespace pmon::mid } } - static std::unique_ptr Default(PM_QUERY_ELEMENT&) + static std::unique_ptr Default(PM_QUERY_ELEMENT&) { assert(false); return {}; @@ -191,16 +252,21 @@ namespace pmon::mid }; } - std::unique_ptr MakeFrameMetricBinding(PM_QUERY_ELEMENT& qel) + std::unique_ptr MakeFrameMetricBinding(PM_QUERY_ELEMENT& qel) { return std::make_unique(qel); } - std::unique_ptr MakeTelemetryRingMetricBinding(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) + std::unique_ptr MakeTelemetryMetricBinding(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) { const auto metricView = intro.FindMetric(qel.metric); const auto typeInfo = metricView.GetDataTypeInfo(); - return ipc::intro::BridgeDataTypeWithEnum( + return ipc::intro::BridgeDataTypeWithEnum( typeInfo.GetFrameType(), typeInfo.GetEnumId(), qel); } + + std::unique_ptr MakeStaticMetricBinding(PM_QUERY_ELEMENT& qel, Middleware& middleware) + { + return std::make_unique(middleware, qel); + } } diff --git a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h b/IntelPresentMon/PresentMonMiddleware/MetricBinding.h similarity index 61% rename from IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h rename to IntelPresentMon/PresentMonMiddleware/MetricBinding.h index c63618fc..e965d19a 100644 --- a/IntelPresentMon/PresentMonMiddleware/RingMetricBinding.h +++ b/IntelPresentMon/PresentMonMiddleware/MetricBinding.h @@ -21,20 +21,22 @@ namespace pmon::ipc namespace pmon::mid { class FrameMetricsSource; + class Middleware; - // container to bind and type erase a metric ring static type to one or more metrics + // container to bind and type erase a metric source type to one or more metrics // (telemetry rings are always 1 metric per ring, but the frame ring serves many metrics) - class RingMetricBinding + class MetricBinding { public: - virtual ~RingMetricBinding() = default; + virtual ~MetricBinding() = default; virtual void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase, ipc::MiddlewareComms& comms, - FrameMetricsSource* pFrameSource) const = 0; + FrameMetricsSource* pFrameSource, uint32_t processId) const = 0; virtual void Finalize() = 0; virtual void AddMetricStat(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) = 0; }; - std::unique_ptr MakeFrameMetricBinding(PM_QUERY_ELEMENT& qel); - std::unique_ptr MakeTelemetryRingMetricBinding(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro); + std::unique_ptr MakeFrameMetricBinding(PM_QUERY_ELEMENT& qel); + std::unique_ptr MakeTelemetryMetricBinding(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro); + std::unique_ptr MakeStaticMetricBinding(PM_QUERY_ELEMENT& qel, Middleware& middleware); } diff --git a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj index d05dbb4d..8f84047e 100644 --- a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj +++ b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj @@ -21,7 +21,7 @@ - + @@ -34,7 +34,7 @@ - + diff --git a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters index 873fd144..e4d6f226 100644 --- a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters +++ b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters @@ -33,7 +33,7 @@ Header Files - + Header Files @@ -74,7 +74,7 @@ Source Files - + Source Files From 512932773e23e532b05794ea1de731387996e1a7 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 28 Jan 2026 17:08:35 +0900 Subject: [PATCH 160/205] fixing static into dynamic query filling when data type mismatches --- .../PresentMonMiddleware/MetricBinding.cpp | 89 ++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/IntelPresentMon/PresentMonMiddleware/MetricBinding.cpp b/IntelPresentMon/PresentMonMiddleware/MetricBinding.cpp index 58704832..46029bdd 100644 --- a/IntelPresentMon/PresentMonMiddleware/MetricBinding.cpp +++ b/IntelPresentMon/PresentMonMiddleware/MetricBinding.cpp @@ -191,6 +191,22 @@ namespace pmon::mid (void)comms; (void)pFrameSource; + if (needsConversion_) { + alignas(alignof(uint64_t)) uint8_t scratch[kScratchSize_]{}; + const PM_QUERY_ELEMENT element{ + .metric = metricId_, + .stat = PM_STAT_NONE, + .deviceId = deviceId_, + .arrayIndex = arrayIndex_, + .dataOffset = 0, + .dataSize = frameDataSize_, + }; + + middleware_.PollStaticQuery(element, processId, scratch); + ConvertStaticMetricValue_(pBlobBase + dataOffset_, outputType_, scratch, frameType_); + return; + } + const PM_QUERY_ELEMENT element{ .metric = metricId_, .stat = PM_STAT_NONE, @@ -210,22 +226,91 @@ namespace pmon::mid void AddMetricStat(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) override { const auto metricView = intro.FindMetric(qel.metric); - const auto dataType = metricView.GetDataTypeInfo().GetPolledType(); - const auto dataSize = ipc::intro::GetDataTypeSize(dataType); + const auto typeInfo = metricView.GetDataTypeInfo(); + frameType_ = typeInfo.GetFrameType(); + outputType_ = typeInfo.GetPolledType(); + const auto dataSize = ipc::intro::GetDataTypeSize(outputType_); qel.dataSize = (uint64_t)dataSize; qel.dataOffset = (uint64_t)util::PadToAlignment((size_t)qel.dataOffset, dataSize); dataOffset_ = qel.dataOffset; dataSize_ = qel.dataSize; + frameDataSize_ = ipc::intro::GetDataTypeSize(frameType_); + needsConversion_ = frameType_ != outputType_; } private: + static constexpr size_t kScratchSize_ = ipc::intro::DataTypeToStaticType_sz; + + template + static void WriteConvertedStaticValue_(uint8_t* pTarget, PM_DATA_TYPE outType, T value) + { + switch (outType) { + case PM_DATA_TYPE_DOUBLE: + *reinterpret_cast(pTarget) = (double)value; + break; + case PM_DATA_TYPE_INT32: + case PM_DATA_TYPE_ENUM: + *reinterpret_cast(pTarget) = (int32_t)value; + break; + case PM_DATA_TYPE_UINT32: + *reinterpret_cast(pTarget) = (uint32_t)value; + break; + case PM_DATA_TYPE_UINT64: + *reinterpret_cast(pTarget) = (uint64_t)value; + break; + case PM_DATA_TYPE_BOOL: + *reinterpret_cast(pTarget) = value != (T)0; + break; + default: + assert(false); + break; + } + } + + static void ConvertStaticMetricValue_(uint8_t* pTarget, PM_DATA_TYPE outType, const uint8_t* pSource, PM_DATA_TYPE inType) + { + if (inType == PM_DATA_TYPE_STRING || outType == PM_DATA_TYPE_STRING || + inType == PM_DATA_TYPE_VOID || outType == PM_DATA_TYPE_VOID) { + assert(false); + return; + } + + switch (inType) { + case PM_DATA_TYPE_DOUBLE: + WriteConvertedStaticValue_(pTarget, outType, *reinterpret_cast(pSource)); + break; + case PM_DATA_TYPE_INT32: + WriteConvertedStaticValue_(pTarget, outType, *reinterpret_cast(pSource)); + break; + case PM_DATA_TYPE_UINT32: + WriteConvertedStaticValue_(pTarget, outType, *reinterpret_cast(pSource)); + break; + case PM_DATA_TYPE_UINT64: + WriteConvertedStaticValue_(pTarget, outType, *reinterpret_cast(pSource)); + break; + case PM_DATA_TYPE_ENUM: + WriteConvertedStaticValue_(pTarget, outType, *reinterpret_cast(pSource)); + break; + case PM_DATA_TYPE_BOOL: + WriteConvertedStaticValue_(pTarget, outType, *reinterpret_cast(pSource)); + break; + default: + assert(false); + break; + } + } + Middleware& middleware_; PM_METRIC metricId_; uint32_t deviceId_; uint32_t arrayIndex_; uint64_t dataOffset_ = 0; uint64_t dataSize_ = 0; + uint64_t frameDataSize_ = 0; + PM_DATA_TYPE frameType_ = PM_DATA_TYPE_VOID; + PM_DATA_TYPE outputType_ = PM_DATA_TYPE_VOID; + bool needsConversion_ = false; }; template From d72bd8913a03d98f3b625594f132059a42dce590 Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 29 Jan 2026 06:48:44 +0900 Subject: [PATCH 161/205] implement multi swap + fix percentile stats --- .../CommonUtilities/CommonUtilities.vcxproj | 3 +- .../CommonUtilities.vcxproj.filters | 5 ++- .../mc/FrameMetricsMemberMap.h | 4 +- .../ConcreteMiddleware.cpp | 24 +++++++--- .../PresentMonMiddleware/DynamicQuery.cpp | 37 ++++++++++++++-- .../PresentMonMiddleware/DynamicQuery.h | 5 ++- .../FrameMetricsSource.cpp | 44 +++++++++++-------- .../PresentMonMiddleware/FrameMetricsSource.h | 14 +----- .../PresentMonMiddleware/MetricBinding.cpp | 38 +++++++++------- .../PresentMonMiddleware/MetricBinding.h | 5 ++- 10 files changed, 117 insertions(+), 62 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj index 834f28ea..064aaf15 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj @@ -69,6 +69,7 @@ + @@ -396,4 +397,4 @@ - + \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters index fde2d727..29f9cbe0 100644 --- a/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters +++ b/IntelPresentMon/CommonUtilities/CommonUtilities.vcxproj.filters @@ -312,6 +312,9 @@ Source Files + + Header Files + @@ -519,4 +522,4 @@ - + \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/mc/FrameMetricsMemberMap.h b/IntelPresentMon/CommonUtilities/mc/FrameMetricsMemberMap.h index f50a21fe..c3e88087 100644 --- a/IntelPresentMon/CommonUtilities/mc/FrameMetricsMemberMap.h +++ b/IntelPresentMon/CommonUtilities/mc/FrameMetricsMemberMap.h @@ -42,5 +42,7 @@ namespace pmon::util::metrics template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msInstrumentedLatency;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msFlipDelay;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::frameType;}; - templateinline constexpr bool HasFrameMetricMember=requires{FrameMetricMember::member;}; + + template + inline constexpr bool HasFrameMetricMember = requires{ FrameMetricMember::member; }; } diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index f4c5a123..7327a2f1 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -201,14 +201,28 @@ namespace pmon::mid void ConcreteMiddleware::PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t* numSwapChains) { - // TODO: implement multi-swap handling - // locate frame metric source for target process if required + if (numSwapChains == nullptr) { + throw Except(PM_STATUS_BAD_ARGUMENT, "numSwapChains pointer is null."); + } + + const uint32_t maxSwapChains = *numSwapChains; + if (maxSwapChains == 0) { + throw Except(PM_STATUS_BAD_ARGUMENT, "numSwapChains is zero."); + } + if (pBlob == nullptr) { + throw Except(PM_STATUS_BAD_ARGUMENT, "pBlob pointer is null."); + } + + *numSwapChains = 0; + FrameMetricsSource* pFrameSource = nullptr; - if (processId) { + if (processId != 0) { pFrameSource = &GetFrameMetricSource_(processId); + pFrameSource->Update(); } - // execute the dynamic poll operation - pQuery->Poll(pBlob, *pComms, (uint64_t)util::GetCurrentTimestamp(), pFrameSource, processId); + + const uint64_t nowTimestamp = (uint64_t)util::GetCurrentTimestamp(); + *numSwapChains = pQuery->Poll(pBlob, *pComms, nowTimestamp, pFrameSource, processId, maxSwapChains); } void ConcreteMiddleware::PollStaticQuery(const PM_QUERY_ELEMENT& element, uint32_t processId, uint8_t* pBlob) diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp index 65efad99..0d776ede 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp @@ -1,10 +1,12 @@ #include "DynamicQuery.h" +#include "FrameMetricsSource.h" #include "QueryValidation.h" #include "../PresentMonAPIWrapperCommon/Introspection.h" #include "../Interprocess/source/SystemDeviceId.h" #include "../Interprocess/source/Interprocess.h" #include "../CommonUtilities/Hash.h" #include +#include #include using namespace pmon; @@ -117,11 +119,38 @@ DynamicQueryWindow PM_DYNAMIC_QUERY::GenerateQueryWindow_(int64_t nowTimestamp) return { .oldest = uint64_t(oldest), .newest = uint64_t(newest)}; } -void PM_DYNAMIC_QUERY::Poll(uint8_t* pBlobBase, ipc::MiddlewareComms& comms, - uint64_t nowTimestamp, FrameMetricsSource* frameSource, uint32_t processId) const +uint32_t PM_DYNAMIC_QUERY::Poll(uint8_t* pBlobBase, ipc::MiddlewareComms& comms, + uint64_t nowTimestamp, FrameMetricsSource* frameSource, uint32_t processId, uint32_t maxSwapChains) const { + if (pBlobBase == nullptr || maxSwapChains == 0) { + return 0; + } + const auto window = GenerateQueryWindow_(nowTimestamp); - for (auto& pRing : ringMetricPtrs_) { - pRing->Poll(window, pBlobBase, comms, frameSource, processId); + std::vector swapChainAddresses; + if (frameSource != nullptr) { + swapChainAddresses = frameSource->GetSwapChainAddressesInTimestampRange(window.oldest, window.newest); + } + + auto pollOnce = [&](const SwapChainState* pSwapChain, uint8_t* pBlob) { + for (auto& pRing : ringMetricPtrs_) { + pRing->Poll(window, pBlob, comms, pSwapChain, processId); + } + }; + + if (swapChainAddresses.empty()) { + pollOnce(nullptr, pBlobBase); + return 1; } + + const uint32_t swapChainsToPoll = (uint32_t)std::min(swapChainAddresses.size(), maxSwapChains); + for (uint32_t i = 0; i < swapChainsToPoll; ++i) { + const SwapChainState* pSwapChain = frameSource != nullptr + ? frameSource->FindSwapChainState(swapChainAddresses[i]) + : nullptr; + pollOnce(pSwapChain, pBlobBase); + pBlobBase += blobSize_; + } + + return swapChainsToPoll; } diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h index e1b65089..c2bdd071 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h @@ -14,6 +14,7 @@ namespace pmon::mid { class MetricBinding; + class FrameMetricsSource; } namespace pmon::ipc @@ -27,8 +28,8 @@ struct PM_DYNAMIC_QUERY PM_DYNAMIC_QUERY(std::span qels, double windowSizeMs, double windowOffsetMs, double qpcPeriodSeconds, pmon::ipc::MiddlewareComms& comms, pmon::mid::Middleware& middleware); size_t GetBlobSize() const; - void Poll(uint8_t* pBlobBase, pmon::ipc::MiddlewareComms& comms, - uint64_t nowTimestamp, pmon::mid::FrameMetricsSource* frameSource, uint32_t processId) const; + uint32_t Poll(uint8_t* pBlobBase, pmon::ipc::MiddlewareComms& comms, + uint64_t nowTimestamp, pmon::mid::FrameMetricsSource* frameSource, uint32_t processId, uint32_t maxSwapChains) const; private: // functions diff --git a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp index e0132371..febc0af1 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2025 Intel Corporation #include "FrameMetricsSource.h" +#include namespace pmon::mid { @@ -270,41 +271,46 @@ namespace pmon::mid return output; } - const SwapChainState* FrameMetricsSource::GetActiveSwapChainState_(uint64_t start, uint64_t end) const + std::vector FrameMetricsSource::GetSwapChainAddressesInTimestampRange(uint64_t start, uint64_t end) const { - const SwapChainState* selectedState = nullptr; - uint64_t selectedCount = 0; + std::vector> candidates; + candidates.reserve(swapChains_.size()); for (const auto& [address, state] : swapChains_) { if (state.Empty()) { continue; } const size_t count = state.CountInTimestampRange(start, end); - if (selectedState == nullptr || - count > selectedCount) { - selectedState = &state; - selectedCount = count; + if (count > 0) { + candidates.emplace_back(address, count); } } - return selectedState; - } + if (candidates.size() > 1) { + std::sort(candidates.begin(), candidates.end(), + [](const auto& lhs, const auto& rhs) { + if (lhs.second != rhs.second) { + return lhs.second > rhs.second; + } + return lhs.first < rhs.first; + }); + } - const util::metrics::FrameMetrics* FrameMetricsSource::FindNearestActive(uint64_t start, uint64_t end, uint64_t timestamp) const - { - const auto* state = GetActiveSwapChainState_(start, end); - if (state == nullptr || state->Empty()) { - return nullptr; + std::vector output; + output.reserve(candidates.size()); + for (const auto& candidate : candidates) { + output.push_back(candidate.first); } - const size_t index = state->NearestIndex(timestamp); - return &state->At(index); + return output; } - bool FrameMetricsSource::HasActiveSwapChainSamples(uint64_t start, uint64_t end) const + const SwapChainState* FrameMetricsSource::FindSwapChainState(uint64_t swapChainAddress) const { - const auto* state = GetActiveSwapChainState_(start, end); - return state != nullptr && !state->Empty(); + if (auto it = swapChains_.find(swapChainAddress); it != swapChains_.end()) { + return &it->second; + } + return nullptr; } const util::QpcConverter& FrameMetricsSource::GetQpcConverter() const diff --git a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h index ab5be271..84acf1d1 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h +++ b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h @@ -78,22 +78,12 @@ namespace pmon::mid void Update(); std::vector Consume(size_t maxFrames); - template - size_t ForEachInActiveTimestampRange(uint64_t start, uint64_t end, F&& func) const - { - const auto* pSwap = GetActiveSwapChainState_(start, end); - if (pSwap == nullptr) { - return 0; - } - return pSwap->ForEachInTimestampRange(start, end, std::forward(func)); - } - const util::metrics::FrameMetrics* FindNearestActive(uint64_t start, uint64_t end, uint64_t timestamp) const; - bool HasActiveSwapChainSamples(uint64_t start, uint64_t end) const; + std::vector GetSwapChainAddressesInTimestampRange(uint64_t start, uint64_t end) const; + const SwapChainState* FindSwapChainState(uint64_t swapChainAddress) const; const util::QpcConverter& GetQpcConverter() const; private: void ProcessNewFrames_(); - const SwapChainState* GetActiveSwapChainState_(uint64_t start, uint64_t end) const; ipc::MiddlewareComms& comms_; const ipc::FrameDataStore* pStore_ = nullptr; diff --git a/IntelPresentMon/PresentMonMiddleware/MetricBinding.cpp b/IntelPresentMon/PresentMonMiddleware/MetricBinding.cpp index 46029bdd..985ada65 100644 --- a/IntelPresentMon/PresentMonMiddleware/MetricBinding.cpp +++ b/IntelPresentMon/PresentMonMiddleware/MetricBinding.cpp @@ -50,6 +50,10 @@ namespace pmon::mid void Finalize() override { + for (const auto& metric : metricPtrs_) { + metric->FinalizeStats(); + } + needsFullTraversalMetricPtrs_.clear(); for (const auto& metric : metricPtrs_) { if (metric->NeedsFullTraversal()) { @@ -110,9 +114,9 @@ namespace pmon::mid } void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase, ipc::MiddlewareComms& comms, - FrameMetricsSource* pFrameSource, uint32_t processId) const override + const SwapChainState* pSwapChain, uint32_t processId) const override { - (void)pFrameSource; + (void)pSwapChain; (void)processId; const ipc::HistoryRing* pRing = nullptr; @@ -148,26 +152,30 @@ namespace pmon::mid } void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase, ipc::MiddlewareComms& comms, - FrameMetricsSource* pFrameSource, uint32_t processId) const override + const SwapChainState* pSwapChain, uint32_t processId) const override { (void)comms; (void)processId; - if (pFrameSource == nullptr) { - throw pmon::util::Except(PM_STATUS_FAILURE, - "Frame metrics source missing for dynamic query."); + if (pSwapChain == nullptr) { + // TODO: consider logging (debug or verbose) that empty chain was processed + auto forEachFunc = [](uint64_t, uint64_t, auto&&) {}; + auto nearestFunc = [](uint64_t) -> const util::metrics::FrameMetrics* { + return nullptr; + }; + this->ProcessSamples_(window, pBlobBase, forEachFunc, nearestFunc, false); + return; } - pFrameSource->Update(); - - auto forEachFunc = [pFrameSource](uint64_t start, uint64_t end, auto&& func) { - pFrameSource->ForEachInActiveTimestampRange(start, end, std::forward(func)); + auto forEachFunc = [pSwapChain](uint64_t start, uint64_t end, auto&& func) { + pSwapChain->ForEachInTimestampRange(start, end, std::forward(func)); }; - auto nearestFunc = [pFrameSource, &window](uint64_t point) -> const util::metrics::FrameMetrics* { - return pFrameSource->FindNearestActive(window.oldest, window.newest, point); + auto nearestFunc = [pSwapChain](uint64_t point) -> const util::metrics::FrameMetrics* { + const size_t index = pSwapChain->NearestIndex(point); + return &pSwapChain->At(index); }; - const bool hasSamples = pFrameSource->HasActiveSwapChainSamples(window.oldest, window.newest); + const bool hasSamples = pSwapChain->CountInTimestampRange(window.oldest, window.newest) > 0; this->ProcessSamples_(window, pBlobBase, forEachFunc, nearestFunc, hasSamples); } }; @@ -185,11 +193,11 @@ namespace pmon::mid } void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase, ipc::MiddlewareComms& comms, - FrameMetricsSource* pFrameSource, uint32_t processId) const override + const SwapChainState* pSwapChain, uint32_t processId) const override { (void)window; (void)comms; - (void)pFrameSource; + (void)pSwapChain; if (needsConversion_) { alignas(alignof(uint64_t)) uint8_t scratch[kScratchSize_]{}; diff --git a/IntelPresentMon/PresentMonMiddleware/MetricBinding.h b/IntelPresentMon/PresentMonMiddleware/MetricBinding.h index e965d19a..4e9d1b8a 100644 --- a/IntelPresentMon/PresentMonMiddleware/MetricBinding.h +++ b/IntelPresentMon/PresentMonMiddleware/MetricBinding.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include #include @@ -20,7 +21,7 @@ namespace pmon::ipc namespace pmon::mid { - class FrameMetricsSource; + class SwapChainState; class Middleware; // container to bind and type erase a metric source type to one or more metrics @@ -31,7 +32,7 @@ namespace pmon::mid virtual ~MetricBinding() = default; virtual void Poll(const DynamicQueryWindow& window, uint8_t* pBlobBase, ipc::MiddlewareComms& comms, - FrameMetricsSource* pFrameSource, uint32_t processId) const = 0; + const SwapChainState* pSwapChain, uint32_t processId) const = 0; virtual void Finalize() = 0; virtual void AddMetricStat(PM_QUERY_ELEMENT& qel, const pmapi::intro::Root& intro) = 0; }; From 4cae43ebd57b13c27f04df2b74f7deaee93076d0 Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 29 Jan 2026 06:56:47 +0900 Subject: [PATCH 162/205] validate pid input to poll dynamic --- .../PresentMonMiddleware/ConcreteMiddleware.cpp | 4 ++++ IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp | 7 +++++++ IntelPresentMon/PresentMonMiddleware/DynamicQuery.h | 2 ++ 3 files changed, 13 insertions(+) diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index 7327a2f1..6c632b6a 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -212,6 +212,10 @@ namespace pmon::mid if (pBlob == nullptr) { throw Except(PM_STATUS_BAD_ARGUMENT, "pBlob pointer is null."); } + if (processId == 0 && pQuery->HasFrameMetrics()) { + throw Except(PM_STATUS_BAD_ARGUMENT, + "processId is zero but query requires frame metrics."); + } *numSwapChains = 0; diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp index 0d776ede..7b7b9f2f 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.cpp @@ -103,6 +103,8 @@ PM_DYNAMIC_QUERY::PM_DYNAMIC_QUERY(std::span qels, double wind binding->Finalize(); } + hasFrameMetrics_ = frameBinding != nullptr; + // make sure blob sizes are multiple of 16 bytes for blob array alignment purposes blobSize_ = util::PadToAlignment(blobCursor, 16u); } @@ -112,6 +114,11 @@ size_t PM_DYNAMIC_QUERY::GetBlobSize() const return blobSize_; } +bool PM_DYNAMIC_QUERY::HasFrameMetrics() const +{ + return hasFrameMetrics_; +} + DynamicQueryWindow PM_DYNAMIC_QUERY::GenerateQueryWindow_(int64_t nowTimestamp) const { const auto newest = nowTimestamp - windowOffsetQpc_; diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h index c2bdd071..b2a6992c 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicQuery.h @@ -28,6 +28,7 @@ struct PM_DYNAMIC_QUERY PM_DYNAMIC_QUERY(std::span qels, double windowSizeMs, double windowOffsetMs, double qpcPeriodSeconds, pmon::ipc::MiddlewareComms& comms, pmon::mid::Middleware& middleware); size_t GetBlobSize() const; + bool HasFrameMetrics() const; uint32_t Poll(uint8_t* pBlobBase, pmon::ipc::MiddlewareComms& comms, uint64_t nowTimestamp, pmon::mid::FrameMetricsSource* frameSource, uint32_t processId, uint32_t maxSwapChains) const; @@ -37,6 +38,7 @@ struct PM_DYNAMIC_QUERY // data std::vector> ringMetricPtrs_; size_t blobSize_; + bool hasFrameMetrics_ = false; // window parameters; these could theoretically be independent of query but current API couples them int64_t windowSizeQpc_ = 0; int64_t windowOffsetQpc_ = 0; From 0faee894216d9d22476db9f63ec9575e04d275ec Mon Sep 17 00:00:00 2001 From: "Galvan, Mark" Date: Wed, 28 Jan 2026 15:12:02 -0800 Subject: [PATCH 163/205] Adding in missed dynamic metrics to unified metrics calc. --- .../CommonUtilities/mc/MetricsCalculator.cpp | 10 +++ .../CommonUtilities/mc/MetricsTypes.h | 3 + IntelPresentMon/UnitTests/MetricsCore.cpp | 64 +++++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 80bfdbd8..1d04b765 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -84,6 +84,12 @@ namespace pmon::util::metrics ? qpc.DeltaSignedMilliSeconds(startQpc, CPUStartTimeQpc) : 0.0; } + + double ComputeFpsFromMs(double ms) + { + return ms > 0.0 ? 1000.0 / ms : 0.0; + } + } // 2) Public entry points @@ -308,6 +314,10 @@ namespace pmon::util::metrics metrics.cpuStartQpc = CalculateCPUStart(chain, present); metrics.cpuStartMs = ComputeCPUStartTimeMs(qpc, metrics.cpuStartQpc); + metrics.fpsPresent = ComputeFpsFromMs(metrics.msBetweenPresents); + metrics.fpsDisplay = ComputeFpsFromMs(metrics.msBetweenDisplayChange); + metrics.fpsApplication = ComputeFpsFromMs(metrics.msCPUTime); + return result; } diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h index 946bad29..cbbb87c4 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h @@ -112,6 +112,7 @@ namespace pmon::util::metrics { double msGpuDuration = 0; double msVideoDuration = 0; double msSinceInput = 0; + double fpsPresent = 0; // Display Metrics (displayed frames only) double msDisplayLatency = 0; @@ -121,11 +122,13 @@ namespace pmon::util::metrics { uint64_t screenTimeQpc = 0; std::optional msReadyTimeToDisplayLatency; bool isDroppedFrame = false; + double fpsDisplay = 0; // CPU Metrics (app frames only) double msCPUBusy = 0; double msCPUWait = 0; double msCPUTime = 0; + double fpsApplication = 0; // GPU Metrics (app frames only) double msGPULatency = 0; diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index 55945a65..6d6886ce 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -1623,6 +1623,12 @@ TEST_CLASS(ComputeMetricsForPresentTests) 0.0001, L"First frame should have msBetweenPresents == 0."); + Assert::AreEqual( + 0.0, + firstMetrics[0].metrics.fpsPresent, + 0.0001, + L"First frame should have fpsPresent == 0 when msBetweenPrese"); + // Chain should now treat this as lastPresent / lastAppPresent Assert::IsTrue(chain.lastPresent.has_value()); if (!chain.lastPresent.has_value()) @@ -1658,6 +1664,13 @@ TEST_CLASS(ComputeMetricsForPresentTests) secondMetrics[0].metrics.msBetweenPresents, 0.0001, L"msBetweenPresents should equal the unsigned delta between lastPresent and current presentStartTime."); + + double expectedFps = (expectedDelta > 0.0) ? (1000.0 / expectedDelta) : 0.0; + Assert::AreEqual( + expectedFps, + secondMetrics[0].metrics.fpsPresent, + 0.0001, + L"fpsPresents should be derived from msBetweenPresents"); } TEST_METHOD(ComputeMetricsForPresent_NotDisplayed_BaseTimingAndCpuStart_AreCorrect) { @@ -2078,6 +2091,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) const auto& m = results[0].metrics; Assert::AreEqual(0.0, m.msBetweenDisplayChange, 0.0001); + Assert::AreEqual(0.0, m.fpsDisplay, 0.0001); } TEST_METHOD(SubsequentDisplayedFrame_UsesChainLastDisplayedScreenTime) @@ -2103,6 +2117,8 @@ TEST_CLASS(ComputeMetricsForPresentTests) double expected = qpc.DeltaUnsignedMilliSeconds(4'000'000, 5'500'000); Assert::AreEqual(expected, m.msBetweenDisplayChange, 0.0001); + double fpsExpected = (expected > 0.0) ? (1000.0 / expected) : 0.0; + Assert::AreEqual(fpsExpected, m.fpsDisplay, 0.0001); } TEST_METHOD(NotDisplayed_ReturnsZero) @@ -2122,6 +2138,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) const auto& m = results[0].metrics; Assert::AreEqual(0.0, m.msBetweenDisplayChange, 0.0001); + Assert::AreEqual(0.0, m.fpsDisplay, 0.0001); } TEST_METHOD(MultipleDisplays_EachComputesDeltaFromPrior) @@ -3492,6 +3509,53 @@ TEST_CLASS(ComputeMetricsForPresentTests) // msCPUWait = 0 ticks = 0 ms Assert::AreEqual(0.0, m.msCPUWait, 0.0001); } + + TEST_METHOD(CPUTime_AndFpsApplication_AreDerivedCorrectly) + { + // Verify msCPUTime = msCPUBusy + msCPUWait, and fpsApplication = 1000 / msCPUTime. + QpcConverter qpc(10'000'000, 0); + SwapChainCoreState chain{}; + + // Prior app frame sets cpuStart = 1'000'000. + FrameData priorFrame{}; + priorFrame.presentStartTime = 900'000; + priorFrame.timeInPresent = 100'000; + priorFrame.readyTime = 1'050'000; + priorFrame.finalState = PresentResult::Presented; + priorFrame.displayed.PushBack({ FrameType::Application, 1'100'000 }); + chain.lastAppPresent = priorFrame; + + // Current frame: presentStartTime=1'100'000 => msCPUBusy=10ms; timeInPresent=200'000 => msCPUWait=20ms. + FrameData frame{}; + frame.presentStartTime = 1'100'000; + frame.timeInPresent = 200'000; + frame.readyTime = 1'350'000; + frame.finalState = PresentResult::Presented; + frame.displayed.PushBack({ FrameType::Application, 1'400'000 }); + + // Next displayed frame required to process current frame's display. + FrameData next{}; + next.presentStartTime = 1'600'000; + next.timeInPresent = 50'000; + next.readyTime = 1'700'000; + next.finalState = PresentResult::Presented; + next.displayed.PushBack({ FrameType::Application, 1'800'000 }); + + auto results = ComputeMetricsForPresent(qpc, frame, &next, chain); + Assert::AreEqual(size_t(1), results.size()); + const auto& m = results[0].metrics; + + double expectedBusy = qpc.DeltaUnsignedMilliSeconds(1'000'000, 1'100'000); + double expectedWait = qpc.DurationMilliSeconds(200'000); + double expectedCpuTime = expectedBusy + expectedWait; + + Assert::AreEqual(expectedBusy, m.msCPUBusy, 0.0001); + Assert::AreEqual(expectedWait, m.msCPUWait, 0.0001); + Assert::AreEqual(expectedCpuTime, m.msCPUTime, 0.0001); + + double expectedFps = expectedCpuTime > 0.0 ? 1000.0 / expectedCpuTime : 0.0; + Assert::AreEqual(expectedFps, m.fpsApplication, 0.0001); + } }; // ============================================================================ From c176457742fabdd27f62e35e16d9c12c4c8ec1b7 Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 29 Jan 2026 14:33:05 +0900 Subject: [PATCH 164/205] test completeness of PM_METRIC to FrameMetrics mapping --- .../IpcMcIntegrationTests.cpp | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp index d127eaf9..8c0a3f65 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp @@ -7,10 +7,13 @@ #include "../PresentMonAPIWrapper/PresentMonAPIWrapper.h" #include "../Interprocess/source/SystemDeviceId.h" +#include "../CommonUtilities/Meta.h" +#include "../CommonUtilities/mc/FrameMetricsMemberMap.h" #include #include #include +#include #include using namespace Microsoft::VisualStudio::CppUnitTestFramework; @@ -19,6 +22,7 @@ using namespace std::chrono_literals; namespace IpcMcIntegrationTests { namespace ipc = pmon::ipc; + namespace util = pmon::util; class TestFixture : public CommonTestFixture { @@ -350,5 +354,61 @@ namespace IpcMcIntegrationTests Logger::WriteMessage(std::format("Application name: {}\n", appName).c_str()); Assert::IsTrue(appName == "PresentBench.exe", L"Unexpected application name"); } + + TEST_METHOD(UniversalNonStaticMetricsMapToFrameMetrics) + { + pmapi::Session session{ fixture_.GetCommonArgs().ctrlPipe }; + auto intro = session.GetIntrospectionRoot(); + Assert::IsTrue((bool)intro); + + std::vector metricsToCheck; + for (auto metricView : intro->GetMetrics()) { + if (metricView.GetType() == PM_METRIC_TYPE_STATIC) { + continue; + } + + bool hasUniversalDevice = false; + for (auto info : metricView.GetDeviceMetricInfo()) { + if (info.GetDevice().GetId() == ipc::kUniversalDeviceId) { + hasUniversalDevice = true; + break; + } + } + + if (hasUniversalDevice) { + metricsToCheck.push_back(metricView.GetId()); + } + } + + Logger::WriteMessage(std::format("Universal non-static metrics to map: {}\n", metricsToCheck.size()).c_str()); + Assert::IsTrue(!metricsToCheck.empty(), L"No universal non-static metrics found"); + + size_t failedMappings = 0; + for (auto metricId : metricsToCheck) { + const auto metricView = intro->FindMetric(metricId); + const auto symbol = metricView.Introspect().GetSymbol(); + const bool mapped = util::DispatchEnumValue( + metricId, + [&]() -> bool { + if constexpr (util::metrics::HasFrameMetricMember) { + constexpr auto memberPtr = util::metrics::FrameMetricMember::member; + using MemberInfo = util::MemberPointerInfo; + return std::is_same_v; + } + return false; + }, + false); + Logger::WriteMessage(std::format("Metric {}: {}\n", symbol, mapped ? "mapped" : "missing").c_str()); + if (!mapped) { + ++failedMappings; + } + } + + if (failedMappings > 0) { + Logger::WriteMessage(std::format("Unmapped universal non-static metrics: {}\n", failedMappings).c_str()); + } + + Assert::IsTrue(failedMappings == 0, L"FrameMetricsMemberMap missing universal non-static metrics"); + } }; } From 12589d418e2fb3c8a0d36b3d127ec62c5fa7e7a0 Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 29 Jan 2026 15:53:37 +0900 Subject: [PATCH 165/205] add missing metrics to mc struct map --- .../mc/FrameMetricsMemberMap.h | 47 ++++++++++--------- .../IpcMcIntegrationTests.cpp | 2 +- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/FrameMetricsMemberMap.h b/IntelPresentMon/CommonUtilities/mc/FrameMetricsMemberMap.h index c3e88087..32f173b1 100644 --- a/IntelPresentMon/CommonUtilities/mc/FrameMetricsMemberMap.h +++ b/IntelPresentMon/CommonUtilities/mc/FrameMetricsMemberMap.h @@ -7,41 +7,46 @@ namespace pmon::util::metrics { templatestruct FrameMetricMember{}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::allowsTearing;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::runtime;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::presentMode;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::presentFlags;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::syncInterval;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::swapChainAddress;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::presentStartQpc;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::presentStartMs;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::cpuStartQpc;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::cpuStartMs;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msBetweenPresents;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msInPresentApi;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msUntilRenderComplete;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msBetweenDisplayChange;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msUntilDisplayed;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msDisplayedTime;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msDisplayLatency;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msBetweenSimStarts;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msPcLatency;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msCPUTime;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::cpuStartQpc;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msCPUBusy;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msCPUWait;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msCPUTime;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msCPUTime;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::fpsDisplay;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::fpsPresent;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msGPULatency;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msGPUTime;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msGPUBusy;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msGPUWait;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::isDroppedFrame;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msDisplayedTime;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::syncInterval;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::presentFlags;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::presentMode;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::runtime;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::allowsTearing;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msDisplayLatency;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msAnimationError;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msAnimationTime;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msClickToPhotonLatency;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::fpsApplication;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::frameType;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msAllInputPhotonLatency;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msInstrumentedLatency;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msAnimationTime;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::presentStartMs;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::presentStartQpc;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msBetweenPresents;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msInPresentApi;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msBetweenDisplayChange;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msUntilDisplayed;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msUntilRenderComplete;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msBetweenSimStarts;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msPcLatency;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msBetweenDisplayChange;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msCPUTime;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msBetweenPresents;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msFlipDelay;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::frameType;}; template inline constexpr bool HasFrameMetricMember = requires{ FrameMetricMember::member; }; diff --git a/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp index 8c0a3f65..9a74cfff 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp @@ -398,7 +398,7 @@ namespace IpcMcIntegrationTests return false; }, false); - Logger::WriteMessage(std::format("Metric {}: {}\n", symbol, mapped ? "mapped" : "missing").c_str()); + Logger::WriteMessage(std::format("Metric {}: {}\n", symbol, mapped ? "ok" : "*FAIL !! MISSING").c_str()); if (!mapped) { ++failedMappings; } From 4a01f9cadd8ce0d4c24dc7d6935ce64d3cacb9f6 Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 29 Jan 2026 17:09:16 +0900 Subject: [PATCH 166/205] dynamic-only metrics still need frame data type to be non-void --- .../Interprocess/source/metadata/MetricList.h | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/metadata/MetricList.h b/IntelPresentMon/Interprocess/source/metadata/MetricList.h index 11bf921b..271f40bc 100644 --- a/IntelPresentMon/Interprocess/source/metadata/MetricList.h +++ b/IntelPresentMon/Interprocess/source/metadata/MetricList.h @@ -13,20 +13,20 @@ X_(PM_METRIC_CPU_VENDOR, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_DEVICE_VENDOR, PM_DEVICE_TYPE_SYSTEM, PM_STAT_NONE) \ X_(PM_METRIC_CPU_NAME, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_STRING, PM_DATA_TYPE_STRING, 0, PM_DEVICE_TYPE_SYSTEM, PM_STAT_NONE) \ X_(PM_METRIC_CPU_START_TIME, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ - X_(PM_METRIC_CPU_START_QPC, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_QPC, PM_DATA_TYPE_UINT64, PM_DATA_TYPE_UINT64, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ + X_(PM_METRIC_CPU_START_QPC, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_QPC, PM_DATA_TYPE_VOID, PM_DATA_TYPE_UINT64, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ X_(PM_METRIC_CPU_FRAME_TIME, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_CPU_BUSY, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_CPU_WAIT, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ - X_(PM_METRIC_DISPLAYED_FPS, PM_METRIC_TYPE_DYNAMIC, PM_UNIT_FPS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_VOID, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ - X_(PM_METRIC_APPLICATION_FPS, PM_METRIC_TYPE_DYNAMIC, PM_UNIT_FPS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_VOID, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ - X_(PM_METRIC_PRESENTED_FPS, PM_METRIC_TYPE_DYNAMIC, PM_UNIT_FPS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_VOID, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ + X_(PM_METRIC_DISPLAYED_FPS, PM_METRIC_TYPE_DYNAMIC, PM_UNIT_FPS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ + X_(PM_METRIC_APPLICATION_FPS, PM_METRIC_TYPE_DYNAMIC, PM_UNIT_FPS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ + X_(PM_METRIC_PRESENTED_FPS, PM_METRIC_TYPE_DYNAMIC, PM_UNIT_FPS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_GPU_TIME, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_GPU_BUSY, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_GPU_WAIT, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_DROPPED_FRAMES, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_BOOL, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ - X_(PM_METRIC_DISPLAYED_TIME, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ + X_(PM_METRIC_DISPLAYED_TIME, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_ANIMATION_ERROR, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ - X_(PM_METRIC_ANIMATION_TIME, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ + X_(PM_METRIC_ANIMATION_TIME, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ X_(PM_METRIC_SYNC_INTERVAL, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_VERTICAL_BLANKS, PM_DATA_TYPE_INT32, PM_DATA_TYPE_INT32, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_MID_POINT) \ X_(PM_METRIC_PRESENT_FLAGS, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_UINT32, PM_DATA_TYPE_UINT32, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_MID_POINT) \ X_(PM_METRIC_PRESENT_MODE, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_PRESENT_MODE, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_MID_POINT) \ @@ -34,7 +34,7 @@ X_(PM_METRIC_ALLOWS_TEARING, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_BOOL, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_FRAME_TYPE, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_FRAME_TYPE, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ X_(PM_METRIC_GPU_LATENCY, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ - X_(PM_METRIC_DISPLAY_LATENCY, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ + X_(PM_METRIC_DISPLAY_LATENCY, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_CLICK_TO_PHOTON_LATENCY, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NON_ZERO_AVG, PM_STAT_PERCENTILE_01, PM_STAT_PERCENTILE_05, PM_STAT_PERCENTILE_10, PM_STAT_MAX) \ X_(PM_METRIC_GPU_SUSTAINED_POWER_LIMIT, PM_METRIC_TYPE_STATIC, PM_UNIT_WATTS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, PM_STAT_NONE) \ X_(PM_METRIC_GPU_POWER, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_WATTS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ @@ -83,16 +83,16 @@ X_(PM_METRIC_GPU_FAN_SPEED_PERCENT, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_PERCENT, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ X_(PM_METRIC_GPU_CARD_POWER, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_WATTS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ X_(PM_METRIC_PRESENT_START_TIME, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ - X_(PM_METRIC_PRESENT_START_QPC, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_QPC, PM_DATA_TYPE_UINT64, PM_DATA_TYPE_UINT64, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ - X_(PM_METRIC_BETWEEN_PRESENTS, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ - X_(PM_METRIC_IN_PRESENT_API, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ + X_(PM_METRIC_PRESENT_START_QPC, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_QPC, PM_DATA_TYPE_VOID, PM_DATA_TYPE_UINT64, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ + X_(PM_METRIC_BETWEEN_PRESENTS, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ + X_(PM_METRIC_IN_PRESENT_API, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_BETWEEN_DISPLAY_CHANGE, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_UNTIL_DISPLAYED, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ - X_(PM_METRIC_RENDER_PRESENT_LATENCY, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ - X_(PM_METRIC_BETWEEN_SIMULATION_START, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ + X_(PM_METRIC_RENDER_PRESENT_LATENCY, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ + X_(PM_METRIC_BETWEEN_SIMULATION_START, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_PC_LATENCY, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_DISPLAYED_FRAME_TIME, PM_METRIC_TYPE_DYNAMIC, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_PRESENTED_FRAME_TIME, PM_METRIC_TYPE_DYNAMIC, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ - X_(PM_METRIC_BETWEEN_APP_START, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ - X_(PM_METRIC_FLIP_DELAY, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ + X_(PM_METRIC_BETWEEN_APP_START, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ + X_(PM_METRIC_FLIP_DELAY, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_COUNT, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_VOID, PM_ENUM_NULL_ENUM, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ From 5af4cd4c83cb1dddfd9c18bb3819557bafdb4ce5 Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 29 Jan 2026 20:40:50 +0900 Subject: [PATCH 167/205] increase data types handled in dynamic queries and fix metric defs --- .../Interprocess/source/TelemetryMap.h | 10 +++++++--- .../Interprocess/source/metadata/MetricList.h | 4 ++-- .../PresentMonMiddleware/DynamicMetric.h | 6 ++++++ .../PresentMonMiddleware/DynamicStat.cpp | 3 +++ .../PresentMonMiddleware/DynamicStat.h | 1 + .../PresentMonMiddleware/MetricBinding.cpp | 1 + .../PresentMonMiddleware.vcxproj | 10 +++++----- .../PresentMonMiddleware/QueryValidation.cpp | 9 +++++++-- IntelPresentMon/SampleClient/PacedPlayback.cpp | 15 +-------------- 9 files changed, 33 insertions(+), 26 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/TelemetryMap.h b/IntelPresentMon/Interprocess/source/TelemetryMap.h index 8017504a..668c9b38 100644 --- a/IntelPresentMon/Interprocess/source/TelemetryMap.h +++ b/IntelPresentMon/Interprocess/source/TelemetryMap.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "SharedMemoryTypes.h" #include "HistoryRing.h" #include "../../PresentMonAPI2/PresentMonAPI.h" @@ -13,7 +13,7 @@ namespace pmon::ipc template using HistoryRingVect = ShmVector>; using MapValueType = std::variant< - HistoryRingVect, HistoryRingVect, + HistoryRingVect, HistoryRingVect, HistoryRingVect, HistoryRingVect, HistoryRingVect>; using MapType = ShmMap; using AllocatorType = MapType::allocator_type; @@ -27,6 +27,9 @@ namespace pmon::ipc case PM_DATA_TYPE_DOUBLE: AddRing(id, size, count); break; + case PM_DATA_TYPE_UINT32: + AddRing(id, size, count); + break; case PM_DATA_TYPE_UINT64: AddRing(id, size, count); break; @@ -45,6 +48,7 @@ namespace pmon::ipc // extra guard of misuse at compile time static_assert( std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v, @@ -117,4 +121,4 @@ namespace pmon::ipc private: MapType ringMap_; }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/Interprocess/source/metadata/MetricList.h b/IntelPresentMon/Interprocess/source/metadata/MetricList.h index 271f40bc..1f12cb62 100644 --- a/IntelPresentMon/Interprocess/source/metadata/MetricList.h +++ b/IntelPresentMon/Interprocess/source/metadata/MetricList.h @@ -32,7 +32,7 @@ X_(PM_METRIC_PRESENT_MODE, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_PRESENT_MODE, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_MID_POINT) \ X_(PM_METRIC_PRESENT_RUNTIME, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_GRAPHICS_RUNTIME, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_MID_POINT) \ X_(PM_METRIC_ALLOWS_TEARING, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_BOOL, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ - X_(PM_METRIC_FRAME_TYPE, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_FRAME_TYPE, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ + X_(PM_METRIC_FRAME_TYPE, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_FRAME_TYPE, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_MID_POINT) \ X_(PM_METRIC_GPU_LATENCY, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_DISPLAY_LATENCY, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_CLICK_TO_PHOTON_LATENCY, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NON_ZERO_AVG, PM_STAT_PERCENTILE_01, PM_STAT_PERCENTILE_05, PM_STAT_PERCENTILE_10, PM_STAT_MAX) \ @@ -67,7 +67,7 @@ X_(PM_METRIC_GPU_MEM_VOLTAGE_LIMITED, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_BOOLEAN, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_BOOL, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ X_(PM_METRIC_GPU_MEM_UTILIZATION_LIMITED, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_BOOLEAN, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_BOOL, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, FULL_STATS) \ X_(PM_METRIC_CPU_UTILIZATION, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_PERCENT, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, FULL_STATS) \ - X_(PM_METRIC_CPU_POWER_LIMIT, PM_METRIC_TYPE_STATIC, PM_UNIT_WATTS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, PM_STAT_MID_POINT) /* maybe stat NONE? */ \ + X_(PM_METRIC_CPU_POWER_LIMIT, PM_METRIC_TYPE_STATIC, PM_UNIT_WATTS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, PM_STAT_NONE) \ X_(PM_METRIC_CPU_POWER, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_WATTS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, FULL_STATS) \ X_(PM_METRIC_CPU_TEMPERATURE, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_CELSIUS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, FULL_STATS) \ X_(PM_METRIC_CPU_FREQUENCY, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MEGAHERTZ, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_SYSTEM, FULL_STATS) \ diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h index c51b1eb1..f3cc1852 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h @@ -164,6 +164,12 @@ namespace pmon::mid else if constexpr (std::is_same_v) { return PM_DATA_TYPE_INT32; } + else if constexpr (std::is_same_v) { + return PM_DATA_TYPE_UINT32; + } + else if constexpr (std::is_same_v) { + return PM_DATA_TYPE_UINT64; + } else if constexpr (std::is_same_v) { return PM_DATA_TYPE_BOOL; } diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp index f3d21f86..e6750307 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp +++ b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp @@ -71,6 +71,9 @@ namespace pmon::mid case PM_DATA_TYPE_ENUM: *reinterpret_cast(pTarget) = value ? (int32_t)doubleVal : 0; break; + case PM_DATA_TYPE_UINT32: + *reinterpret_cast(pTarget) = value ? (uint32_t)uint64Val : 0; + break; case PM_DATA_TYPE_BOOL: *reinterpret_cast(pTarget) = value ? doubleVal != 0.0 : false; break; diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicStat.h b/IntelPresentMon/PresentMonMiddleware/DynamicStat.h index 55b383e5..c8bd126a 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicStat.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicStat.h @@ -31,6 +31,7 @@ namespace pmon::mid extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); + extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); } diff --git a/IntelPresentMon/PresentMonMiddleware/MetricBinding.cpp b/IntelPresentMon/PresentMonMiddleware/MetricBinding.cpp index 985ada65..c41fa9e8 100644 --- a/IntelPresentMon/PresentMonMiddleware/MetricBinding.cpp +++ b/IntelPresentMon/PresentMonMiddleware/MetricBinding.cpp @@ -16,6 +16,7 @@ namespace pmon::mid template inline constexpr bool IsTelemetryRingValue_ = std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v; diff --git a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj index 8f84047e..67fad3f6 100644 --- a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj +++ b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj @@ -27,14 +27,14 @@ - - /bigobj %(AdditionalOptions) - + - + + /bigobj /we4062 %(AdditionalOptions) + @@ -145,4 +145,4 @@ - + \ No newline at end of file diff --git a/IntelPresentMon/PresentMonMiddleware/QueryValidation.cpp b/IntelPresentMon/PresentMonMiddleware/QueryValidation.cpp index 77a9a81b..08a3b759 100644 --- a/IntelPresentMon/PresentMonMiddleware/QueryValidation.cpp +++ b/IntelPresentMon/PresentMonMiddleware/QueryValidation.cpp @@ -72,6 +72,7 @@ namespace case PM_DATA_TYPE_DOUBLE: case PM_DATA_TYPE_INT32: case PM_DATA_TYPE_ENUM: + case PM_DATA_TYPE_UINT32: case PM_DATA_TYPE_UINT64: case PM_DATA_TYPE_BOOL: return true; @@ -80,7 +81,7 @@ namespace } } - bool IsSupportedDynamicOutputType_(PM_DATA_TYPE outType, bool allowBool, bool allowUint64) + bool IsSupportedDynamicOutputType_(PM_DATA_TYPE outType, bool allowBool, bool allowUint32, bool allowUint64) { switch (outType) { case PM_DATA_TYPE_DOUBLE: @@ -89,6 +90,8 @@ namespace return true; case PM_DATA_TYPE_BOOL: return allowBool; + case PM_DATA_TYPE_UINT32: + return allowUint32; case PM_DATA_TYPE_UINT64: return allowUint64; default: @@ -106,8 +109,9 @@ namespace } const bool allowBool = inType == PM_DATA_TYPE_BOOL; + const bool allowUint32 = inType == PM_DATA_TYPE_UINT32; const bool allowUint64 = inType == PM_DATA_TYPE_UINT64; - if (!IsSupportedDynamicOutputType_(outType, allowBool, allowUint64)) { + if (!IsSupportedDynamicOutputType_(outType, allowBool, allowUint32, allowUint64)) { return "Unsupported dynamic stat output data type"; } return nullptr; @@ -138,6 +142,7 @@ namespace { using ValueType = typename ipc::intro::DataTypeToStaticType::type; return std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v; diff --git a/IntelPresentMon/SampleClient/PacedPlayback.cpp b/IntelPresentMon/SampleClient/PacedPlayback.cpp index 940030c3..b25e8f9a 100644 --- a/IntelPresentMon/SampleClient/PacedPlayback.cpp +++ b/IntelPresentMon/SampleClient/PacedPlayback.cpp @@ -88,20 +88,6 @@ std::vector BuildQueryElementSet(const pmapi::intro::Root& int { std::vector qels; for (const auto& m : intro.GetMetrics()) { - // there is no reliable way of distinguishing CPU telemetry metrics from PresentData-based metrics via introspection - // adding CPU device type is an idea, however that would require changing device id of the cpu metrics from 0 to - // whatever id is assigned to cpu (probably an upper range like 1024+) and this might break existing code that just - // hardcodes device id for the CPU metrics; for the time being use a hardcoded blacklist here - if (rn::contains(std::array{ - PM_METRIC_CPU_UTILIZATION, - PM_METRIC_CPU_POWER_LIMIT, - PM_METRIC_CPU_POWER, - PM_METRIC_CPU_TEMPERATURE, - PM_METRIC_CPU_FREQUENCY, - PM_METRIC_CPU_CORE_UTILITY, - }, m.GetId())) { - continue; - } // only allow dynamic metrics if (m.GetType() != PM_METRIC_TYPE_DYNAMIC && m.GetType() != PM_METRIC_TYPE_DYNAMIC_FRAME) { continue; @@ -121,6 +107,7 @@ std::vector BuildQueryElementSet(const pmapi::intro::Root& int } for (const auto& s : m.GetStatInfo()) { // skip displayed fps (max) as it is broken now + // TODO: verify this and look into the underlying issue if still present if (m.GetId() == PM_METRIC_DISPLAYED_FPS && s.GetStat() == PM_STAT_MAX) { continue; } From 95fad6046d9c69c8d458cc1f213cf88e3c212286 Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 30 Jan 2026 06:57:13 +0900 Subject: [PATCH 168/205] add session start qpc and internalize metric count w/ _ --- IntelPresentMon/Interprocess/source/DataStores.cpp | 2 ++ IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h | 2 +- IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp | 2 +- IntelPresentMon/Interprocess/source/metadata/MetricList.h | 3 ++- IntelPresentMon/PresentMonAPI2/PresentMonAPI.h | 3 ++- IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp | 2 +- IntelPresentMon/PresentMonMiddleware/DynamicMetric.h | 2 +- IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp | 2 +- IntelPresentMon/PresentMonMiddleware/QueryValidation.cpp | 2 +- IntelPresentMon/metrics.csv | 3 ++- 10 files changed, 14 insertions(+), 9 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/DataStores.cpp b/IntelPresentMon/Interprocess/source/DataStores.cpp index b4834f6f..bb0bf8ea 100644 --- a/IntelPresentMon/Interprocess/source/DataStores.cpp +++ b/IntelPresentMon/Interprocess/source/DataStores.cpp @@ -153,6 +153,8 @@ namespace pmon::ipc return statics.applicationName.c_str(); case PM_METRIC_PROCESS_ID: return bookkeeping.processId; + case PM_METRIC_SESSION_START_QPC: + return bookkeeping.startQpc; default: throw util::Except(PM_STATUS_QUERY_MALFORMED, "Static metric not handled by frame data store"); diff --git a/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h b/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h index 380d82f4..1062c9ab 100644 --- a/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h +++ b/IntelPresentMon/Interprocess/source/IntrospectionCapsLookup.h @@ -10,7 +10,7 @@ namespace pmon::ipc::intro // mapping of caps to metrics template struct IntrospectionCapsLookup { using Universal = std::true_type; }; // sentinel metric used for enumeration only (no availability) - template<> struct IntrospectionCapsLookup {}; + template<> struct IntrospectionCapsLookup {}; // GPU caps template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_power; }; template<> struct IntrospectionCapsLookup { static constexpr auto gpuCapBit = GpuTelemetryCapBits::gpu_voltage; }; diff --git a/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp b/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp index 47757a81..373abe98 100644 --- a/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp +++ b/IntelPresentMon/Interprocess/source/MetricCapabilitiesShim.cpp @@ -9,7 +9,7 @@ namespace pmon::ipc::intro { using MetricEnum = PM_METRIC; // Probe underlying values in [0, COUNT) - constexpr auto MaxMetricUnderlying = int(PM_METRIC_COUNT); + constexpr auto MaxMetricUnderlying = int(PM_METRIC_COUNT_); // xxxCapBits is std::bitset template diff --git a/IntelPresentMon/Interprocess/source/metadata/MetricList.h b/IntelPresentMon/Interprocess/source/metadata/MetricList.h index 1f12cb62..bce736da 100644 --- a/IntelPresentMon/Interprocess/source/metadata/MetricList.h +++ b/IntelPresentMon/Interprocess/source/metadata/MetricList.h @@ -95,4 +95,5 @@ X_(PM_METRIC_PRESENTED_FRAME_TIME, PM_METRIC_TYPE_DYNAMIC, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_BETWEEN_APP_START, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_FLIP_DELAY, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ - X_(PM_METRIC_COUNT, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_VOID, PM_ENUM_NULL_ENUM, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ + X_(PM_METRIC_SESSION_START_QPC, PM_METRIC_TYPE_STATIC, PM_UNIT_QPC, PM_DATA_TYPE_UINT64, PM_DATA_TYPE_UINT64, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ + X_(PM_METRIC_COUNT_, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_VOID, PM_ENUM_NULL_ENUM, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ diff --git a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h index e11e7dbf..2ad8da9d 100644 --- a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h +++ b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h @@ -138,7 +138,8 @@ extern "C" { PM_METRIC_PRESENTED_FRAME_TIME, PM_METRIC_FLIP_DELAY, PM_METRIC_PROCESS_ID, - PM_METRIC_COUNT, // sentry to mark end of metric list; not an actual query metric + PM_METRIC_SESSION_START_QPC, + PM_METRIC_COUNT_, // sentry to mark end of metric list; not an actual query metric }; enum PM_METRIC_TYPE diff --git a/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp index 9a74cfff..2394bff2 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/IpcMcIntegrationTests.cpp @@ -387,7 +387,7 @@ namespace IpcMcIntegrationTests for (auto metricId : metricsToCheck) { const auto metricView = intro->FindMetric(metricId); const auto symbol = metricView.Introspect().GetSymbol(); - const bool mapped = util::DispatchEnumValue( + const bool mapped = util::DispatchEnumValue( metricId, [&]() -> bool { if constexpr (util::metrics::HasFrameMetricMember) { diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h index f3cc1852..b1bbd2b9 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h @@ -198,7 +198,7 @@ namespace pmon::mid template std::unique_ptr> MakeDynamicMetric(const PM_QUERY_ELEMENT& qel) { - return util::DispatchEnumValue( + return util::DispatchEnumValue( qel.metric, [&]() -> std::unique_ptr> { if constexpr (util::metrics::HasFrameMetricMember) { diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index 84b2263f..f91d580c 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -111,7 +111,7 @@ PM_FRAME_QUERY::GatherCommand_ PM_FRAME_QUERY::MapQueryElementToFrameGatherComma .arrayIdx = q.arrayIndex, }; - const bool mapped = util::DispatchEnumValue( + const bool mapped = util::DispatchEnumValue( q.metric, [&]() -> bool { if constexpr (util::metrics::HasFrameMetricMember) { diff --git a/IntelPresentMon/PresentMonMiddleware/QueryValidation.cpp b/IntelPresentMon/PresentMonMiddleware/QueryValidation.cpp index 08a3b759..ce6472ad 100644 --- a/IntelPresentMon/PresentMonMiddleware/QueryValidation.cpp +++ b/IntelPresentMon/PresentMonMiddleware/QueryValidation.cpp @@ -127,7 +127,7 @@ namespace bool IsFrameMetricMapped_(PM_METRIC metric) { - return util::DispatchEnumValue( + return util::DispatchEnumValue( metric, [&]() -> bool { return util::metrics::HasFrameMetricMember; diff --git a/IntelPresentMon/metrics.csv b/IntelPresentMon/metrics.csv index 2bc6ac88..4c109e47 100644 --- a/IntelPresentMon/metrics.csv +++ b/IntelPresentMon/metrics.csv @@ -104,5 +104,6 @@ PM_METRIC_DISPLAYED_FRAME_TIME,1,FrameTime-Display,"The time between when the pr PM_METRIC_PRESENTED_FRAME_TIME,1,FrameTime-Presents,"The time between this Present call and the previous one, in milliseconds." PM_METRIC_BETWEEN_APP_START,,Ms Between App Start,"How long it took from the start of this frame until the CPU started working on the next frame, in milliseconds." PM_METRIC_FLIP_DELAY,,Ms Flip Delay,"Delay added to when the Present() was displayed." +PM_METRIC_SESSION_START_QPC,1,Session Start QPC,"QPC of the start of the ETW trace session, derived from the first received event timestamp." ,,, -PM_METRIC_COUNT,1,Metric Count,"Sentinel value for enumeration/reflection only. Not an actual metric for use in queries." +PM_METRIC_COUNT_,1,Metric Count,"Sentinel value for enumeration/reflection only. Not an actual metric for use in queries." From f42391ddcac4c63c8f8da6e607e9241a1eea74dd Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 30 Jan 2026 08:59:36 +0900 Subject: [PATCH 169/205] ability to inject window base timestamp --- IntelPresentMon/PresentMonAPI2/Internal.h | 1 - .../PresentMonMiddleware/ConcreteMiddleware.cpp | 7 ++++--- IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h | 3 ++- IntelPresentMon/PresentMonMiddleware/Middleware.h | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/IntelPresentMon/PresentMonAPI2/Internal.h b/IntelPresentMon/PresentMonAPI2/Internal.h index a230062a..e8c9f8f2 100644 --- a/IntelPresentMon/PresentMonAPI2/Internal.h +++ b/IntelPresentMon/PresentMonAPI2/Internal.h @@ -43,5 +43,4 @@ PRESENTMON_API2_EXPORT void pmSetupODSLogging_(PM_DIAGNOSTIC_LEVEL logLevel, PRESENTMON_API2_EXPORT PM_STATUS pmSetupFileLogging_(const char* filename, PM_DIAGNOSTIC_LEVEL logLevel, PM_DIAGNOSTIC_LEVEL stackTraceLevel, bool exceptionTrace); -// set middleware to log using OutputDebugString PRESENTMON_API2_EXPORT PM_STATUS pmStopPlayback_(PM_SESSION_HANDLE handle); diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index 6c632b6a..48ddc015 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -199,7 +199,8 @@ namespace pmon::mid return new PM_DYNAMIC_QUERY{ queryElements, windowSizeMs, metricOffsetMs, qpcPeriod, *pComms, *this }; } - void ConcreteMiddleware::PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t* numSwapChains) + void ConcreteMiddleware::PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, + uint8_t* pBlob, uint32_t* numSwapChains, std::optional nowTimestamp) { if (numSwapChains == nullptr) { throw Except(PM_STATUS_BAD_ARGUMENT, "numSwapChains pointer is null."); @@ -225,8 +226,8 @@ namespace pmon::mid pFrameSource->Update(); } - const uint64_t nowTimestamp = (uint64_t)util::GetCurrentTimestamp(); - *numSwapChains = pQuery->Poll(pBlob, *pComms, nowTimestamp, pFrameSource, processId, maxSwapChains); + const auto now = nowTimestamp.value_or((uint64_t)util::GetCurrentTimestamp()); + *numSwapChains = pQuery->Poll(pBlob, *pComms, now, pFrameSource, processId, maxSwapChains); } void ConcreteMiddleware::PollStaticQuery(const PM_QUERY_ELEMENT& element, uint32_t processId, uint8_t* pBlob) diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h index 066e86bc..5c0761b3 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h @@ -34,7 +34,8 @@ namespace pmon::mid PM_STATUS SetEtwFlushPeriod(std::optional periodMs) override; PM_DYNAMIC_QUERY* RegisterDynamicQuery(std::span queryElements, double windowSizeMs, double metricOffsetMs) override; void FreeDynamicQuery(const PM_DYNAMIC_QUERY* pQuery) override {} - void PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t* numSwapChains) override; + void PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, + uint32_t* numSwapChains, std::optional nowTimestamp = {}) override; void PollStaticQuery(const PM_QUERY_ELEMENT& element, uint32_t processId, uint8_t* pBlob) override; PM_FRAME_QUERY* RegisterFrameEventQuery(std::span queryElements, uint32_t& blobSize) override; void FreeFrameEventQuery(const PM_FRAME_QUERY* pQuery) override; diff --git a/IntelPresentMon/PresentMonMiddleware/Middleware.h b/IntelPresentMon/PresentMonMiddleware/Middleware.h index ea19a8f9..cf8ffb9b 100644 --- a/IntelPresentMon/PresentMonMiddleware/Middleware.h +++ b/IntelPresentMon/PresentMonMiddleware/Middleware.h @@ -20,7 +20,7 @@ namespace pmon::mid virtual PM_STATUS SetEtwFlushPeriod(std::optional periodMs) = 0; virtual PM_DYNAMIC_QUERY* RegisterDynamicQuery(std::span queryElements, double windowSizeMs, double metricOffsetMs) = 0; virtual void FreeDynamicQuery(const PM_DYNAMIC_QUERY* pQuery) = 0; - virtual void PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t* numSwapChains) = 0; + virtual void PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t* numSwapChains, std::optional nowTimestamp = {}) = 0; virtual void PollStaticQuery(const PM_QUERY_ELEMENT& element, uint32_t processId, uint8_t* pBlob) = 0; virtual PM_FRAME_QUERY* RegisterFrameEventQuery(std::span queryElements, uint32_t& blobSize) = 0; virtual void FreeFrameEventQuery(const PM_FRAME_QUERY* pQuery) = 0; From 5c8485dac192cb4eeee70c4a42ac518cca51c8c7 Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 30 Jan 2026 09:40:03 +0900 Subject: [PATCH 170/205] poll window control + timer upgrades --- .../CommonUtilities/IntervalWaiter.cpp | 24 ++++++++++++------ .../CommonUtilities/IntervalWaiter.h | 10 +++++++- .../CommonUtilities/PrecisionWaiter.cpp | 17 +++++++------ .../CommonUtilities/PrecisionWaiter.h | 6 ++--- IntelPresentMon/CommonUtilities/Qpc.cpp | 19 +++++++++++--- IntelPresentMon/CommonUtilities/Qpc.h | 4 ++- IntelPresentMon/PresentMonAPI2/Internal.h | 2 +- .../PresentMonAPI2/PresentMonAPI.cpp | 25 +++++++++++++++++++ .../PresentMonAPI2/PresentMonAPI.h | 2 ++ .../PresentMonAPI2Loader/Implementation.cpp | 7 ++++++ .../PresentMonAPIWrapper/DynamicQuery.cpp | 17 ++++++++++++- .../PresentMonAPIWrapper/DynamicQuery.h | 12 ++++++++- 12 files changed, 119 insertions(+), 26 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/IntervalWaiter.cpp b/IntelPresentMon/CommonUtilities/IntervalWaiter.cpp index 9d327530..d0815d36 100644 --- a/IntelPresentMon/CommonUtilities/IntervalWaiter.cpp +++ b/IntelPresentMon/CommonUtilities/IntervalWaiter.cpp @@ -3,6 +3,13 @@ namespace pmon::util { + IntervalWaiter::IntervalWaiter(double intervalSeconds, int64_t syncTimestamp, double waitBuffer) + : + intervalSeconds_{ intervalSeconds }, + waiter_{ waitBuffer }, + timer_{ syncTimestamp } + {} + IntervalWaiter::IntervalWaiter(double intervalSeconds, double waitBuffer) : intervalSeconds_{ intervalSeconds }, @@ -19,22 +26,25 @@ namespace pmon::util intervalSeconds_ = double(interval.count()) / 1'000'000'000.; } - void IntervalWaiter::Wait() + IntervalWaiter::WaitResult IntervalWaiter::Wait() { - const auto waitTimeSeconds = [=, this] { + WaitResult res{}; + const auto waitTimeSeconds = [=, this, &res] { const auto t = timer_.Peek(); - auto targetCandidate = lastTargetTime_ + intervalSeconds_; + res.targetSec = lastTargetTime_ + intervalSeconds_; // if we are on-time - if (t <= targetCandidate) { - lastTargetTime_ = targetCandidate; - return targetCandidate - t; + if (t <= res.targetSec) { + lastTargetTime_ = res.targetSec; + return res.targetSec - t; } // if we are late, reset target to NOW and do not wait lastTargetTime_ = t; + res.errorSec = res.targetSec - t; return 0.; }(); if (waitTimeSeconds > 0.) { - waiter_.Wait(waitTimeSeconds); + res.errorSec = waiter_.Wait(waitTimeSeconds); } + return res; } } diff --git a/IntelPresentMon/CommonUtilities/IntervalWaiter.h b/IntelPresentMon/CommonUtilities/IntervalWaiter.h index d202ed40..33301ce5 100644 --- a/IntelPresentMon/CommonUtilities/IntervalWaiter.h +++ b/IntelPresentMon/CommonUtilities/IntervalWaiter.h @@ -10,6 +10,14 @@ namespace pmon::util class IntervalWaiter { public: + // types + struct WaitResult + { + double targetSec; + double errorSec; + }; + // functions + IntervalWaiter(double intervalSeconds, int64_t syncTimestamp, double waitBuffer = PrecisionWaiter::standardWaitBuffer); IntervalWaiter(double intervalSeconds, double waitBuffer = PrecisionWaiter::standardWaitBuffer); IntervalWaiter(const IntervalWaiter&) = delete; IntervalWaiter & operator=(const IntervalWaiter&) = delete; @@ -18,7 +26,7 @@ namespace pmon::util ~IntervalWaiter() = default; void SetInterval(double intervalSeconds); void SetInterval(std::chrono::nanoseconds interval); - void Wait(); + WaitResult Wait(); private: double intervalSeconds_; double lastTargetTime_ = 0.; diff --git a/IntelPresentMon/CommonUtilities/PrecisionWaiter.cpp b/IntelPresentMon/CommonUtilities/PrecisionWaiter.cpp index 0df192f1..341ebe9b 100644 --- a/IntelPresentMon/CommonUtilities/PrecisionWaiter.cpp +++ b/IntelPresentMon/CommonUtilities/PrecisionWaiter.cpp @@ -23,15 +23,15 @@ namespace pmon::util pmlog_error(ReportException("Failed creating timer")); } } - void PrecisionWaiter::Wait(double seconds, bool alertable) noexcept + double PrecisionWaiter::Wait(double seconds, bool alertable) noexcept { - WaitWithBuffer(seconds, defaultWaitBuffer_, alertable); + return WaitWithBuffer(seconds, defaultWaitBuffer_, alertable); } - void PrecisionWaiter::WaitUnbuffered(double seconds, bool alertable) noexcept + double PrecisionWaiter::WaitUnbuffered(double seconds, bool alertable) noexcept { - WaitWithBuffer(seconds, 0., alertable); + return WaitWithBuffer(seconds, 0., alertable); } - void PrecisionWaiter::WaitWithBuffer(double seconds, double buffer, bool alertable) noexcept + double PrecisionWaiter::WaitWithBuffer(double seconds, double buffer, bool alertable) noexcept { try { if (waitableTimer_) { @@ -61,10 +61,10 @@ namespace pmon::util } // spin wait for the remainder buffer time if (buffer > 0.) { - qpcTimer_.SpinWaitUntil(seconds); + return qpcTimer_.SpinWaitUntil(seconds); } // return so we don't do the fallback Sleep() wait - return; + return 0.; } } catch (...) { @@ -75,6 +75,7 @@ namespace pmon::util // fall through to the fallback } // fallback if we fail high performance timing - Sleep(DWORD(seconds * 1000.f)); + Sleep(DWORD(seconds * 1000.)); + return 0.; } } \ No newline at end of file diff --git a/IntelPresentMon/CommonUtilities/PrecisionWaiter.h b/IntelPresentMon/CommonUtilities/PrecisionWaiter.h index 3850d8fc..3d46c820 100644 --- a/IntelPresentMon/CommonUtilities/PrecisionWaiter.h +++ b/IntelPresentMon/CommonUtilities/PrecisionWaiter.h @@ -18,9 +18,9 @@ namespace pmon::util PrecisionWaiter(PrecisionWaiter&&) = delete; PrecisionWaiter& operator=(PrecisionWaiter&&) = delete; ~PrecisionWaiter() = default; - void Wait(double seconds, bool alertable = false) noexcept; - void WaitUnbuffered(double seconds, bool alertable = false) noexcept; - void WaitWithBuffer(double seconds, double buffer, bool alertable = false) noexcept; + double Wait(double seconds, bool alertable = false) noexcept; + double WaitUnbuffered(double seconds, bool alertable = false) noexcept; + double WaitWithBuffer(double seconds, double buffer, bool alertable = false) noexcept; // todo: add function to set wait and receive handle, make compatible with event wait functions private: // amount of time we should wake up early by to do hyper-accurate spin wait diff --git a/IntelPresentMon/CommonUtilities/Qpc.cpp b/IntelPresentMon/CommonUtilities/Qpc.cpp index b7e6832a..6d7a7dd0 100644 --- a/IntelPresentMon/CommonUtilities/Qpc.cpp +++ b/IntelPresentMon/CommonUtilities/Qpc.cpp @@ -63,9 +63,16 @@ namespace pmon::util : -TimestampDeltaToMilliSeconds(start - end, qpcFrequency); } + QpcTimer::QpcTimer(int64_t seededStartTimestamp) + : + performanceCounterPeriod_{ GetTimestampPeriodSeconds() }, + startTimestamp_{ seededStartTimestamp } + {} + QpcTimer::QpcTimer() noexcept + : + performanceCounterPeriod_{ GetTimestampPeriodSeconds() } { - performanceCounterPeriod_ = GetTimestampPeriodSeconds(); Mark(); } double QpcTimer::Mark() noexcept @@ -85,11 +92,17 @@ namespace pmon::util { return startTimestamp_; } - void QpcTimer::SpinWaitUntil(double seconds) const noexcept + double QpcTimer::GetPerformanceCounterPeriod() const noexcept + { + return performanceCounterPeriod_; + } + double QpcTimer::SpinWaitUntil(double seconds) const noexcept { - while (Peek() < seconds) { + double t = Peek(); + for (; t < seconds; t = Peek()) { std::this_thread::yield(); } + return t - seconds; } // Duration in ticks -> ms diff --git a/IntelPresentMon/CommonUtilities/Qpc.h b/IntelPresentMon/CommonUtilities/Qpc.h index 0f91a951..3bdf25d6 100644 --- a/IntelPresentMon/CommonUtilities/Qpc.h +++ b/IntelPresentMon/CommonUtilities/Qpc.h @@ -18,11 +18,13 @@ namespace pmon::util class QpcTimer { public: + QpcTimer(int64_t seededStartTimestamp); QpcTimer() noexcept; double Mark() noexcept; double Peek() const noexcept; int64_t GetStartTimestamp() const noexcept; - void SpinWaitUntil(double seconds) const noexcept; + double GetPerformanceCounterPeriod() const noexcept; + double SpinWaitUntil(double seconds) const noexcept; private: double performanceCounterPeriod_; int64_t startTimestamp_ = 0; diff --git a/IntelPresentMon/PresentMonAPI2/Internal.h b/IntelPresentMon/PresentMonAPI2/Internal.h index e8c9f8f2..7bcc6abd 100644 --- a/IntelPresentMon/PresentMonAPI2/Internal.h +++ b/IntelPresentMon/PresentMonAPI2/Internal.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "PresentMonAPI.h" #include "PresentMonDiagnostics.h" #include "../CommonUtilities/log/Log.h" diff --git a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp index 2f9f60f6..7690eb40 100644 --- a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp +++ b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp @@ -338,6 +338,31 @@ PRESENTMON_API2_EXPORT PM_STATUS pmPollDynamicQuery(PM_DYNAMIC_QUERY_HANDLE hand } } +PRESENTMON_API2_EXPORT PM_STATUS pmPollDynamicQueryWithTimestamp(PM_DYNAMIC_QUERY_HANDLE handle, uint32_t processId, uint8_t* pBlob, uint32_t* numSwapChains, uint64_t nowTimestamp) +{ + try { + if (!pBlob) { + pmlog_error("null blob ptr").diag(); + return PM_STATUS_BAD_ARGUMENT; + } + if (!numSwapChains) { + pmlog_error("null swap chain inoutptr").diag(); + return PM_STATUS_BAD_ARGUMENT; + } + if (!*numSwapChains) { + pmlog_error("swap chain in count is zero").diag(); + return PM_STATUS_BAD_ARGUMENT; + } + LookupMiddleware_(handle).PollDynamicQuery(handle, processId, pBlob, numSwapChains, nowTimestamp); + return PM_STATUS_SUCCESS; + } + catch (...) { + const auto code = util::GeneratePmStatus(); + pmlog_error(util::ReportException()).code(code); + return code; + } +} + PRESENTMON_API2_EXPORT PM_STATUS pmPollStaticQuery(PM_SESSION_HANDLE sessionHandle, const PM_QUERY_ELEMENT* pElement, uint32_t processId, uint8_t* pBlob) { try { diff --git a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h index 2ad8da9d..d02f6112 100644 --- a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h +++ b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h @@ -425,6 +425,8 @@ extern "C" { PRESENTMON_API2_EXPORT PM_STATUS pmFreeDynamicQuery(PM_DYNAMIC_QUERY_HANDLE handle); // poll a dynamic query, writing the query poll results into the specified memory blob (byte buffer) PRESENTMON_API2_EXPORT PM_STATUS pmPollDynamicQuery(PM_DYNAMIC_QUERY_HANDLE handle, uint32_t processId, uint8_t* pBlob, uint32_t* numSwapChains); + // poll a dynamic query, writing the query poll results into the specified memory blob (byte buffer) using the provided now timestamp + PRESENTMON_API2_EXPORT PM_STATUS pmPollDynamicQueryWithTimestamp(PM_DYNAMIC_QUERY_HANDLE handle, uint32_t processId, uint8_t* pBlob, uint32_t* numSwapChains, uint64_t nowTimestamp); // query a static metric immediately, writing the result into the specified memory blob (byte buffer) PRESENTMON_API2_EXPORT PM_STATUS pmPollStaticQuery(PM_SESSION_HANDLE sessionHandle, const PM_QUERY_ELEMENT* pElement, uint32_t processId, uint8_t* pBlob); // register a frame query used for consuming desired metrics from a queue of frame events diff --git a/IntelPresentMon/PresentMonAPI2Loader/Implementation.cpp b/IntelPresentMon/PresentMonAPI2Loader/Implementation.cpp index 48f4d3c6..8825eb33 100644 --- a/IntelPresentMon/PresentMonAPI2Loader/Implementation.cpp +++ b/IntelPresentMon/PresentMonAPI2Loader/Implementation.cpp @@ -36,6 +36,7 @@ PM_STATUS(*pFunc_pmSetEtwFlushPeriod_)(PM_SESSION_HANDLE, uint32_t) = nullptr; PM_STATUS(*pFunc_pmRegisterDynamicQuery_)(PM_SESSION_HANDLE, PM_DYNAMIC_QUERY_HANDLE*, PM_QUERY_ELEMENT*, uint64_t, double, double) = nullptr; PM_STATUS(*pFunc_pmFreeDynamicQuery_)(PM_DYNAMIC_QUERY_HANDLE) = nullptr; PM_STATUS(*pFunc_pmPollDynamicQuery_)(PM_DYNAMIC_QUERY_HANDLE, uint32_t, uint8_t*, uint32_t*) = nullptr; +PM_STATUS(*pFunc_pmPollDynamicQueryWithTimestamp_)(PM_DYNAMIC_QUERY_HANDLE, uint32_t, uint8_t*, uint32_t*, uint64_t) = nullptr; PM_STATUS(*pFunc_pmPollStaticQuery_)(PM_SESSION_HANDLE, const PM_QUERY_ELEMENT*, uint32_t, uint8_t*) = nullptr; PM_STATUS(*pFunc_pmRegisterFrameQuery_)(PM_SESSION_HANDLE, PM_FRAME_QUERY_HANDLE*, PM_QUERY_ELEMENT*, uint64_t, uint32_t*) = nullptr; PM_STATUS(*pFunc_pmConsumeFrames_)(PM_FRAME_QUERY_HANDLE, uint32_t, uint8_t*, uint32_t*) = nullptr; @@ -166,6 +167,7 @@ PRESENTMON_API2_EXPORT PM_STATUS LoadLibrary_(bool versionOnly = false) RESOLVE(pmRegisterDynamicQuery); RESOLVE(pmFreeDynamicQuery); RESOLVE(pmPollDynamicQuery); + RESOLVE(pmPollDynamicQueryWithTimestamp); RESOLVE(pmPollStaticQuery); RESOLVE(pmRegisterFrameQuery); RESOLVE(pmConsumeFrames); @@ -275,6 +277,11 @@ PRESENTMON_API2_EXPORT PM_STATUS pmPollDynamicQuery(PM_DYNAMIC_QUERY_HANDLE hand LoadEndpointsIfEmpty_(); return pFunc_pmPollDynamicQuery_(handle, processId, pBlob, numSwapChains); } +PRESENTMON_API2_EXPORT PM_STATUS pmPollDynamicQueryWithTimestamp(PM_DYNAMIC_QUERY_HANDLE handle, uint32_t processId, uint8_t* pBlob, uint32_t* numSwapChains, uint64_t nowTimestamp) +{ + LoadEndpointsIfEmpty_(); + return pFunc_pmPollDynamicQueryWithTimestamp_(handle, processId, pBlob, numSwapChains, nowTimestamp); +} PRESENTMON_API2_EXPORT PM_STATUS pmPollStaticQuery(PM_SESSION_HANDLE sessionHandle, const PM_QUERY_ELEMENT* pElement, uint32_t processId, uint8_t* pBlob) { LoadEndpointsIfEmpty_(); diff --git a/IntelPresentMon/PresentMonAPIWrapper/DynamicQuery.cpp b/IntelPresentMon/PresentMonAPIWrapper/DynamicQuery.cpp index 13558c5a..51c0030d 100644 --- a/IntelPresentMon/PresentMonAPIWrapper/DynamicQuery.cpp +++ b/IntelPresentMon/PresentMonAPIWrapper/DynamicQuery.cpp @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "DynamicQuery.h" #include #include @@ -38,6 +38,14 @@ namespace pmapi } } + void DynamicQuery::PollWithTimestamp(const ProcessTracker& tracker, uint8_t* pBlob, uint32_t& numSwapChains, uint64_t nowTimestamp) const + { + if (auto sta = pmPollDynamicQueryWithTimestamp(hQuery_, tracker.GetPid(), pBlob, &numSwapChains, nowTimestamp); + sta != PM_STATUS_SUCCESS) { + throw ApiErrorException{ sta, "dynamic poll with timestamp call failed" }; + } + } + void DynamicQuery::Poll(const ProcessTracker& tracker, BlobContainer& blobs) const { assert(!Empty()); @@ -45,6 +53,13 @@ namespace pmapi Poll(tracker, blobs.GetFirst(), blobs.AcquireNumBlobsInRef_()); } + void DynamicQuery::PollWithTimestamp(const ProcessTracker& tracker, BlobContainer& blobs, uint64_t nowTimestamp) const + { + assert(!Empty()); + assert(blobs.CheckHandle(hQuery_)); + PollWithTimestamp(tracker, blobs.GetFirst(), blobs.AcquireNumBlobsInRef_(), nowTimestamp); + } + BlobContainer DynamicQuery::MakeBlobContainer(uint32_t nBlobs) const { assert(!Empty()); diff --git a/IntelPresentMon/PresentMonAPIWrapper/DynamicQuery.h b/IntelPresentMon/PresentMonAPIWrapper/DynamicQuery.h index 6224c1df..1107af2d 100644 --- a/IntelPresentMon/PresentMonAPIWrapper/DynamicQuery.h +++ b/IntelPresentMon/PresentMonAPIWrapper/DynamicQuery.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include #include "BlobContainer.h" #include "ProcessTracker.h" @@ -33,6 +33,16 @@ namespace pmapi // numSwapChains: input indicates to API how many blobs available, output indicates how many were written // if the target process has multiple swap chains, will poll data for as many swaps as there are blobs available void Poll(const ProcessTracker& tracker, uint8_t* pBlob, uint32_t& numSwapChains) const; + // poll the specified process using this query, using an explicit timestamp for the "now" time + // polling processes frame event data and generates metrics for the time point at which this query was polled + // numSwapChains: input indicates to API how many blobs available, output indicates how many were written + // if the target process has multiple swap chains, will poll data for as many swaps as there are blobs available + void PollWithTimestamp(const ProcessTracker& tracker, uint8_t* pBlob, uint32_t& numSwapChains, uint64_t nowTimestamp) const; + // poll the specified process using this query, using an explicit timestamp for the "now" time + // polling processes frame event data and generates metrics for the time point at which this query was polled + // makes use of a blob container to manage the output data + // if the target process has multiple swap chains, will poll data for as many swaps has the container has room for + void PollWithTimestamp(const ProcessTracker& tracker, BlobContainer& blobs, uint64_t nowTimestamp) const; // create a blob container sized suited for this query // nBlobs parameter will control how many swaps can be polled maximum using the container BlobContainer MakeBlobContainer(uint32_t nBlobs) const; From ffc2308c521d9c16aabba238cfa0bef6f36c5f83 Mon Sep 17 00:00:00 2001 From: Chili Date: Tue, 3 Feb 2026 16:56:50 +0900 Subject: [PATCH 171/205] use lerp for percentile stats --- .../PresentMonMiddleware/DynamicStat.cpp | 53 ++++++++++++++++--- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp index e6750307..18edb2dd 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp +++ b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp @@ -171,20 +171,59 @@ namespace pmon::mid bool NeedsSortedWindow() const override { return true; } void InputSortedSamples(std::span sortedSamples) override { - // TODO: review interaction of optional values with percentile sorted buffer + // Methodology / steps: + // + // 0) Find the first sample that "has value" (for std::optional and similar), + // assuming empties/invalids sort before valids in the already-sorted buffer. + // + // 1) Map p to a fractional index h in [0, N-1] using: + // h = p * (N - 1) + // This is the "linear interpolation of order statistics" mapping that + // is most intuitive for continuous metrics: + // - p = 0 => h = 0 => returns x[0] (min) + // - p = 1 => h = N-1 => returns x[N-1] (max) + // - otherwise interpolates smoothly between neighbors + // + // 2) Split h into: + // i = floor(h) (base index) + // g = h - i (fraction in [0,1)) + // + // 3) Retrieve neighbours: i and i+1 (or just i 2x if at end) + // + // 4) Perform lerp: + // q = x[i] + g * (x[i+1] - x[i]) + // (note that for p=1, g becomes 0.) + + // Step 0: locate the first valid value (ignore empties/invalids at the front). size_t firstValid = 0; - while (firstValid < sortedSamples.size() && !SampleAdapter_::HasValue(sortedSamples[firstValid])) { + while (firstValid < sortedSamples.size() && + !SampleAdapter_::HasValue(sortedSamples[firstValid])) { ++firstValid; } const size_t validCount = sortedSamples.size() - firstValid; if (validCount == 0) { - return; + return; // no valid samples => leave value_ unchanged (or set to NaN if desired) } - const size_t last = validCount - 1; - const double position = percentile_ * (double)last; - const size_t index = (size_t)(position + 0.5); - value_ = SampleAdapter_::ToDouble(sortedSamples[firstValid + index]); + // Step 1: p-to-index mapping (fractional index over [0, N-1]). + const double h = percentile_ * double(validCount - 1); + + // Step 2: split into integer index + fractional part. + // Since h is in [0, N-1] and non-negative, truncation is equivalent to floor. + const size_t i = size_t(h); + const double g = h - double(i); + + // Step 3: fetch neighbors + // i is nearest index position less than or equal to target position + // so interpolation always requires 2nd index i1 to be after i + // (but if at the end of container, use i for both sides of interpolation) + const size_t i1 = (i + 1 < validCount) ? (i + 1) : i; + // retrieve both samples + const double x0 = SampleAdapter_::ToDouble(sortedSamples[firstValid + i]); + const double x1 = SampleAdapter_::ToDouble(sortedSamples[firstValid + i1]); + + // Step 4: perform linear interpolation + value_ = x0 + g * (x1 - x0); } void GatherToBlob(uint8_t* pBase) const override { From 51c9b0725b85752e3e47c643360cbf0835550fb9 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 4 Feb 2026 08:50:45 +0900 Subject: [PATCH 172/205] fix fps to reciprocate after (calculate in middleware) --- .../mc/FrameMetricsMemberMap.h | 24 +++- .../CommonUtilities/mc/MetricsCalculator.cpp | 9 -- .../CommonUtilities/mc/MetricsTypes.h | 3 - .../PresentMonMiddleware/DynamicMetric.h | 14 ++- .../PresentMonMiddleware/DynamicStat.cpp | 112 ++++++++++++------ .../PresentMonMiddleware/DynamicStat.h | 14 ++- IntelPresentMon/UnitTests/MetricsCore.cpp | 23 +--- 7 files changed, 113 insertions(+), 86 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/FrameMetricsMemberMap.h b/IntelPresentMon/CommonUtilities/mc/FrameMetricsMemberMap.h index 32f173b1..a6c37b12 100644 --- a/IntelPresentMon/CommonUtilities/mc/FrameMetricsMemberMap.h +++ b/IntelPresentMon/CommonUtilities/mc/FrameMetricsMemberMap.h @@ -1,6 +1,7 @@ // Copyright (C) 2026 Intel Corporation // SPDX-License-Identifier: MIT #pragma once +#include #include "MetricsTypes.h" #include "../PresentMonAPI2/PresentMonAPI.h" @@ -13,8 +14,10 @@ namespace pmon::util::metrics template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::cpuStartQpc;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msCPUBusy;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msCPUWait;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::fpsDisplay;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::fpsPresent;}; + template<>struct FrameMetricMember {static constexpr auto member = &FrameMetrics::msBetweenDisplayChange; + static constexpr auto reciprocationFactor = 1000.;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msBetweenPresents; + static constexpr auto reciprocationFactor = 1000.;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msGPULatency;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msGPUTime;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msGPUBusy;}; @@ -29,7 +32,8 @@ namespace pmon::util::metrics template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msDisplayLatency;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msAnimationError;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msClickToPhotonLatency;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::fpsApplication;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msCPUTime; + static constexpr auto reciprocationFactor = 1000.;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::frameType;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msAllInputPhotonLatency;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msInstrumentedLatency;}; @@ -50,4 +54,18 @@ namespace pmon::util::metrics template inline constexpr bool HasFrameMetricMember = requires{ FrameMetricMember::member; }; + + template + inline constexpr bool HasReciprocationFactor = requires{ FrameMetricMember::reciprocationFactor; }; + + template + constexpr std::optional GetReciprocationFactor() + { + if constexpr (HasReciprocationFactor) { + return FrameMetricMember::reciprocationFactor; + } + else { + return std::nullopt; + } + } } diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index 1d04b765..b340e7ae 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -85,11 +85,6 @@ namespace pmon::util::metrics : 0.0; } - double ComputeFpsFromMs(double ms) - { - return ms > 0.0 ? 1000.0 / ms : 0.0; - } - } // 2) Public entry points @@ -314,10 +309,6 @@ namespace pmon::util::metrics metrics.cpuStartQpc = CalculateCPUStart(chain, present); metrics.cpuStartMs = ComputeCPUStartTimeMs(qpc, metrics.cpuStartQpc); - metrics.fpsPresent = ComputeFpsFromMs(metrics.msBetweenPresents); - metrics.fpsDisplay = ComputeFpsFromMs(metrics.msBetweenDisplayChange); - metrics.fpsApplication = ComputeFpsFromMs(metrics.msCPUTime); - return result; } diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h index cbbb87c4..946bad29 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h @@ -112,7 +112,6 @@ namespace pmon::util::metrics { double msGpuDuration = 0; double msVideoDuration = 0; double msSinceInput = 0; - double fpsPresent = 0; // Display Metrics (displayed frames only) double msDisplayLatency = 0; @@ -122,13 +121,11 @@ namespace pmon::util::metrics { uint64_t screenTimeQpc = 0; std::optional msReadyTimeToDisplayLatency; bool isDroppedFrame = false; - double fpsDisplay = 0; // CPU Metrics (app frames only) double msCPUBusy = 0; double msCPUWait = 0; double msCPUTime = 0; - double fpsApplication = 0; // GPU Metrics (app frames only) double msGPULatency = 0; diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h index b1bbd2b9..4d4655c1 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -51,9 +52,10 @@ namespace pmon::mid class DynamicMetricBinding : public DynamicMetric { public: - DynamicMetricBinding(PM_METRIC metric) + DynamicMetricBinding(PM_METRIC metric, std::optional reciprocationFactor) : - metric_{ metric } + metric_{ metric }, + reciprocationFactor_{ reciprocationFactor } { } @@ -125,7 +127,7 @@ namespace pmon::mid qel.dataSize = (uint32_t)ipc::intro::GetDataTypeSize(outType); // adjust offset written in qel when padding is needed for type alignment qel.dataOffset = util::PadToAlignment(qel.dataOffset, qel.dataSize); - auto statPtr = MakeDynamicStat(qel.stat, inType, outType, qel.dataOffset); + auto statPtr = MakeDynamicStat(qel.stat, inType, outType, qel.dataOffset, reciprocationFactor_); auto* rawPtr = statPtr.get(); statPtrs_.push_back(std::move(statPtr)); @@ -187,6 +189,7 @@ namespace pmon::mid } PM_METRIC metric_; + std::optional reciprocationFactor_; mutable boost::container::vector samples_; std::vector>> statPtrs_; std::vector*> needsUpdatePtrs_; @@ -206,7 +209,8 @@ namespace pmon::mid using MemberInfo = util::MemberPointerInfo; if constexpr (std::is_same_v) { using MemberType = typename MemberInfo::MemberType; - return std::make_unique>(Metric); + auto reciprocationFactor = util::metrics::GetReciprocationFactor(); + return std::make_unique>(Metric, reciprocationFactor); } } if constexpr (requires { &S::value; }) { @@ -214,7 +218,7 @@ namespace pmon::mid constexpr auto memberPtr = &S::value; using MemberInfo = util::MemberPointerInfo; using MemberType = typename MemberInfo::MemberType; - return std::make_unique>(Metric); + return std::make_unique>(Metric, std::nullopt); } pmlog_error("Cannot make dynamic metric for").pmwatch((int)Metric); return {}; diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp index 18edb2dd..36bd9bb5 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp +++ b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp @@ -107,22 +107,51 @@ namespace pmon::mid throw util::Except(PM_STATUS_QUERY_MALFORMED, "DynamicStat::InputSortedSamples unsupported for this stat"); } protected: - DynamicStatBase_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes) + // functions + DynamicStatBase_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, + std::optional reciprocationFactor) : inType_{ inType }, outType_{ outType }, - offsetBytes_{ offsetBytes } + offsetBytes_{ offsetBytes }, + reciprocationFactor_{ reciprocationFactor } {} + template + void WriteValue_(uint8_t* pBase, const std::optional& value) const + { + // if not recip we can forward to Write function (it handles empty opt etc.) + if (!reciprocationFactor_) { + WriteOptionalValueToBlob_(pBase, offsetBytes_, outType_, value); + return; + } + // if recip but no value, write nullopt (cannot reciprocate a nullopt) + if (!value) { + WriteOptionalValueToBlob_(pBase, offsetBytes_, outType_, std::optional{}); + return; + } + const double rawValue = SampleAdapter_::ToDouble(*value); + // if value is present but zero, cannot recip zero so write nullopt + if (rawValue == 0.0) { + WriteOptionalValueToBlob_(pBase, offsetBytes_, outType_, std::optional{}); + return; + } + // otherwise, perform reciprocation and then write + const std::optional adjusted = *reciprocationFactor_ / rawValue; + WriteOptionalValueToBlob_(pBase, offsetBytes_, outType_, adjusted); + } + // data PM_DATA_TYPE inType_ = PM_DATA_TYPE_DOUBLE; PM_DATA_TYPE outType_ = PM_DATA_TYPE_DOUBLE; size_t offsetBytes_ = 0; + std::optional reciprocationFactor_; }; template class DynamicStatAverage_ : public DynamicStatBase_ { public: - DynamicStatAverage_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, bool skipZero) - : DynamicStatBase_{ inType, outType, offsetBytes }, + DynamicStatAverage_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, + std::optional reciprocationFactor, bool skipZero) + : DynamicStatBase_{ inType, outType, offsetBytes, reciprocationFactor }, skipZero_{ skipZero } { } @@ -146,7 +175,7 @@ namespace pmon::mid if (count_ > 0) { avg = sum_ / (double)count_; } - WriteOptionalValueToBlob_(pBase, this->offsetBytes_, this->outType_, avg); + this->WriteValue_(pBase, avg); // reset for the next poll sum_ = 0; count_ = 0; @@ -161,8 +190,9 @@ namespace pmon::mid class DynamicStatPercentile_ : public DynamicStatBase_ { public: - DynamicStatPercentile_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, double percentile) - : DynamicStatBase_{ inType, outType, offsetBytes }, + DynamicStatPercentile_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, + std::optional reciprocationFactor, double percentile) + : DynamicStatBase_{ inType, outType, offsetBytes, reciprocationFactor }, percentile_{ percentile } { } @@ -227,7 +257,7 @@ namespace pmon::mid } void GatherToBlob(uint8_t* pBase) const override { - WriteOptionalValueToBlob_(pBase, this->offsetBytes_, this->outType_, value_); + this->WriteValue_(pBase, value_); // reset for the next poll value_.reset(); } @@ -240,8 +270,9 @@ namespace pmon::mid class DynamicStatMinMax_ : public DynamicStatBase_ { public: - DynamicStatMinMax_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, bool isMax) - : DynamicStatBase_{ inType, outType, offsetBytes }, + DynamicStatMinMax_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, + std::optional reciprocationFactor, bool isMax) + : DynamicStatBase_{ inType, outType, offsetBytes, reciprocationFactor }, isMax_{ isMax } { } @@ -271,7 +302,7 @@ namespace pmon::mid } void GatherToBlob(uint8_t* pBase) const override { - WriteOptionalValueToBlob_(pBase, this->offsetBytes_, this->outType_, value_); + this->WriteValue_(pBase, value_); // reset min/max value_.reset(); } @@ -284,8 +315,9 @@ namespace pmon::mid class DynamicStatPoint_ : public DynamicStatBase_ { public: - DynamicStatPoint_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, PM_STAT mode) - : DynamicStatBase_{ inType, outType, offsetBytes }, + DynamicStatPoint_(PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, + std::optional reciprocationFactor, PM_STAT mode) + : DynamicStatBase_{ inType, outType, offsetBytes, reciprocationFactor }, mode_{ mode } { } @@ -314,7 +346,7 @@ namespace pmon::mid } void GatherToBlob(uint8_t* pBase) const override { - WriteOptionalValueToBlob_(pBase, this->offsetBytes_, this->outType_, value_); + this->WriteValue_(pBase, value_); // reset for the next poll value_.reset(); } @@ -327,35 +359,36 @@ namespace pmon::mid namespace { template - std::unique_ptr> MakeDynamicStatTyped_(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes) + std::unique_ptr> MakeDynamicStatTyped_(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, + size_t offsetBytes, std::optional reciprocationFactor) { switch (stat) { case PM_STAT_AVG: - return std::make_unique>(inType, outType, offsetBytes, false); + return std::make_unique>(inType, outType, offsetBytes, reciprocationFactor, false); case PM_STAT_NON_ZERO_AVG: - return std::make_unique>(inType, outType, offsetBytes, true); + return std::make_unique>(inType, outType, offsetBytes, reciprocationFactor, true); case PM_STAT_PERCENTILE_99: - return std::make_unique>(inType, outType, offsetBytes, 0.99); + return std::make_unique>(inType, outType, offsetBytes, reciprocationFactor, 0.99); case PM_STAT_PERCENTILE_95: - return std::make_unique>(inType, outType, offsetBytes, 0.95); + return std::make_unique>(inType, outType, offsetBytes, reciprocationFactor, 0.95); case PM_STAT_PERCENTILE_90: - return std::make_unique>(inType, outType, offsetBytes, 0.90); + return std::make_unique>(inType, outType, offsetBytes, reciprocationFactor, 0.90); case PM_STAT_PERCENTILE_01: - return std::make_unique>(inType, outType, offsetBytes, 0.01); + return std::make_unique>(inType, outType, offsetBytes, reciprocationFactor, 0.01); case PM_STAT_PERCENTILE_05: - return std::make_unique>(inType, outType, offsetBytes, 0.05); + return std::make_unique>(inType, outType, offsetBytes, reciprocationFactor, 0.05); case PM_STAT_PERCENTILE_10: - return std::make_unique>(inType, outType, offsetBytes, 0.10); + return std::make_unique>(inType, outType, offsetBytes, reciprocationFactor, 0.10); case PM_STAT_MAX: - return std::make_unique>(inType, outType, offsetBytes, true); + return std::make_unique>(inType, outType, offsetBytes, reciprocationFactor, true); case PM_STAT_MIN: - return std::make_unique>(inType, outType, offsetBytes, false); + return std::make_unique>(inType, outType, offsetBytes, reciprocationFactor, false); case PM_STAT_MID_POINT: - return std::make_unique>(inType, outType, offsetBytes, PM_STAT_MID_POINT); + return std::make_unique>(inType, outType, offsetBytes, reciprocationFactor, PM_STAT_MID_POINT); case PM_STAT_NEWEST_POINT: - return std::make_unique>(inType, outType, offsetBytes, PM_STAT_NEWEST_POINT); + return std::make_unique>(inType, outType, offsetBytes, reciprocationFactor, PM_STAT_NEWEST_POINT); case PM_STAT_OLDEST_POINT: - return std::make_unique>(inType, outType, offsetBytes, PM_STAT_OLDEST_POINT); + return std::make_unique>(inType, outType, offsetBytes, reciprocationFactor, PM_STAT_OLDEST_POINT); case PM_STAT_NONE: case PM_STAT_MID_LERP: case PM_STAT_COUNT: @@ -368,18 +401,19 @@ namespace pmon::mid } template - std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes) + std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, + std::optional reciprocationFactor) { - return MakeDynamicStatTyped_(stat, inType, outType, offsetBytes); + return MakeDynamicStatTyped_(stat, inType, outType, offsetBytes, reciprocationFactor); } - template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); - template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); - template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); - template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); - template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); - template std::unique_ptr>> MakeDynamicStat>(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); - template std::unique_ptr> MakeDynamicStat<::PresentMode>(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); - template std::unique_ptr> MakeDynamicStat<::Runtime>(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); - template std::unique_ptr> MakeDynamicStat<::FrameType>(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); + template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, std::optional reciprocationFactor); + template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, std::optional reciprocationFactor); + template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, std::optional reciprocationFactor); + template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, std::optional reciprocationFactor); + template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, std::optional reciprocationFactor); + template std::unique_ptr>> MakeDynamicStat>(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, std::optional reciprocationFactor); + template std::unique_ptr> MakeDynamicStat<::PresentMode>(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, std::optional reciprocationFactor); + template std::unique_ptr> MakeDynamicStat<::Runtime>(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, std::optional reciprocationFactor); + template std::unique_ptr> MakeDynamicStat<::FrameType>(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, std::optional reciprocationFactor); } diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicStat.h b/IntelPresentMon/PresentMonMiddleware/DynamicStat.h index c8bd126a..a0e2cc9b 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicStat.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicStat.h @@ -2,6 +2,7 @@ #include #include #include +#include #include #include "../PresentMonAPI2/PresentMonAPI.h" @@ -27,11 +28,12 @@ namespace pmon::mid }; template - std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t blobOffsetBytes); + std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, + size_t blobOffsetBytes, std::optional reciprocationFactor); - extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); - extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); - extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); - extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); - extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes); + extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, std::optional reciprocationFactor); + extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, std::optional reciprocationFactor); + extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, std::optional reciprocationFactor); + extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, std::optional reciprocationFactor); + extern template std::unique_ptr> MakeDynamicStat(PM_STAT stat, PM_DATA_TYPE inType, PM_DATA_TYPE outType, size_t offsetBytes, std::optional reciprocationFactor); } diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index 6d6886ce..b0a6d76c 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -1623,12 +1623,6 @@ TEST_CLASS(ComputeMetricsForPresentTests) 0.0001, L"First frame should have msBetweenPresents == 0."); - Assert::AreEqual( - 0.0, - firstMetrics[0].metrics.fpsPresent, - 0.0001, - L"First frame should have fpsPresent == 0 when msBetweenPrese"); - // Chain should now treat this as lastPresent / lastAppPresent Assert::IsTrue(chain.lastPresent.has_value()); if (!chain.lastPresent.has_value()) @@ -1665,12 +1659,6 @@ TEST_CLASS(ComputeMetricsForPresentTests) 0.0001, L"msBetweenPresents should equal the unsigned delta between lastPresent and current presentStartTime."); - double expectedFps = (expectedDelta > 0.0) ? (1000.0 / expectedDelta) : 0.0; - Assert::AreEqual( - expectedFps, - secondMetrics[0].metrics.fpsPresent, - 0.0001, - L"fpsPresents should be derived from msBetweenPresents"); } TEST_METHOD(ComputeMetricsForPresent_NotDisplayed_BaseTimingAndCpuStart_AreCorrect) { @@ -2091,7 +2079,6 @@ TEST_CLASS(ComputeMetricsForPresentTests) const auto& m = results[0].metrics; Assert::AreEqual(0.0, m.msBetweenDisplayChange, 0.0001); - Assert::AreEqual(0.0, m.fpsDisplay, 0.0001); } TEST_METHOD(SubsequentDisplayedFrame_UsesChainLastDisplayedScreenTime) @@ -2117,8 +2104,6 @@ TEST_CLASS(ComputeMetricsForPresentTests) double expected = qpc.DeltaUnsignedMilliSeconds(4'000'000, 5'500'000); Assert::AreEqual(expected, m.msBetweenDisplayChange, 0.0001); - double fpsExpected = (expected > 0.0) ? (1000.0 / expected) : 0.0; - Assert::AreEqual(fpsExpected, m.fpsDisplay, 0.0001); } TEST_METHOD(NotDisplayed_ReturnsZero) @@ -2138,7 +2123,6 @@ TEST_CLASS(ComputeMetricsForPresentTests) const auto& m = results[0].metrics; Assert::AreEqual(0.0, m.msBetweenDisplayChange, 0.0001); - Assert::AreEqual(0.0, m.fpsDisplay, 0.0001); } TEST_METHOD(MultipleDisplays_EachComputesDeltaFromPrior) @@ -3510,9 +3494,9 @@ TEST_CLASS(ComputeMetricsForPresentTests) Assert::AreEqual(0.0, m.msCPUWait, 0.0001); } - TEST_METHOD(CPUTime_AndFpsApplication_AreDerivedCorrectly) + TEST_METHOD(CPUTime_IsDerivedCorrectly) { - // Verify msCPUTime = msCPUBusy + msCPUWait, and fpsApplication = 1000 / msCPUTime. + // Verify msCPUTime = msCPUBusy + msCPUWait. QpcConverter qpc(10'000'000, 0); SwapChainCoreState chain{}; @@ -3552,9 +3536,6 @@ TEST_CLASS(ComputeMetricsForPresentTests) Assert::AreEqual(expectedBusy, m.msCPUBusy, 0.0001); Assert::AreEqual(expectedWait, m.msCPUWait, 0.0001); Assert::AreEqual(expectedCpuTime, m.msCPUTime, 0.0001); - - double expectedFps = expectedCpuTime > 0.0 ? 1000.0 / expectedCpuTime : 0.0; - Assert::AreEqual(expectedFps, m.fpsApplication, 0.0001); } }; From 43defb4024ebfdd9410c6c0ce0e870972aeadea1 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 4 Feb 2026 11:06:52 +0900 Subject: [PATCH 173/205] abs handling for animation error in dyn queries --- .../mc/FrameMetricsMemberMap.h | 17 ++++++- .../PresentMonMiddleware/DynamicMetric.h | 46 ++++++++++++++++--- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/mc/FrameMetricsMemberMap.h b/IntelPresentMon/CommonUtilities/mc/FrameMetricsMemberMap.h index a6c37b12..c3ef779e 100644 --- a/IntelPresentMon/CommonUtilities/mc/FrameMetricsMemberMap.h +++ b/IntelPresentMon/CommonUtilities/mc/FrameMetricsMemberMap.h @@ -30,7 +30,8 @@ namespace pmon::util::metrics template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::runtime;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::allowsTearing;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msDisplayLatency;}; - template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msAnimationError;}; + template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msAnimationError; + static constexpr bool needsDynamicAbs = true;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msClickToPhotonLatency;}; template<>struct FrameMetricMember{static constexpr auto member=&FrameMetrics::msCPUTime; static constexpr auto reciprocationFactor = 1000.;}; @@ -58,6 +59,9 @@ namespace pmon::util::metrics template inline constexpr bool HasReciprocationFactor = requires{ FrameMetricMember::reciprocationFactor; }; + template + inline constexpr bool HasDynamicAbsRequirement = requires{ FrameMetricMember::needsDynamicAbs; }; + template constexpr std::optional GetReciprocationFactor() { @@ -68,4 +72,15 @@ namespace pmon::util::metrics return std::nullopt; } } + + template + constexpr bool NeedsDynamicAbs() + { + if constexpr (HasDynamicAbsRequirement) { + return FrameMetricMember::needsDynamicAbs; + } + else { + return false; + } + } } diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h index 4d4655c1..b254ddb6 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h +++ b/IntelPresentMon/PresentMonMiddleware/DynamicMetric.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include #include @@ -52,10 +53,11 @@ namespace pmon::mid class DynamicMetricBinding : public DynamicMetric { public: - DynamicMetricBinding(PM_METRIC metric, std::optional reciprocationFactor) + DynamicMetricBinding(PM_METRIC metric, std::optional reciprocationFactor, bool needsDynamicAbs) : metric_{ metric }, - reciprocationFactor_{ reciprocationFactor } + reciprocationFactor_{ reciprocationFactor }, + needsDynamicAbs_{ needsDynamicAbs } { } @@ -66,7 +68,7 @@ namespace pmon::mid void AddSample(const S& sample) override { - const auto& value = sample.*MemberPtr; + const auto value = AdjustSample_(sample.*MemberPtr); // if samples has reserved size, it is needed if (samples_.capacity()) { samples_.push_back(value); @@ -99,7 +101,7 @@ namespace pmon::mid throw pmon::util::Except(PM_STATUS_FAILURE, "DynamicMetricBinding received null point sample."); } - stat->SetSampledValue(sample->*MemberPtr); + stat->SetSampledValue(AdjustSample_(sample->*MemberPtr)); } } @@ -158,6 +160,35 @@ namespace pmon::mid ~DynamicMetricBinding() = default; private: + T AdjustSample_(T value) const + { + if (!needsDynamicAbs_) { + return value; + } + return ApplyAbs_(value); + } + + static T ApplyAbs_(T value) + { + if constexpr (util::IsStdOptional) { + using ValueType = typename T::value_type; + if (value) { + if constexpr (std::is_arithmetic_v && std::is_signed_v) { + using std::abs; + *value = static_cast(abs(*value)); + } + } + return value; + } + else if constexpr (std::is_arithmetic_v && std::is_signed_v) { + using std::abs; + return static_cast(abs(value)); + } + else { + return value; + } + } + static constexpr PM_DATA_TYPE GetSampleType_() { if constexpr (std::is_same_v) { @@ -190,6 +221,7 @@ namespace pmon::mid PM_METRIC metric_; std::optional reciprocationFactor_; + bool needsDynamicAbs_ = false; mutable boost::container::vector samples_; std::vector>> statPtrs_; std::vector*> needsUpdatePtrs_; @@ -210,7 +242,9 @@ namespace pmon::mid if constexpr (std::is_same_v) { using MemberType = typename MemberInfo::MemberType; auto reciprocationFactor = util::metrics::GetReciprocationFactor(); - return std::make_unique>(Metric, reciprocationFactor); + const auto needsDynamicAbs = util::metrics::NeedsDynamicAbs(); + return std::make_unique>( + Metric, reciprocationFactor, needsDynamicAbs); } } if constexpr (requires { &S::value; }) { @@ -218,7 +252,7 @@ namespace pmon::mid constexpr auto memberPtr = &S::value; using MemberInfo = util::MemberPointerInfo; using MemberType = typename MemberInfo::MemberType; - return std::make_unique>(Metric, std::nullopt); + return std::make_unique>(Metric, std::nullopt, false); } pmlog_error("Cannot make dynamic metric for").pmwatch((int)Metric); return {}; From a1bf83688ca4d7dc3d91093ea415ca888c38a754 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 4 Feb 2026 14:26:36 +0900 Subject: [PATCH 174/205] injected timestamp for paced polled automated tests --- .../CommonUtilities/IntervalWaiter.cpp | 5 ++++ .../CommonUtilities/IntervalWaiter.h | 1 + IntelPresentMon/CommonUtilities/Qpc.cpp | 6 +++++ IntelPresentMon/CommonUtilities/Qpc.h | 1 + .../SampleClient/PacedPlayback.cpp | 27 ++++++++++++------- 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/IntelPresentMon/CommonUtilities/IntervalWaiter.cpp b/IntelPresentMon/CommonUtilities/IntervalWaiter.cpp index d0815d36..63749420 100644 --- a/IntelPresentMon/CommonUtilities/IntervalWaiter.cpp +++ b/IntelPresentMon/CommonUtilities/IntervalWaiter.cpp @@ -47,4 +47,9 @@ namespace pmon::util } return res; } + + int64_t IntervalWaiter::TargetTimeToTimestamp(double targetTime) const + { + return timer_.TimeToTimestamp(targetTime); + } } diff --git a/IntelPresentMon/CommonUtilities/IntervalWaiter.h b/IntelPresentMon/CommonUtilities/IntervalWaiter.h index 33301ce5..40b18c55 100644 --- a/IntelPresentMon/CommonUtilities/IntervalWaiter.h +++ b/IntelPresentMon/CommonUtilities/IntervalWaiter.h @@ -27,6 +27,7 @@ namespace pmon::util void SetInterval(double intervalSeconds); void SetInterval(std::chrono::nanoseconds interval); WaitResult Wait(); + int64_t TargetTimeToTimestamp(double targetTime) const; private: double intervalSeconds_; double lastTargetTime_ = 0.; diff --git a/IntelPresentMon/CommonUtilities/Qpc.cpp b/IntelPresentMon/CommonUtilities/Qpc.cpp index 6d7a7dd0..f1801059 100644 --- a/IntelPresentMon/CommonUtilities/Qpc.cpp +++ b/IntelPresentMon/CommonUtilities/Qpc.cpp @@ -104,6 +104,12 @@ namespace pmon::util } return t - seconds; } + int64_t QpcTimer::TimeToTimestamp(double seconds) const noexcept + { + return startTimestamp_ + int64_t(seconds / performanceCounterPeriod_); + } + + // Duration in ticks -> ms double QpcConverter::TicksToMilliSeconds(uint64_t ticks) const noexcept diff --git a/IntelPresentMon/CommonUtilities/Qpc.h b/IntelPresentMon/CommonUtilities/Qpc.h index 3bdf25d6..ecd30800 100644 --- a/IntelPresentMon/CommonUtilities/Qpc.h +++ b/IntelPresentMon/CommonUtilities/Qpc.h @@ -25,6 +25,7 @@ namespace pmon::util int64_t GetStartTimestamp() const noexcept; double GetPerformanceCounterPeriod() const noexcept; double SpinWaitUntil(double seconds) const noexcept; + int64_t TimeToTimestamp(double seconds) const noexcept; private: double performanceCounterPeriod_; int64_t startTimestamp_ = 0; diff --git a/IntelPresentMon/SampleClient/PacedPlayback.cpp b/IntelPresentMon/SampleClient/PacedPlayback.cpp index b25e8f9a..f7254eed 100644 --- a/IntelPresentMon/SampleClient/PacedPlayback.cpp +++ b/IntelPresentMon/SampleClient/PacedPlayback.cpp @@ -148,34 +148,41 @@ class TestClientModule std::vector> RecordPolling(uint32_t targetPid, double recordingStartSec, double recordingStopSec, double pollInterval) { + if (recordingStartSec < 1.) { + pmlog_error("Insufficient recording start leeway").pmwatch(recordingStartSec).no_trace(); + } // start tracking target auto tracker = pSession_->TrackProcess(targetPid, true, false); // get the waiter and the timer clocks ready using Clock = std::chrono::high_resolution_clock; - const auto startTime = Clock::now(); - util::IntervalWaiter waiter{ pollInterval, 0.001 }; + // wait to give time for the static data (startQpc specifically) to propagate to the shm + // wait until 500ms before (buffer time) the requested recordingStart + util::PrecisionWaiter{}.Wait(recordingStartSec - 0.5); + // capture session startQpc and setup interval waiter to sync with session start + const auto startQpc = pmapi::PollStatic(*pSession_, tracker, PM_METRIC_SESSION_START_QPC).As(); + util::IntervalWaiter waiter{ pollInterval, (int64_t)startQpc, 0.0015 }; // run polling loop and poll into vector std::vector> rows; std::vector cells; BlobReader br{ qels_, pIntro_ }; br.Target(blobs_); - const auto recordingStart = recordingStartSec * 1s; - const auto recordingStop = recordingStopSec * 1s; - for (auto now = Clock::now(), start = Clock::now(); - now - start <= recordingStop; now = Clock::now()) { + while (true) { + const auto wr = waiter.Wait(); // skip recording while time has not reached start time - if (now - start >= recordingStart) { + if (wr.targetSec >= recordingStartSec) { cells.reserve(qels_.size() + 1); - query_.Poll(tracker, blobs_); + query_.PollWithTimestamp(tracker, blobs_, (uint64_t)waiter.TargetTimeToTimestamp(wr.targetSec)); // first column is the time as measured in polling loop - cells.push_back(std::chrono::duration(now - start).count()); + cells.push_back(wr.targetSec + wr.errorSec); // remaining columns are from the query for (size_t i = 0; i < qels_.size(); i++) { cells.push_back(br.At(i)); } rows.push_back(std::move(cells)); } - waiter.Wait(); + if (wr.targetSec >= recordingStopSec) { + break; + } } return rows; } From df785ceaed2f376b2e984e012b96db3e22678e1c Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 5 Feb 2026 05:51:36 +0900 Subject: [PATCH 175/205] restore disp fps max for paced polled tests --- IntelPresentMon/PresentMonAPI2Tests/PacedPollingTests.cpp | 2 +- IntelPresentMon/SampleClient/PacedPlayback.cpp | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/IntelPresentMon/PresentMonAPI2Tests/PacedPollingTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/PacedPollingTests.cpp index 6e67ee47..b2895c77 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/PacedPollingTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/PacedPollingTests.cpp @@ -252,7 +252,7 @@ namespace PacedPolling "--run-time"s, std::to_string(recordingStop - recordingStart), "--run-start"s, std::to_string(recordingStart), "--poll-period"s, std::to_string(pollPeriod), - "--metric-offset"s, "64"s, + "--metric-offset"s, "100"s, "--window-size"s, "1000"s, }); // load up result diff --git a/IntelPresentMon/SampleClient/PacedPlayback.cpp b/IntelPresentMon/SampleClient/PacedPlayback.cpp index f7254eed..0ad4e580 100644 --- a/IntelPresentMon/SampleClient/PacedPlayback.cpp +++ b/IntelPresentMon/SampleClient/PacedPlayback.cpp @@ -106,11 +106,6 @@ std::vector BuildQueryElementSet(const pmapi::intro::Root& int continue; } for (const auto& s : m.GetStatInfo()) { - // skip displayed fps (max) as it is broken now - // TODO: verify this and look into the underlying issue if still present - if (m.GetId() == PM_METRIC_DISPLAYED_FPS && s.GetStat() == PM_STAT_MAX) { - continue; - } qels.push_back(PM_QUERY_ELEMENT{ m.GetId(), s.GetStat() }); } } @@ -156,7 +151,7 @@ class TestClientModule // get the waiter and the timer clocks ready using Clock = std::chrono::high_resolution_clock; // wait to give time for the static data (startQpc specifically) to propagate to the shm - // wait until 500ms before (buffer time) the requested recordingStart + // wait until 500ms (buffer time) before the requested recordingStart util::PrecisionWaiter{}.Wait(recordingStartSec - 0.5); // capture session startQpc and setup interval waiter to sync with session start const auto startQpc = pmapi::PollStatic(*pSession_, tracker, PM_METRIC_SESSION_START_QPC).As(); From 84f91649c6136e02f1c68a4c098d56a971996aa8 Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 5 Feb 2026 06:26:58 +0900 Subject: [PATCH 176/205] raw stat => near point (removes lag) --- .../Interprocess/source/metadata/EnumStat.h | 6 +++--- .../Interprocess/source/metadata/MetricList.h | 12 ++++++------ .../Interprocess/source/metadata/StatsShortcuts.h | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/IntelPresentMon/Interprocess/source/metadata/EnumStat.h b/IntelPresentMon/Interprocess/source/metadata/EnumStat.h index baa0ecb5..35997451 100644 --- a/IntelPresentMon/Interprocess/source/metadata/EnumStat.h +++ b/IntelPresentMon/Interprocess/source/metadata/EnumStat.h @@ -13,9 +13,9 @@ X_(STAT, PERCENTILE_10, "10th Percentile", "10%", "Value below which 10% of the observations within the sliding window fall") \ X_(STAT, MAX, "Maximum", "max", "Maximum value of observations within the sliding window") \ X_(STAT, MIN, "Minimum", "min", "Minimum value of observations within the sliding window") \ - X_(STAT, MID_POINT, "Midpoint", "raw", "Point sample of the observation nearest to the middle of the sliding window") \ + X_(STAT, MID_POINT, "Midpoint", "mpt", "Point sample of the observation nearest to the middle of the sliding window") \ X_(STAT, MID_LERP, "Mid Lerp", "mlp", "Linear interpolation between the two observations nearest to the middle of the sliding window") \ - X_(STAT, NEWEST_POINT, "Newest Point", "npt", "Value in the most recent observation in the sliding window") \ - X_(STAT, OLDEST_POINT, "Oldest Point", "opt", "Value in the least recent observation in the sliding window") \ + X_(STAT, NEWEST_POINT, "Newest Point", "raw", "Point sample of the most recent observation in the sliding window") \ + X_(STAT, OLDEST_POINT, "Oldest Point", "opt", "Point sample of the least recent observation in the sliding window") \ X_(STAT, COUNT, "Count", "cnt", "Count of observations in the sliding window matching a predicate (e.g. counting # of observations for which a field is boolean true)") \ X_(STAT, NON_ZERO_AVG, "Non-zero Average", (const char*)u8"avg", "Average or mean of frame samples over the sliding window, excluding all zero values") \ No newline at end of file diff --git a/IntelPresentMon/Interprocess/source/metadata/MetricList.h b/IntelPresentMon/Interprocess/source/metadata/MetricList.h index bce736da..6ae5bf03 100644 --- a/IntelPresentMon/Interprocess/source/metadata/MetricList.h +++ b/IntelPresentMon/Interprocess/source/metadata/MetricList.h @@ -7,7 +7,7 @@ #define METRIC_LIST(X_) \ X_(PM_METRIC_APPLICATION, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_STRING, PM_DATA_TYPE_STRING, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ X_(PM_METRIC_PROCESS_ID, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_UINT32, PM_DATA_TYPE_UINT32, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ - X_(PM_METRIC_SWAP_CHAIN_ADDRESS, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_UINT64, PM_DATA_TYPE_UINT64, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_MID_POINT) \ + X_(PM_METRIC_SWAP_CHAIN_ADDRESS, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_UINT64, PM_DATA_TYPE_UINT64, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NEWEST_POINT) \ X_(PM_METRIC_GPU_VENDOR, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_DEVICE_VENDOR, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, PM_STAT_NONE) \ X_(PM_METRIC_GPU_NAME, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_STRING, PM_DATA_TYPE_STRING, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, PM_STAT_NONE) \ X_(PM_METRIC_CPU_VENDOR, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_DEVICE_VENDOR, PM_DEVICE_TYPE_SYSTEM, PM_STAT_NONE) \ @@ -27,12 +27,12 @@ X_(PM_METRIC_DISPLAYED_TIME, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_ANIMATION_ERROR, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_ANIMATION_TIME, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ - X_(PM_METRIC_SYNC_INTERVAL, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_VERTICAL_BLANKS, PM_DATA_TYPE_INT32, PM_DATA_TYPE_INT32, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_MID_POINT) \ - X_(PM_METRIC_PRESENT_FLAGS, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_UINT32, PM_DATA_TYPE_UINT32, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_MID_POINT) \ - X_(PM_METRIC_PRESENT_MODE, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_PRESENT_MODE, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_MID_POINT) \ - X_(PM_METRIC_PRESENT_RUNTIME, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_GRAPHICS_RUNTIME, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_MID_POINT) \ + X_(PM_METRIC_SYNC_INTERVAL, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_VERTICAL_BLANKS, PM_DATA_TYPE_INT32, PM_DATA_TYPE_INT32, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NEWEST_POINT) \ + X_(PM_METRIC_PRESENT_FLAGS, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_UINT32, PM_DATA_TYPE_UINT32, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NEWEST_POINT) \ + X_(PM_METRIC_PRESENT_MODE, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_PRESENT_MODE, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NEWEST_POINT) \ + X_(PM_METRIC_PRESENT_RUNTIME, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_GRAPHICS_RUNTIME, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NEWEST_POINT) \ X_(PM_METRIC_ALLOWS_TEARING, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_BOOL, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ - X_(PM_METRIC_FRAME_TYPE, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_FRAME_TYPE, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_MID_POINT) \ + X_(PM_METRIC_FRAME_TYPE, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_FRAME_TYPE, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NEWEST_POINT) \ X_(PM_METRIC_GPU_LATENCY, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_DISPLAY_LATENCY, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_VOID, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, FULL_STATS) \ X_(PM_METRIC_CLICK_TO_PHOTON_LATENCY, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_MILLISECONDS, PM_DATA_TYPE_DOUBLE, PM_DATA_TYPE_DOUBLE, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NON_ZERO_AVG, PM_STAT_PERCENTILE_01, PM_STAT_PERCENTILE_05, PM_STAT_PERCENTILE_10, PM_STAT_MAX) \ diff --git a/IntelPresentMon/Interprocess/source/metadata/StatsShortcuts.h b/IntelPresentMon/Interprocess/source/metadata/StatsShortcuts.h index 0099b830..6d0d4ff1 100644 --- a/IntelPresentMon/Interprocess/source/metadata/StatsShortcuts.h +++ b/IntelPresentMon/Interprocess/source/metadata/StatsShortcuts.h @@ -3,4 +3,4 @@ #define FULL_STATS PM_STAT_AVG, PM_STAT_PERCENTILE_99, PM_STAT_PERCENTILE_95, \ PM_STAT_PERCENTILE_90, PM_STAT_PERCENTILE_01, PM_STAT_PERCENTILE_05, \ - PM_STAT_PERCENTILE_10, PM_STAT_MAX, PM_STAT_MIN, PM_STAT_MID_POINT \ No newline at end of file + PM_STAT_PERCENTILE_10, PM_STAT_MAX, PM_STAT_MIN, PM_STAT_NEWEST_POINT \ No newline at end of file From ebf4925ae0b56a7ea96e08ba365a302b31142513 Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 5 Feb 2026 07:15:08 +0900 Subject: [PATCH 177/205] enable swap addr for dynamic queries --- IntelPresentMon/Interprocess/source/metadata/MetricList.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IntelPresentMon/Interprocess/source/metadata/MetricList.h b/IntelPresentMon/Interprocess/source/metadata/MetricList.h index 6ae5bf03..c9daeec1 100644 --- a/IntelPresentMon/Interprocess/source/metadata/MetricList.h +++ b/IntelPresentMon/Interprocess/source/metadata/MetricList.h @@ -7,7 +7,7 @@ #define METRIC_LIST(X_) \ X_(PM_METRIC_APPLICATION, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_STRING, PM_DATA_TYPE_STRING, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ X_(PM_METRIC_PROCESS_ID, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_UINT32, PM_DATA_TYPE_UINT32, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NONE) \ - X_(PM_METRIC_SWAP_CHAIN_ADDRESS, PM_METRIC_TYPE_FRAME_EVENT, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_UINT64, PM_DATA_TYPE_UINT64, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NEWEST_POINT) \ + X_(PM_METRIC_SWAP_CHAIN_ADDRESS, PM_METRIC_TYPE_DYNAMIC_FRAME, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_UINT64, PM_DATA_TYPE_UINT64, 0, PM_DEVICE_TYPE_INDEPENDENT, PM_STAT_NEWEST_POINT) \ X_(PM_METRIC_GPU_VENDOR, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_DEVICE_VENDOR, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, PM_STAT_NONE) \ X_(PM_METRIC_GPU_NAME, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_STRING, PM_DATA_TYPE_STRING, 0, PM_DEVICE_TYPE_GRAPHICS_ADAPTER, PM_STAT_NONE) \ X_(PM_METRIC_CPU_VENDOR, PM_METRIC_TYPE_STATIC, PM_UNIT_DIMENSIONLESS, PM_DATA_TYPE_ENUM, PM_DATA_TYPE_ENUM, PM_ENUM_DEVICE_VENDOR, PM_DEVICE_TYPE_SYSTEM, PM_STAT_NONE) \ From ebe4f9f1da72eea8d6dd49570a9932b959977fb6 Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 5 Feb 2026 09:55:07 +0900 Subject: [PATCH 178/205] nullopt=>NaN for optional FrameMetrics members; fix duplicate in IPM fq --- .../Core/source/pmon/RawFrameDataMetricList.h | 1 - .../PresentMonMiddleware/FrameEventQuery.cpp | 52 ++++++++++--------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/IntelPresentMon/Core/source/pmon/RawFrameDataMetricList.h b/IntelPresentMon/Core/source/pmon/RawFrameDataMetricList.h index 585e884d..136ac9c0 100644 --- a/IntelPresentMon/Core/source/pmon/RawFrameDataMetricList.h +++ b/IntelPresentMon/Core/source/pmon/RawFrameDataMetricList.h @@ -41,7 +41,6 @@ namespace p2c::pmon Element{.metricId = PM_METRIC_IN_PRESENT_API, .deviceId = 0 }, Element{.metricId = PM_METRIC_RENDER_PRESENT_LATENCY, .deviceId = 0 }, Element{.metricId = PM_METRIC_UNTIL_DISPLAYED, .deviceId = 0 }, - Element{.metricId = PM_METRIC_PC_LATENCY, .deviceId = 0 }, Element{.metricId = PM_METRIC_CPU_START_TIME, .deviceId = 0 }, Element{.metricId = PM_METRIC_BETWEEN_APP_START, .deviceId = 0 }, Element{.metricId = PM_METRIC_CPU_BUSY, .deviceId = 0 }, diff --git a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp index f91d580c..edf802d6 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameEventQuery.cpp @@ -144,43 +144,45 @@ void PM_FRAME_QUERY::GatherFromFrameMetrics_(const GatherCommand_& cmd, uint8_t* std::numeric_limits::quiet_NaN(); return; } + // Write frame metric into the blob, preserving optional<...> semantics. + // For optional, nullopt maps to NaN for downstream compatibility. + const auto WriteValue = [&]() { + auto& blobValue = *reinterpret_cast(pBlobBytes + cmd.blobOffset); + if (!cmd.isOptional) { + blobValue = *reinterpret_cast(pFrameMemberBytes); + return; + } + const auto& optValue = *reinterpret_cast*>(pFrameMemberBytes); + if (optValue) { + blobValue = *optValue; + } + else { + if constexpr (std::is_same_v) { + blobValue = std::numeric_limits::quiet_NaN(); + } + else { + blobValue = T{}; + } + } + }; switch (cmd.gatherType) { case PM_DATA_TYPE_UINT64: - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - *reinterpret_cast(pFrameMemberBytes); + WriteValue.template operator()(); break; case PM_DATA_TYPE_INT32: - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - *reinterpret_cast(pFrameMemberBytes); + WriteValue.template operator()(); break; case PM_DATA_TYPE_UINT32: - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - *reinterpret_cast(pFrameMemberBytes); + WriteValue.template operator()(); break; case PM_DATA_TYPE_DOUBLE: - { - auto& blobDouble = *reinterpret_cast(pBlobBytes + cmd.blobOffset); - if (!cmd.isOptional) { - blobDouble = *reinterpret_cast(pFrameMemberBytes); - } - else { - auto& optDouble = *reinterpret_cast*>(pFrameMemberBytes); - if (optDouble) { - blobDouble = *optDouble; - } - else { - blobDouble = std::numeric_limits::quiet_NaN(); - } - } + WriteValue.template operator()(); break; - } case PM_DATA_TYPE_ENUM: - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - *reinterpret_cast(pFrameMemberBytes); + WriteValue.template operator()(); break; case PM_DATA_TYPE_BOOL: - *reinterpret_cast(pBlobBytes + cmd.blobOffset) = - *reinterpret_cast(pFrameMemberBytes); + WriteValue.template operator()(); break; case PM_DATA_TYPE_STRING: case PM_DATA_TYPE_VOID: From fffd87a91682888350c89d410a1fba9b69c2ba9f Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 5 Feb 2026 11:27:19 +0900 Subject: [PATCH 179/205] csv capture shouldn't omit columns even when metric unavailable --- .../Core/source/pmon/PresentMon.cpp | 3 +- .../Core/source/pmon/RawFrameDataWriter.cpp | 107 ++++++++++-------- .../Core/source/pmon/RawFrameDataWriter.h | 3 +- 3 files changed, 62 insertions(+), 51 deletions(-) diff --git a/IntelPresentMon/Core/source/pmon/PresentMon.cpp b/IntelPresentMon/Core/source/pmon/PresentMon.cpp index 02883aa7..1aa2330d 100644 --- a/IntelPresentMon/Core/source/pmon/PresentMon.cpp +++ b/IntelPresentMon/Core/source/pmon/PresentMon.cpp @@ -155,9 +155,10 @@ namespace p2c::pmon // flush any buffered present events before starting capture pFlusher->Flush(processTracker); + constexpr bool omitUnavailableColumns = false; // make the frame data writer return std::make_shared(std::move(path), processTracker, selectedAdapter.value_or(1), - *pSession, std::move(statsPath), *pIntrospectionRoot); + *pSession, std::move(statsPath), *pIntrospectionRoot, omitUnavailableColumns); } std::optional PresentMon::GetSelectedAdapter() const { diff --git a/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.cpp b/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.cpp index a18be8cf..e2b18ec3 100644 --- a/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.cpp +++ b/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.cpp @@ -53,11 +53,17 @@ namespace p2c::pmon return elements; } - void FilterUnavailableMetrics_(std::vector& elements, uint32_t activeDeviceId, - const pmapi::intro::Root& introRoot) + struct MetricColumn_ { - std::vector filtered; - filtered.reserve(elements.size()); + RawFrameQueryElementDefinition definition; + bool available = false; + }; + + std::vector BuildMetricColumns_(const std::vector& elements, + uint32_t activeDeviceId, const pmapi::intro::Root& introRoot, bool omitUnavailableColumns) + { + std::vector columns; + columns.reserve(elements.size()); for (const auto& element : elements) { const auto& metric = introRoot.FindMetric(element.metricId); const auto checkDeviceId = element.deviceId != 0 ? element.deviceId : activeDeviceId; @@ -74,11 +80,13 @@ namespace p2c::pmon pmlog_warn("Metric not available for active device") .pmwatch(metric.Introspect().GetSymbol()) .pmwatch(checkDeviceId); - continue; + if (omitUnavailableColumns) { + continue; + } } - filtered.push_back(element); + columns.push_back(MetricColumn_{ .definition = element, .available = available }); } - elements = std::move(filtered); + return columns; } class StreamFlagPreserver_ @@ -110,9 +118,9 @@ namespace p2c::pmon virtual ~Annotation_() = default; virtual void Write(std::ostream& out, const uint8_t* pBytes) const = 0; std::string columnName; - size_t queryElementIndex = 0; - static std::unique_ptr MakeTyped(PM_METRIC metricId, uint32_t deviceId, - const pmapi::intro::MetricView& metric); + std::optional queryElementIndex; + static std::unique_ptr MakeTyped(PM_METRIC metricId, const pmapi::intro::MetricView& metric, + bool available); protected: uint32_t flags_; @@ -184,15 +192,9 @@ namespace p2c::pmon } mutable std::optional startTime; }; - std::unique_ptr Annotation_::MakeTyped(PM_METRIC metricId, uint32_t deviceId, - const pmapi::intro::MetricView& metric) + std::unique_ptr Annotation_::MakeTyped(PM_METRIC metricId, const pmapi::intro::MetricView& metric, + bool available) { - // set availability (defaults to false, if we find matching device use its availability) - bool available = false; - for (auto di : metric.GetDeviceMetricInfo()) { - if (di.GetDevice().GetId() != deviceId) continue; - available = di.IsAvailable(); - } std::unique_ptr pAnnotation; if (available) { const auto typeId = metric.GetDataTypeInfo().GetFrameType(); @@ -251,36 +253,38 @@ namespace p2c::pmon class QueryElementContainer_ { public: - QueryElementContainer_(std::span elements, + QueryElementContainer_(std::span columns, pmapi::Session& session, const pmapi::intro::Root& introRoot) { - for (auto& el : elements) { - const auto metric = introRoot.FindMetric(el.metricId); - annotationPtrs_.push_back(Annotation_::MakeTyped(el.metricId, el.deviceId, metric)); + for (auto& column : columns) { + const auto metric = introRoot.FindMetric(column.definition.metricId); + annotationPtrs_.push_back(Annotation_::MakeTyped(column.definition.metricId, metric, column.available)); // append metric array index to column name if array metric - if (el.index.has_value()) { - annotationPtrs_.back()->columnName += std::format("[{}]", *el.index); + if (column.definition.index.has_value()) { + annotationPtrs_.back()->columnName += std::format("[{}]", *column.definition.index); } - // set index into query elements - annotationPtrs_.back()->queryElementIndex = queryElements_.size(); - // add to query elements - queryElements_.push_back(PM_QUERY_ELEMENT{ - .metric = el.metricId, - .stat = PM_STAT_NONE, - .deviceId = el.deviceId, - .arrayIndex = el.index.value_or(0), - }); - // check if metric is one of the specially-required fields - // these fields are required because they are used for summary stats - // we need pointers to these specific ones to read for generating those stats - if (el.metricId == PM_METRIC_CPU_START_TIME) { - totalTimeElementIdx_ = int(queryElements_.size() - 1); - } - else if (el.metricId == PM_METRIC_BETWEEN_PRESENTS) { - msBetweenPresentsElementIdx_ = int(queryElements_.size() - 1); - } - else if (el.metricId == PM_METRIC_ANIMATION_ERROR) { - animationErrorElementIdx_ = int(queryElements_.size() - 1); + if (column.available) { + // set index into query elements + annotationPtrs_.back()->queryElementIndex = queryElements_.size(); + // add to query elements + queryElements_.push_back(PM_QUERY_ELEMENT{ + .metric = column.definition.metricId, + .stat = PM_STAT_NONE, + .deviceId = column.definition.deviceId, + .arrayIndex = column.definition.index.value_or(0), + }); + // check if metric is one of the specially-required fields + // these fields are required because they are used for summary stats + // we need pointers to these specific ones to read for generating those stats + if (column.definition.metricId == PM_METRIC_CPU_START_TIME) { + totalTimeElementIdx_ = int(queryElements_.size() - 1); + } + else if (column.definition.metricId == PM_METRIC_BETWEEN_PRESENTS) { + msBetweenPresentsElementIdx_ = int(queryElements_.size() - 1); + } + else if (column.definition.metricId == PM_METRIC_ANIMATION_ERROR) { + animationErrorElementIdx_ = int(queryElements_.size() - 1); + } } } // if any specially-required fields are missing, add to query (but not to annotations) @@ -342,8 +346,12 @@ namespace p2c::pmon if (i) { out << ','; } + if (!pAnno->queryElementIndex.has_value()) { + out << "NA"; + continue; + } // using output from the query registration of get offset of column's data - const auto pBytes = pBlob + queryElements_[pAnno->queryElementIndex].dataOffset; + const auto pBytes = pBlob + queryElements_[*pAnno->queryElementIndex].dataOffset; // annotation contains polymorphic info to reinterpret and convert bytes pAnno->Write(out, pBytes); } @@ -372,7 +380,8 @@ namespace p2c::pmon }; RawFrameDataWriter::RawFrameDataWriter(std::wstring path, const pmapi::ProcessTracker& procTrackerIn, uint32_t activeDeviceId, - pmapi::Session& session, std::optional frameStatsPathIn, const pmapi::intro::Root& introRoot) + pmapi::Session& session, std::optional frameStatsPathIn, const pmapi::intro::Root& introRoot, + bool omitUnavailableColumns) : procTracker{ procTrackerIn }, frameStatsPath{ std::move(frameStatsPathIn) }, @@ -388,11 +397,11 @@ namespace p2c::pmon else { elements = GetDefaultRawFrameDataMetricList(activeDeviceId, opt.enableTimestampColumn); } - FilterUnavailableMetrics_(elements, activeDeviceId, introRoot); - if (elements.empty()) { + const auto columns = BuildMetricColumns_(elements, activeDeviceId, introRoot, omitUnavailableColumns); + if (columns.empty()) { pmlog_error("No valid metrics specified for frame event capture").raise<::pmon::util::Exception>(); } - pQueryElementContainer = std::make_unique(std::move(elements), session, introRoot); + pQueryElementContainer = std::make_unique(columns, session, introRoot); blobs = pQueryElementContainer->MakeBlobs(numberOfBlobs); // write header pQueryElementContainer->WriteHeader(file); diff --git a/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.h b/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.h index 5842a3ef..5dba4bed 100644 --- a/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.h +++ b/IntelPresentMon/Core/source/pmon/RawFrameDataWriter.h @@ -19,7 +19,8 @@ namespace p2c::pmon { public: RawFrameDataWriter(std::wstring path, const pmapi::ProcessTracker& procTracker, uint32_t activeDeviceId, - pmapi::Session& session, std::optional frameStatsPath, const pmapi::intro::Root& introRoot); + pmapi::Session& session, std::optional frameStatsPath, const pmapi::intro::Root& introRoot, + bool omitUnavailableColumns); RawFrameDataWriter(const RawFrameDataWriter&) = delete; RawFrameDataWriter& operator=(const RawFrameDataWriter&) = delete; void Process(); From 1d1e31ee9aa6ac8238fea434d1ecca0023f91699 Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 5 Feb 2026 13:01:04 +0900 Subject: [PATCH 180/205] update paced polled golds and relax p00 thresholds --- IntelPresentMon/PresentMonAPI2Tests/PacedPollingTests.cpp | 4 ++-- Tests/aux-data.lock.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/IntelPresentMon/PresentMonAPI2Tests/PacedPollingTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/PacedPollingTests.cpp index b2895c77..0d99826d 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/PacedPollingTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/PacedPollingTests.cpp @@ -485,8 +485,8 @@ namespace PacedPolling const auto recordingStart = 1.; const auto recordingStop = 14.; const auto pollPeriod = 0.1; - const auto toleranceFactor = 0.005; - const auto fullFailRatio = 0.667; + const auto toleranceFactor = 0.01; + const auto fullFailRatio = 0.95; // run test ExecutePacedPollingTest(STRINGIFY(TEST_NAME), targetPid, recordingStart, recordingStop, pollPeriod, toleranceFactor, fullFailRatio, fixture_); diff --git a/Tests/aux-data.lock.json b/Tests/aux-data.lock.json index 5c2e8721..43ad6796 100644 --- a/Tests/aux-data.lock.json +++ b/Tests/aux-data.lock.json @@ -1,3 +1,3 @@ { - "pinnedCommitHash": "340d15ae64a23c54dded7793805f01dc2fcb4166" + "pinnedCommitHash": "1ef35d437a4bece0d8a1beca13da4fdcf98cd5ca" } From de63bd43843bb676876f9689ac62ef69bb078508 Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 5 Feb 2026 14:08:33 +0900 Subject: [PATCH 181/205] add frame flush to Capi/wrapper --- .../Core/source/pmon/PresentMon.cpp | 23 +------------------ IntelPresentMon/Core/source/pmon/PresentMon.h | 2 -- .../PresentMonAPI2/PresentMonAPI.cpp | 12 ++++++++++ .../PresentMonAPI2/PresentMonAPI.h | 4 +++- .../PresentMonAPI2Loader/Implementation.cpp | 7 ++++++ .../PresentMonAPIWrapper/ProcessTracker.cpp | 8 +++++++ .../PresentMonAPIWrapper/ProcessTracker.h | 2 ++ .../ConcreteMiddleware.cpp | 15 ++++++++++++ .../PresentMonMiddleware/ConcreteMiddleware.h | 1 + .../FrameMetricsSource.cpp | 10 ++++++++ .../PresentMonMiddleware/FrameMetricsSource.h | 1 + .../PresentMonMiddleware/Middleware.h | 1 + 12 files changed, 61 insertions(+), 25 deletions(-) diff --git a/IntelPresentMon/Core/source/pmon/PresentMon.cpp b/IntelPresentMon/Core/source/pmon/PresentMon.cpp index 1aa2330d..c7f6466a 100644 --- a/IntelPresentMon/Core/source/pmon/PresentMon.cpp +++ b/IntelPresentMon/Core/source/pmon/PresentMon.cpp @@ -12,25 +12,6 @@ namespace p2c::pmon { using namespace ::pmapi; - class FrameEventFlusher - { - public: - FrameEventFlusher(Session& sesh) - { - // register minimal query used for flushing frame events - std::array queryElements{ PM_QUERY_ELEMENT{ PM_METRIC_PRESENT_MODE, PM_STAT_MID_POINT } }; - query_ = sesh.RegisterFrameQuery(queryElements); - blobs_ = query_.MakeBlobContainer(50); - } - void Flush(ProcessTracker& tracker) - { - query_.ForEachConsume(tracker, blobs_, [](auto) {}); - } - private: - FrameQuery query_; - BlobContainer blobs_; - }; - PresentMon::PresentMon(std::optional namedPipeName, double window_in, double offset_in, uint32_t telemetrySamplePeriodMs_in) { const auto RemoveDoubleQuotes = [](std::string s) { @@ -56,8 +37,6 @@ namespace p2c::pmon SetGpuTelemetryPeriod(telemetrySamplePeriodMs_in); SetEtwFlushPeriod(std::nullopt); - // create flusher used to clear out piled-up old frame events before capture - pFlusher = std::make_unique(*pSession); } PresentMon::~PresentMon() = default; void PresentMon::StartTracking(uint32_t pid_) @@ -153,7 +132,7 @@ namespace p2c::pmon std::optional statsPath, uint32_t pid) { // flush any buffered present events before starting capture - pFlusher->Flush(processTracker); + processTracker.FlushFrames(); constexpr bool omitUnavailableColumns = false; // make the frame data writer diff --git a/IntelPresentMon/Core/source/pmon/PresentMon.h b/IntelPresentMon/Core/source/pmon/PresentMon.h index aee20427..a6a08b9a 100644 --- a/IntelPresentMon/Core/source/pmon/PresentMon.h +++ b/IntelPresentMon/Core/source/pmon/PresentMon.h @@ -20,7 +20,6 @@ namespace pmapi namespace p2c::pmon { class RawFrameDataWriter; - class FrameEventFlusher; class PresentMon { @@ -51,7 +50,6 @@ namespace p2c::pmon std::optional etwFlushPeriodMs; pmapi::EtlLogger etlLogger; std::unique_ptr pSession; - std::unique_ptr pFlusher; std::shared_ptr pIntrospectionRoot; pmapi::ProcessTracker processTracker; std::optional selectedAdapter; diff --git a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp index 7690eb40..2670a3cf 100644 --- a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp +++ b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp @@ -269,6 +269,18 @@ PRESENTMON_API2_EXPORT PM_STATUS pmSetEtwFlushPeriod(PM_SESSION_HANDLE handle, u } } +PRESENTMON_API2_EXPORT PM_STATUS pmFlushFrames(PM_SESSION_HANDLE handle, uint32_t processId) +{ + try { + return LookupMiddleware_(handle).FlushFrames(processId); + } + catch (...) { + const auto code = util::GeneratePmStatus(); + pmlog_error(util::ReportException()).code(code); + return code; + } +} + PRESENTMON_API2_EXPORT PM_STATUS pmRegisterDynamicQuery(PM_SESSION_HANDLE sessionHandle, PM_DYNAMIC_QUERY_HANDLE* pQueryHandle, PM_QUERY_ELEMENT* pElements, uint64_t numElements, double windowSizeMs, double metricOffsetMs) { diff --git a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h index d02f6112..70dcc577 100644 --- a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h +++ b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.h @@ -10,7 +10,7 @@ #include #define PM_API_VERSION_MAJOR 3 -#define PM_API_VERSION_MINOR 2 +#define PM_API_VERSION_MINOR 3 #define PM_MAX_PATH 260 @@ -419,6 +419,8 @@ extern "C" { // a value of zero indicates to use current service setting (default or value requested by other client) PRESENTMON_API2_EXPORT PM_STATUS pmSetEtwFlushPeriod(PM_SESSION_HANDLE handle, uint32_t periodMs); #define PM_ETW_FLUSH_PERIOD_MAX 1000 + // flush any buffered frame event data for the specified process on this session + PRESENTMON_API2_EXPORT PM_STATUS pmFlushFrames(PM_SESSION_HANDLE handle, uint32_t processId); // register a dynamic query used for polling metric data with (optional) statistic processing such as average or percentile PRESENTMON_API2_EXPORT PM_STATUS pmRegisterDynamicQuery(PM_SESSION_HANDLE sessionHandle, PM_DYNAMIC_QUERY_HANDLE* pHandle, PM_QUERY_ELEMENT* pElements, uint64_t numElements, double windowSizeMs, double metricOffsetMs); // free the resources associated with a registered dynamic query diff --git a/IntelPresentMon/PresentMonAPI2Loader/Implementation.cpp b/IntelPresentMon/PresentMonAPI2Loader/Implementation.cpp index 8825eb33..00b1cd4a 100644 --- a/IntelPresentMon/PresentMonAPI2Loader/Implementation.cpp +++ b/IntelPresentMon/PresentMonAPI2Loader/Implementation.cpp @@ -33,6 +33,7 @@ PM_STATUS(*pFunc_pmGetIntrospectionRoot_)(PM_SESSION_HANDLE, const PM_INTROSPECT PM_STATUS(*pFunc_pmFreeIntrospectionRoot_)(const PM_INTROSPECTION_ROOT*) = nullptr; PM_STATUS(*pFunc_pmSetTelemetryPollingPeriod_)(PM_SESSION_HANDLE, uint32_t, uint32_t) = nullptr; PM_STATUS(*pFunc_pmSetEtwFlushPeriod_)(PM_SESSION_HANDLE, uint32_t) = nullptr; +PM_STATUS(*pFunc_pmFlushFrames_)(PM_SESSION_HANDLE, uint32_t) = nullptr; PM_STATUS(*pFunc_pmRegisterDynamicQuery_)(PM_SESSION_HANDLE, PM_DYNAMIC_QUERY_HANDLE*, PM_QUERY_ELEMENT*, uint64_t, double, double) = nullptr; PM_STATUS(*pFunc_pmFreeDynamicQuery_)(PM_DYNAMIC_QUERY_HANDLE) = nullptr; PM_STATUS(*pFunc_pmPollDynamicQuery_)(PM_DYNAMIC_QUERY_HANDLE, uint32_t, uint8_t*, uint32_t*) = nullptr; @@ -164,6 +165,7 @@ PRESENTMON_API2_EXPORT PM_STATUS LoadLibrary_(bool versionOnly = false) RESOLVE(pmFreeIntrospectionRoot); RESOLVE(pmSetTelemetryPollingPeriod); RESOLVE(pmSetEtwFlushPeriod); + RESOLVE(pmFlushFrames); RESOLVE(pmRegisterDynamicQuery); RESOLVE(pmFreeDynamicQuery); RESOLVE(pmPollDynamicQuery); @@ -262,6 +264,11 @@ PRESENTMON_API2_EXPORT PM_STATUS pmSetEtwFlushPeriod(PM_SESSION_HANDLE handle, u LoadEndpointsIfEmpty_(); return pFunc_pmSetEtwFlushPeriod_(handle, periodMs); } +PRESENTMON_API2_EXPORT PM_STATUS pmFlushFrames(PM_SESSION_HANDLE handle, uint32_t processId) +{ + LoadEndpointsIfEmpty_(); + return pFunc_pmFlushFrames_(handle, processId); +} PRESENTMON_API2_EXPORT PM_STATUS pmRegisterDynamicQuery(PM_SESSION_HANDLE sessionHandle, PM_DYNAMIC_QUERY_HANDLE* pHandle, PM_QUERY_ELEMENT* pElements, uint64_t numElements, double windowSizeMs, double metricOffsetMs) { LoadEndpointsIfEmpty_(); diff --git a/IntelPresentMon/PresentMonAPIWrapper/ProcessTracker.cpp b/IntelPresentMon/PresentMonAPIWrapper/ProcessTracker.cpp index 85b09b66..edc65e53 100644 --- a/IntelPresentMon/PresentMonAPIWrapper/ProcessTracker.cpp +++ b/IntelPresentMon/PresentMonAPIWrapper/ProcessTracker.cpp @@ -30,6 +30,14 @@ namespace pmapi return pid_; } + void ProcessTracker::FlushFrames() + { + assert(!Empty()); + if (auto sta = pmFlushFrames(hSession_, pid_); sta != PM_STATUS_SUCCESS) { + throw ApiErrorException{ sta, "flush frames call failed" }; + } + } + void ProcessTracker::Reset() noexcept { if (!Empty()) { diff --git a/IntelPresentMon/PresentMonAPIWrapper/ProcessTracker.h b/IntelPresentMon/PresentMonAPIWrapper/ProcessTracker.h index 3d1ff6a8..7771d7e1 100644 --- a/IntelPresentMon/PresentMonAPIWrapper/ProcessTracker.h +++ b/IntelPresentMon/PresentMonAPIWrapper/ProcessTracker.h @@ -20,6 +20,8 @@ namespace pmapi ProcessTracker& operator=(ProcessTracker&& rhs) noexcept; // get the id of process being tracked uint32_t GetPid() const; + // flush any buffered frame event data for this process + void FlushFrames(); // empty this tracker (stop tracking process if any) void Reset() noexcept; // check if tracker is empty diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp index 48ddc015..ffcc8892 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp @@ -191,6 +191,21 @@ namespace pmon::mid return PM_STATUS_SUCCESS; } + PM_STATUS ConcreteMiddleware::FlushFrames(uint32_t processId) + { + try { + if (auto it = frameMetricsSources.find(processId); it != frameMetricsSources.end() && it->second) { + it->second->Flush(); + } + } + catch (...) { + const auto code = util::GeneratePmStatus(); + pmlog_error(util::ReportException()).code(code).diag(); + return code; + } + return PM_STATUS_SUCCESS; + } + PM_DYNAMIC_QUERY* ConcreteMiddleware::RegisterDynamicQuery(std::span queryElements, double windowSizeMs, double metricOffsetMs) { diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h index 5c0761b3..658bbb52 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h +++ b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h @@ -32,6 +32,7 @@ namespace pmon::mid PM_STATUS StopStreaming(uint32_t processId) override; PM_STATUS SetTelemetryPollingPeriod(uint32_t deviceId, uint32_t timeMs) override; PM_STATUS SetEtwFlushPeriod(std::optional periodMs) override; + PM_STATUS FlushFrames(uint32_t processId) override; PM_DYNAMIC_QUERY* RegisterDynamicQuery(std::span queryElements, double windowSizeMs, double metricOffsetMs) override; void FreeDynamicQuery(const PM_DYNAMIC_QUERY* pQuery) override {} void PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, diff --git a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp index febc0af1..5cc3a502 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp +++ b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.cpp @@ -271,6 +271,16 @@ namespace pmon::mid return output; } + void FrameMetricsSource::Flush() + { + Update(); + for (auto& [address, state] : swapChains_) { + while (state.HasPending()) { + state.ConsumeNext(); + } + } + } + std::vector FrameMetricsSource::GetSwapChainAddressesInTimestampRange(uint64_t start, uint64_t end) const { std::vector> candidates; diff --git a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h index 84acf1d1..cc97a11d 100644 --- a/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h +++ b/IntelPresentMon/PresentMonMiddleware/FrameMetricsSource.h @@ -78,6 +78,7 @@ namespace pmon::mid void Update(); std::vector Consume(size_t maxFrames); + void Flush(); std::vector GetSwapChainAddressesInTimestampRange(uint64_t start, uint64_t end) const; const SwapChainState* FindSwapChainState(uint64_t swapChainAddress) const; const util::QpcConverter& GetQpcConverter() const; diff --git a/IntelPresentMon/PresentMonMiddleware/Middleware.h b/IntelPresentMon/PresentMonMiddleware/Middleware.h index cf8ffb9b..9c70e8dd 100644 --- a/IntelPresentMon/PresentMonMiddleware/Middleware.h +++ b/IntelPresentMon/PresentMonMiddleware/Middleware.h @@ -18,6 +18,7 @@ namespace pmon::mid virtual PM_STATUS StopStreaming(uint32_t processId) = 0; virtual PM_STATUS SetTelemetryPollingPeriod(uint32_t deviceId, uint32_t timeMs) = 0; virtual PM_STATUS SetEtwFlushPeriod(std::optional periodMs) = 0; + virtual PM_STATUS FlushFrames(uint32_t processId) = 0; virtual PM_DYNAMIC_QUERY* RegisterDynamicQuery(std::span queryElements, double windowSizeMs, double metricOffsetMs) = 0; virtual void FreeDynamicQuery(const PM_DYNAMIC_QUERY* pQuery) = 0; virtual void PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t* numSwapChains, std::optional nowTimestamp = {}) = 0; From ca712e62566de3e9ad9561fbac770230f6996616 Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 5 Feb 2026 14:57:14 +0900 Subject: [PATCH 182/205] de-virtualize middleware --- .../PresentMonAPI2/PresentMonAPI.cpp | 6 +- .../PresentMonMiddleware/ConcreteMiddleware.h | 61 ---------------- ...{ConcreteMiddleware.cpp => Middleware.cpp} | 46 ++++++------ .../PresentMonMiddleware/Middleware.h | 72 +++++++++++++------ .../PresentMonMiddleware.vcxproj | 5 +- .../PresentMonMiddleware.vcxproj.filters | 5 +- 6 files changed, 79 insertions(+), 116 deletions(-) delete mode 100644 IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h rename IntelPresentMon/PresentMonMiddleware/{ConcreteMiddleware.cpp => Middleware.cpp} (83%) diff --git a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp index 2670a3cf..d1fac864 100644 --- a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp +++ b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp @@ -1,7 +1,7 @@ #include #include #include -#include "../PresentMonMiddleware/ConcreteMiddleware.h" +#include "../PresentMonMiddleware/Middleware.h" #include "../Interprocess/source/PmStatusError.h" #include "Internal.h" #include "PresentMonAPI.h" @@ -126,8 +126,8 @@ PRESENTMON_API2_EXPORT PM_STATUS pmOpenSessionWithPipe(PM_SESSION_HANDLE* pHandl return PM_STATUS_BAD_ARGUMENT; } std::shared_ptr pMiddleware; - pMiddleware = std::make_shared(pipe ? std::optional{ pipe } : std::nullopt); - *pHandle = pMiddleware.get(); + pMiddleware = std::make_shared(pipe ? std::optional{ pipe } : std::nullopt); + *pHandle = reinterpret_cast(pMiddleware.get()); handleMap_[*pHandle] = std::move(pMiddleware); pmlog_info("Middleware successfully opened session with service"); return PM_STATUS_SUCCESS; diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h b/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h deleted file mode 100644 index 658bbb52..00000000 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.h +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once -#include "../CommonUtilities/win/WinAPI.h" -#include "Middleware.h" -#include "../Interprocess/source/Interprocess.h" -#include "../Streamer/StreamClient.h" -#include -#include -#include -#include "../CommonUtilities/Hash.h" -#include "../CommonUtilities/Math.h" -#include "FrameTimingData.h" -#include "../IntelPresentMon/CommonUtilities/mc/SwapChainState.h" - -namespace pmapi::intro -{ - class Root; -} - -namespace pmon::mid -{ - class FrameMetricsSource; - - class ConcreteMiddleware : public Middleware - { - public: - ConcreteMiddleware(std::optional pipeNameOverride = {}); - ~ConcreteMiddleware() override; - const PM_INTROSPECTION_ROOT* GetIntrospectionData() override; - void FreeIntrospectionData(const PM_INTROSPECTION_ROOT* pRoot) override; - PM_STATUS StartStreaming(uint32_t processId) override; - PM_STATUS StartPlaybackTracking(uint32_t processId, bool isBackpressured) override; - PM_STATUS StopStreaming(uint32_t processId) override; - PM_STATUS SetTelemetryPollingPeriod(uint32_t deviceId, uint32_t timeMs) override; - PM_STATUS SetEtwFlushPeriod(std::optional periodMs) override; - PM_STATUS FlushFrames(uint32_t processId) override; - PM_DYNAMIC_QUERY* RegisterDynamicQuery(std::span queryElements, double windowSizeMs, double metricOffsetMs) override; - void FreeDynamicQuery(const PM_DYNAMIC_QUERY* pQuery) override {} - void PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, - uint32_t* numSwapChains, std::optional nowTimestamp = {}) override; - void PollStaticQuery(const PM_QUERY_ELEMENT& element, uint32_t processId, uint8_t* pBlob) override; - PM_FRAME_QUERY* RegisterFrameEventQuery(std::span queryElements, uint32_t& blobSize) override; - void FreeFrameEventQuery(const PM_FRAME_QUERY* pQuery) override; - void ConsumeFrameEvents(const PM_FRAME_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t& numFrames) override; - void StopPlayback() override; - uint32_t StartEtlLogging() override; - std::string FinishEtlLogging(uint32_t etlLogSessionHandle) override; - private: - // functions - const pmapi::intro::Root& GetIntrospectionRoot_(); - FrameMetricsSource& GetFrameMetricSource_(uint32_t pid) const; - // data - // action client connection to service RPC - std::shared_ptr pActionClient; - // ipc shared memory for frame data, telemetry, and introspection - std::unique_ptr pComms; - // cache of marshalled introspection data - std::unique_ptr pIntroRoot; - // Frame metrics sources mapped to process id - std::map> frameMetricsSources; - }; -} diff --git a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp b/IntelPresentMon/PresentMonMiddleware/Middleware.cpp similarity index 83% rename from IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp rename to IntelPresentMon/PresentMonMiddleware/Middleware.cpp index ffcc8892..d42c77df 100644 --- a/IntelPresentMon/PresentMonMiddleware/ConcreteMiddleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/Middleware.cpp @@ -1,6 +1,6 @@ // Copyright (C) 2017-2024 Intel Corporation // SPDX-License-Identifier: MIT -#include "ConcreteMiddleware.h" +#include "Middleware.h" #include #include #include @@ -14,8 +14,6 @@ #include "../PresentMonUtils/QPCUtils.h" #include "../PresentMonAPI2/Internal.h" #include "../PresentMonAPIWrapperCommon/Introspection.h" -// TODO: don't need transfer if we can somehow get the PM_ struct generation working without inheritance -// needed right now because even if we forward declare, we don't have the inheritance info #include "../Interprocess/source/IntrospectionTransfer.h" #include "../Interprocess/source/IntrospectionHelpers.h" #include "../Interprocess/source/IntrospectionCloneAllocators.h" @@ -47,7 +45,7 @@ namespace pmon::mid static const uint64_t kClientFrameDeltaQPCThreshold = 50000000; static constexpr size_t kFrameMetricsPerSwapChainCapacity = 4096u; - ConcreteMiddleware::ConcreteMiddleware(std::optional pipeNameOverride) + Middleware::Middleware(std::optional pipeNameOverride) { const auto pipeName = pipeNameOverride.transform(&std::string::c_str) .value_or(pmon::gid::defaultControlPipeName); @@ -77,21 +75,21 @@ namespace pmon::mid } } - ConcreteMiddleware::~ConcreteMiddleware() = default; + Middleware::~Middleware() = default; - const PM_INTROSPECTION_ROOT* ConcreteMiddleware::GetIntrospectionData() + const PM_INTROSPECTION_ROOT* Middleware::GetIntrospectionData() { // TODO: consider updating cache or otherwise connecting to middleware intro cache here return pComms->GetIntrospectionRoot(); } - void ConcreteMiddleware::FreeIntrospectionData(const PM_INTROSPECTION_ROOT* pRoot) + void Middleware::FreeIntrospectionData(const PM_INTROSPECTION_ROOT* pRoot) { free(const_cast(pRoot)); } // TODO: rename => tracking - PM_STATUS ConcreteMiddleware::StartStreaming(uint32_t targetPid) + PM_STATUS Middleware::StartStreaming(uint32_t targetPid) { try { auto res = pActionClient->DispatchSync(StartTracking::Params{ targetPid }); @@ -112,7 +110,7 @@ namespace pmon::mid return PM_STATUS_SUCCESS; } - PM_STATUS ConcreteMiddleware::StartPlaybackTracking(uint32_t targetPid, bool isBackpressured) + PM_STATUS Middleware::StartPlaybackTracking(uint32_t targetPid, bool isBackpressured) { try { auto res = pActionClient->DispatchSync(StartTracking::Params{ @@ -138,7 +136,7 @@ namespace pmon::mid } // TODO: rename => tracking - PM_STATUS ConcreteMiddleware::StopStreaming(uint32_t targetPid) + PM_STATUS Middleware::StopStreaming(uint32_t targetPid) { try { // TODO: error when not tracking (returns 0 not 1) @@ -155,7 +153,7 @@ namespace pmon::mid return PM_STATUS_SUCCESS; } - const pmapi::intro::Root& mid::ConcreteMiddleware::GetIntrospectionRoot_() + const pmapi::intro::Root& mid::Middleware::GetIntrospectionRoot_() { if (!pIntroRoot) { pmlog_info("Creating and cacheing introspection root object").diag(); @@ -164,7 +162,7 @@ namespace pmon::mid return *pIntroRoot; } - PM_STATUS ConcreteMiddleware::SetTelemetryPollingPeriod(uint32_t deviceId, uint32_t timeMs) + PM_STATUS Middleware::SetTelemetryPollingPeriod(uint32_t deviceId, uint32_t timeMs) { try { // note: deviceId is being ignored for the time being, but might be used in the future @@ -178,7 +176,7 @@ namespace pmon::mid return PM_STATUS_SUCCESS; } - PM_STATUS ConcreteMiddleware::SetEtwFlushPeriod(std::optional periodMs) + PM_STATUS Middleware::SetEtwFlushPeriod(std::optional periodMs) { try { pActionClient->DispatchSync(acts::SetEtwFlushPeriod::Params{ periodMs }); @@ -191,7 +189,7 @@ namespace pmon::mid return PM_STATUS_SUCCESS; } - PM_STATUS ConcreteMiddleware::FlushFrames(uint32_t processId) + PM_STATUS Middleware::FlushFrames(uint32_t processId) { try { if (auto it = frameMetricsSources.find(processId); it != frameMetricsSources.end() && it->second) { @@ -206,7 +204,7 @@ namespace pmon::mid return PM_STATUS_SUCCESS; } - PM_DYNAMIC_QUERY* ConcreteMiddleware::RegisterDynamicQuery(std::span queryElements, + PM_DYNAMIC_QUERY* Middleware::RegisterDynamicQuery(std::span queryElements, double windowSizeMs, double metricOffsetMs) { pmlog_dbg("Registering dynamic query").pmwatch(queryElements.size()).pmwatch(windowSizeMs).pmwatch(metricOffsetMs); @@ -214,7 +212,7 @@ namespace pmon::mid return new PM_DYNAMIC_QUERY{ queryElements, windowSizeMs, metricOffsetMs, qpcPeriod, *pComms, *this }; } - void ConcreteMiddleware::PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, + void Middleware::PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t* numSwapChains, std::optional nowTimestamp) { if (numSwapChains == nullptr) { @@ -245,7 +243,7 @@ namespace pmon::mid *numSwapChains = pQuery->Poll(pBlob, *pComms, now, pFrameSource, processId, maxSwapChains); } - void ConcreteMiddleware::PollStaticQuery(const PM_QUERY_ELEMENT& element, uint32_t processId, uint8_t* pBlob) + void Middleware::PollStaticQuery(const PM_QUERY_ELEMENT& element, uint32_t processId, uint8_t* pBlob) { const ipc::StaticMetricValue value = [&]() { if (element.deviceId == ipc::kSystemDeviceId) { @@ -269,19 +267,19 @@ namespace pmon::mid }, value); } - PM_FRAME_QUERY* mid::ConcreteMiddleware::RegisterFrameEventQuery(std::span queryElements, uint32_t& blobSize) + PM_FRAME_QUERY* mid::Middleware::RegisterFrameEventQuery(std::span queryElements, uint32_t& blobSize) { auto pQuery = new PM_FRAME_QUERY{ queryElements, *this, *pComms, GetIntrospectionRoot_() }; blobSize = (uint32_t)pQuery->GetBlobSize(); return pQuery; } - void mid::ConcreteMiddleware::FreeFrameEventQuery(const PM_FRAME_QUERY* pQuery) + void mid::Middleware::FreeFrameEventQuery(const PM_FRAME_QUERY* pQuery) { delete const_cast(pQuery); } - void mid::ConcreteMiddleware::ConsumeFrameEvents(const PM_FRAME_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t& numFrames) + void mid::Middleware::ConsumeFrameEvents(const PM_FRAME_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t& numFrames) { const auto framesToCopy = numFrames; numFrames = 0; @@ -300,22 +298,22 @@ namespace pmon::mid numFrames = uint32_t(frames.size()); } - void ConcreteMiddleware::StopPlayback() + void Middleware::StopPlayback() { pActionClient->DispatchSync(StopPlayback::Params{}); } - uint32_t ConcreteMiddleware::StartEtlLogging() + uint32_t Middleware::StartEtlLogging() { return pActionClient->DispatchSync(StartEtlLogging::Params{}).etwLogSessionHandle; } - std::string ConcreteMiddleware::FinishEtlLogging(uint32_t etlLogSessionHandle) + std::string Middleware::FinishEtlLogging(uint32_t etlLogSessionHandle) { return pActionClient->DispatchSync(FinishEtlLogging::Params{ etlLogSessionHandle }).etlFilePath; } - FrameMetricsSource& ConcreteMiddleware::GetFrameMetricSource_(uint32_t pid) const + FrameMetricsSource& Middleware::GetFrameMetricSource_(uint32_t pid) const { if (auto it = frameMetricsSources.find(pid); it == frameMetricsSources.end() || it->second == nullptr) { diff --git a/IntelPresentMon/PresentMonMiddleware/Middleware.h b/IntelPresentMon/PresentMonMiddleware/Middleware.h index 9c70e8dd..8d4e8e52 100644 --- a/IntelPresentMon/PresentMonMiddleware/Middleware.h +++ b/IntelPresentMon/PresentMonMiddleware/Middleware.h @@ -1,33 +1,63 @@ #pragma once +#include "../CommonUtilities/win/WinAPI.h" +#include "../Interprocess/source/Interprocess.h" #include "../PresentMonAPI2/PresentMonAPI.h" -#include +#include "../Streamer/StreamClient.h" +#include +#include #include +#include #include +#include "../CommonUtilities/Hash.h" +#include "../CommonUtilities/Math.h" +#include "FrameTimingData.h" +#include "../IntelPresentMon/CommonUtilities/mc/SwapChainState.h" -struct PM_SESSION { virtual ~PM_SESSION() = default; }; +namespace pmapi::intro +{ + class Root; +} namespace pmon::mid { - class Middleware : public PM_SESSION + class FrameMetricsSource; + + class Middleware { public: - virtual const PM_INTROSPECTION_ROOT* GetIntrospectionData() = 0; - virtual void FreeIntrospectionData(const PM_INTROSPECTION_ROOT* pRoot) = 0; - virtual PM_STATUS StartStreaming(uint32_t processId) = 0; - virtual PM_STATUS StartPlaybackTracking(uint32_t processId, bool isBackpressured) = 0; - virtual PM_STATUS StopStreaming(uint32_t processId) = 0; - virtual PM_STATUS SetTelemetryPollingPeriod(uint32_t deviceId, uint32_t timeMs) = 0; - virtual PM_STATUS SetEtwFlushPeriod(std::optional periodMs) = 0; - virtual PM_STATUS FlushFrames(uint32_t processId) = 0; - virtual PM_DYNAMIC_QUERY* RegisterDynamicQuery(std::span queryElements, double windowSizeMs, double metricOffsetMs) = 0; - virtual void FreeDynamicQuery(const PM_DYNAMIC_QUERY* pQuery) = 0; - virtual void PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t* numSwapChains, std::optional nowTimestamp = {}) = 0; - virtual void PollStaticQuery(const PM_QUERY_ELEMENT& element, uint32_t processId, uint8_t* pBlob) = 0; - virtual PM_FRAME_QUERY* RegisterFrameEventQuery(std::span queryElements, uint32_t& blobSize) = 0; - virtual void FreeFrameEventQuery(const PM_FRAME_QUERY* pQuery) = 0; - virtual void ConsumeFrameEvents(const PM_FRAME_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t& numFrames) = 0; - virtual void StopPlayback() = 0; - virtual uint32_t StartEtlLogging() = 0; - virtual std::string FinishEtlLogging(uint32_t etlLogSessionHandle) = 0; + Middleware(std::optional pipeNameOverride = {}); + ~Middleware(); + const PM_INTROSPECTION_ROOT* GetIntrospectionData(); + void FreeIntrospectionData(const PM_INTROSPECTION_ROOT* pRoot); + PM_STATUS StartStreaming(uint32_t processId); + PM_STATUS StartPlaybackTracking(uint32_t processId, bool isBackpressured); + PM_STATUS StopStreaming(uint32_t processId); + PM_STATUS SetTelemetryPollingPeriod(uint32_t deviceId, uint32_t timeMs); + PM_STATUS SetEtwFlushPeriod(std::optional periodMs); + PM_STATUS FlushFrames(uint32_t processId); + PM_DYNAMIC_QUERY* RegisterDynamicQuery(std::span queryElements, double windowSizeMs, double metricOffsetMs); + void FreeDynamicQuery(const PM_DYNAMIC_QUERY* pQuery) { (void)pQuery; } + void PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, + uint32_t* numSwapChains, std::optional nowTimestamp = {}); + void PollStaticQuery(const PM_QUERY_ELEMENT& element, uint32_t processId, uint8_t* pBlob); + PM_FRAME_QUERY* RegisterFrameEventQuery(std::span queryElements, uint32_t& blobSize); + void FreeFrameEventQuery(const PM_FRAME_QUERY* pQuery); + void ConsumeFrameEvents(const PM_FRAME_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t& numFrames); + void StopPlayback(); + uint32_t StartEtlLogging(); + std::string FinishEtlLogging(uint32_t etlLogSessionHandle); + private: + // functions + const pmapi::intro::Root& GetIntrospectionRoot_(); + FrameMetricsSource& GetFrameMetricSource_(uint32_t pid) const; + // data + // action client connection to service RPC + std::shared_ptr pActionClient; + // ipc shared memory for frame data, telemetry, and introspection + std::unique_ptr pComms; + // cache of marshalled introspection data + std::unique_ptr pIntroRoot; + // Frame metrics sources mapped to process id + std::map> frameMetricsSources; }; } diff --git a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj index 67fad3f6..dcfc00e3 100644 --- a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj +++ b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj @@ -15,7 +15,6 @@ - @@ -27,7 +26,7 @@ - + @@ -145,4 +144,4 @@ - \ No newline at end of file + diff --git a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters index e4d6f226..4f8a81e7 100644 --- a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters +++ b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters @@ -18,9 +18,6 @@ Header Files - - Header Files - Header Files @@ -59,7 +56,7 @@ - + Source Files From d426289385d085074e6270a6120c02dfd9e4f835 Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 5 Feb 2026 15:15:46 +0900 Subject: [PATCH 183/205] clean out unused source / includes --- .../PresentMonMiddleware/FrameTimingData.h | 33 ------------------- .../PresentMonMiddleware/Middleware.cpp | 25 +++++--------- .../PresentMonMiddleware/Middleware.h | 4 --- .../PresentMonMiddleware/MockCommon.h | 29 ---------------- .../PresentMonMiddleware.vcxproj | 4 +-- .../PresentMonMiddleware.vcxproj.filters | 8 +---- 6 files changed, 10 insertions(+), 93 deletions(-) delete mode 100644 IntelPresentMon/PresentMonMiddleware/FrameTimingData.h delete mode 100644 IntelPresentMon/PresentMonMiddleware/MockCommon.h diff --git a/IntelPresentMon/PresentMonMiddleware/FrameTimingData.h b/IntelPresentMon/PresentMonMiddleware/FrameTimingData.h deleted file mode 100644 index ac5f0a4f..00000000 --- a/IntelPresentMon/PresentMonMiddleware/FrameTimingData.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -// What the animation error calculation is based on -enum class AnimationErrorSource { - CpuStart, - AppProvider, - PCLatency, -}; - -struct FlipDelayData { - uint64_t flipDelay = 0; - uint64_t displayQpc = 0; -}; - -struct FrameTimingData { - // QPC of the very first AppSimStartTime. This will either be from - // the app provider or the PCL simulation start time. - uint64_t firstAppSimStartTime = 0; - // QPC of the latest AppSimStartTime. This needs to be tracked because - // not every present is guaranteed to have an app provider or - // PCL simulation start time attached to it. Used when calculating - // ms between simulation starts - uint64_t lastAppSimStartTime = 0; - // QPC of the last displayed AppSimStartTime. Similar to lastAppSimStartTime - // but this is only updated when the present is displayed. Used in calculating - // animation error and time. - uint64_t lastDisplayedAppSimStartTime = 0; - uint64_t lastDisplayedAppScreenTime = 0; - AnimationErrorSource animationErrorSource = AnimationErrorSource::CpuStart; - // NVIDIA Flip Delay related data - std::unordered_map flipDelayDataMap{}; - uint32_t lastDisplayedFrameId = 0; -}; \ No newline at end of file diff --git a/IntelPresentMon/PresentMonMiddleware/Middleware.cpp b/IntelPresentMon/PresentMonMiddleware/Middleware.cpp index d42c77df..d1d81c23 100644 --- a/IntelPresentMon/PresentMonMiddleware/Middleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/Middleware.cpp @@ -1,38 +1,29 @@ // Copyright (C) 2017-2024 Intel Corporation // SPDX-License-Identifier: MIT #include "Middleware.h" -#include #include #include #include #include #include -#include -#include #include #include -#include "../PresentMonUtils/QPCUtils.h" -#include "../PresentMonAPI2/Internal.h" -#include "../PresentMonAPIWrapperCommon/Introspection.h" +#include "../CommonUtilities/mt/Thread.h" +#include "../CommonUtilities/log/Log.h" +#include "../CommonUtilities/Qpc.h" #include "../Interprocess/source/IntrospectionTransfer.h" #include "../Interprocess/source/IntrospectionHelpers.h" #include "../Interprocess/source/IntrospectionCloneAllocators.h" #include "../Interprocess/source/SystemDeviceId.h" #include "../Interprocess/source/PmStatusError.h" -#include "DynamicQuery.h" -#include "../ControlLib/PresentMonPowerTelemetry.h" -#include "../ControlLib/CpuTelemetryInfo.h" +#include "../PresentMonAPI2/Internal.h" +#include "../PresentMonAPIWrapperCommon/Introspection.h" #include "../PresentMonService/GlobalIdentifiers.h" -#include "FrameEventQuery.h" #include "FrameMetricsSource.h" -#include "../CommonUtilities/mt/Thread.h" -#include "../CommonUtilities/log/Log.h" -#include "../CommonUtilities/Qpc.h" - -#include "../CommonUtilities/log/GlogShim.h" - -#include "ActionClient.h" +#include "FrameEventQuery.h" +#include "DynamicQuery.h" #include "QueryValidation.h" +#include "ActionClient.h" namespace pmon::mid { diff --git a/IntelPresentMon/PresentMonMiddleware/Middleware.h b/IntelPresentMon/PresentMonMiddleware/Middleware.h index 8d4e8e52..696c9dcd 100644 --- a/IntelPresentMon/PresentMonMiddleware/Middleware.h +++ b/IntelPresentMon/PresentMonMiddleware/Middleware.h @@ -2,15 +2,11 @@ #include "../CommonUtilities/win/WinAPI.h" #include "../Interprocess/source/Interprocess.h" #include "../PresentMonAPI2/PresentMonAPI.h" -#include "../Streamer/StreamClient.h" #include #include #include #include #include -#include "../CommonUtilities/Hash.h" -#include "../CommonUtilities/Math.h" -#include "FrameTimingData.h" #include "../IntelPresentMon/CommonUtilities/mc/SwapChainState.h" namespace pmapi::intro diff --git a/IntelPresentMon/PresentMonMiddleware/MockCommon.h b/IntelPresentMon/PresentMonMiddleware/MockCommon.h deleted file mode 100644 index 230ad50b..00000000 --- a/IntelPresentMon/PresentMonMiddleware/MockCommon.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#include "../Interprocess/source/Interprocess.h" - -namespace pmon::ipc::intro -{ - inline void RegisterMockIntrospectionDevices(ServiceComms& comms) - { - using namespace std::string_literals; - { - CpuTelemetryBitset caps; - caps.set(size_t(CpuTelemetryCapBits::cpu_utilization)); - comms.RegisterCpuDevice(PM_DEVICE_VENDOR_INTEL, "Core i7 4770k"s, caps); - } - { - GpuTelemetryBitset caps; - caps.set(size_t(GpuTelemetryCapBits::gpu_power)); - caps.set(size_t(GpuTelemetryCapBits::fan_speed_0)); - comms.RegisterGpuDevice(PM_DEVICE_VENDOR_INTEL, "Arc 750"s, caps); - } - { - GpuTelemetryBitset caps; - caps.set(size_t(GpuTelemetryCapBits::gpu_power)); - caps.set(size_t(GpuTelemetryCapBits::fan_speed_0)); - caps.set(size_t(GpuTelemetryCapBits::fan_speed_1)); - comms.RegisterGpuDevice(PM_DEVICE_VENDOR_NVIDIA, "GeForce RTX 2080 ti"s, caps); - } - comms.FinalizeGpuDevices(); - } -} \ No newline at end of file diff --git a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj index dcfc00e3..9d620d0c 100644 --- a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj +++ b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj @@ -13,7 +13,6 @@ - @@ -22,7 +21,6 @@ - @@ -144,4 +142,4 @@ - + \ No newline at end of file diff --git a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters index 4f8a81e7..a8691c60 100644 --- a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters +++ b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj.filters @@ -18,9 +18,6 @@ Header Files - - Header Files - Header Files @@ -42,9 +39,6 @@ Header Files - - Header Files - Header Files @@ -84,4 +78,4 @@ Source Files - + \ No newline at end of file From 93dfcccd2e1e69b71f306e5b0ea40c6ba1a719da Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 5 Feb 2026 15:42:24 +0900 Subject: [PATCH 184/205] unify error handling in middleware --- .../PresentMonAPI2/PresentMonAPI.cpp | 24 ++-- .../PresentMonMiddleware/Middleware.cpp | 131 +++++------------- .../PresentMonMiddleware/Middleware.h | 12 +- 3 files changed, 56 insertions(+), 111 deletions(-) diff --git a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp index d1fac864..7672f4b6 100644 --- a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp +++ b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp @@ -161,9 +161,8 @@ PRESENTMON_API2_EXPORT PM_STATUS pmCloseSession(PM_SESSION_HANDLE handle) PRESENTMON_API2_EXPORT PM_STATUS pmStartTrackingProcess(PM_SESSION_HANDLE handle, uint32_t processId) { try { - // TODO: consider tracking resource usage for process tracking to validate Start/Stop pairing - // TODO: middleware (StartStreaming) should not return status codes - return LookupMiddleware_(handle).StartStreaming(processId); + LookupMiddleware_(handle).StartStreaming(processId); + return PM_STATUS_SUCCESS; } catch (...) { const auto code = util::GeneratePmStatus(); @@ -175,9 +174,8 @@ PRESENTMON_API2_EXPORT PM_STATUS pmStartTrackingProcess(PM_SESSION_HANDLE handle PRESENTMON_API2_EXPORT PM_STATUS pmStartPlaybackTracking(PM_SESSION_HANDLE handle, uint32_t processId, uint32_t isBackpressured) { try { - // TODO: consider tracking resource usage for process tracking to validate Start/Stop pairing - // TODO: middleware (StartStreaming) should not return status codes - return LookupMiddleware_(handle).StartPlaybackTracking(processId, isBackpressured != 0); + LookupMiddleware_(handle).StartPlaybackTracking(processId, isBackpressured != 0); + return PM_STATUS_SUCCESS; } catch (...) { const auto code = util::GeneratePmStatus(); @@ -189,9 +187,8 @@ PRESENTMON_API2_EXPORT PM_STATUS pmStartPlaybackTracking(PM_SESSION_HANDLE handl PRESENTMON_API2_EXPORT PM_STATUS pmStopTrackingProcess(PM_SESSION_HANDLE handle, uint32_t processId) { try { - // TODO: consider tracking resource usage for process tracking to validate Start/Stop pairing - // TODO: middleware should not return status codes - return LookupMiddleware_(handle).StopStreaming(processId); + LookupMiddleware_(handle).StopStreaming(processId); + return PM_STATUS_SUCCESS; } catch (...) { const auto code = util::GeneratePmStatus(); @@ -248,7 +245,8 @@ PRESENTMON_API2_EXPORT PM_STATUS pmFreeIntrospectionRoot(const PM_INTROSPECTION_ PRESENTMON_API2_EXPORT PM_STATUS pmSetTelemetryPollingPeriod(PM_SESSION_HANDLE handle, uint32_t deviceId, uint32_t timeMs) { try { - return LookupMiddleware_(handle).SetTelemetryPollingPeriod(deviceId, timeMs); + LookupMiddleware_(handle).SetTelemetryPollingPeriod(deviceId, timeMs); + return PM_STATUS_SUCCESS; } catch (...) { const auto code = util::GeneratePmStatus(); @@ -260,7 +258,8 @@ PRESENTMON_API2_EXPORT PM_STATUS pmSetTelemetryPollingPeriod(PM_SESSION_HANDLE h PRESENTMON_API2_EXPORT PM_STATUS pmSetEtwFlushPeriod(PM_SESSION_HANDLE handle, uint32_t periodMs) { try { - return LookupMiddleware_(handle).SetEtwFlushPeriod(periodMs ? std::optional{ periodMs } : std::nullopt); + LookupMiddleware_(handle).SetEtwFlushPeriod(periodMs ? std::optional{ periodMs } : std::nullopt); + return PM_STATUS_SUCCESS; } catch (...) { const auto code = util::GeneratePmStatus(); @@ -272,7 +271,8 @@ PRESENTMON_API2_EXPORT PM_STATUS pmSetEtwFlushPeriod(PM_SESSION_HANDLE handle, u PRESENTMON_API2_EXPORT PM_STATUS pmFlushFrames(PM_SESSION_HANDLE handle, uint32_t processId) { try { - return LookupMiddleware_(handle).FlushFrames(processId); + LookupMiddleware_(handle).FlushFrames(processId); + return PM_STATUS_SUCCESS; } catch (...) { const auto code = util::GeneratePmStatus(); diff --git a/IntelPresentMon/PresentMonMiddleware/Middleware.cpp b/IntelPresentMon/PresentMonMiddleware/Middleware.cpp index d1d81c23..8069698a 100644 --- a/IntelPresentMon/PresentMonMiddleware/Middleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/Middleware.cpp @@ -42,28 +42,17 @@ namespace pmon::mid .value_or(pmon::gid::defaultControlPipeName); // Try to open a named pipe to action server; wait for it, if necessary - try { - if (!pipe::DuplexPipe::WaitForAvailability(pipeName, 500)) { - throw std::runtime_error{ "Timeout waiting for service action pipe to become available" }; - } - pActionClient = std::make_shared(pipeName); - } - catch (...) { - pmlog_error(util::ReportException()).diag(); - throw util::Except(PM_STATUS_PIPE_ERROR); + if (!pipe::DuplexPipe::WaitForAvailability(pipeName, 500)) { + throw util::Except(PM_STATUS_PIPE_ERROR, + "Timeout waiting for service action pipe to become available"); } + pActionClient = std::make_shared(pipeName); // connect to the shm server pComms = ipc::MakeMiddlewareComms(pActionClient->GetShmPrefix(), pActionClient->GetShmSalt()); // Get and cache the introspection data - try { - auto& ispec = GetIntrospectionRoot_(); - } - catch (...) { - pmlog_error(ReportException("Problem acquiring introspection data")); - throw; - } + (void)GetIntrospectionRoot_(); } Middleware::~Middleware() = default; @@ -80,68 +69,48 @@ namespace pmon::mid } // TODO: rename => tracking - PM_STATUS Middleware::StartStreaming(uint32_t targetPid) + void Middleware::StartStreaming(uint32_t targetPid) { - try { - auto res = pActionClient->DispatchSync(StartTracking::Params{ targetPid }); - // TODO: error when already tracking - auto sourceIter = frameMetricsSources.find(targetPid); - if (sourceIter == frameMetricsSources.end()) { - frameMetricsSources.emplace(targetPid, - std::make_unique(*pComms, targetPid, kFrameMetricsPerSwapChainCapacity)); - } - } - catch (...) { - const auto code = util::GeneratePmStatus(); - pmlog_error(util::ReportException()).code(code).diag(); - return code; + if (frameMetricsSources.contains(targetPid)) { + throw util::Except(PM_STATUS_ALREADY_TRACKING_PROCESS, + std::format("Process [{}] is already being tracked", targetPid)); } + pActionClient->DispatchSync(StartTracking::Params{ targetPid }); + frameMetricsSources.emplace(targetPid, + std::make_unique(*pComms, targetPid, kFrameMetricsPerSwapChainCapacity)); pmlog_info(std::format("Started tracking pid [{}]", targetPid)).diag(); - return PM_STATUS_SUCCESS; } - PM_STATUS Middleware::StartPlaybackTracking(uint32_t targetPid, bool isBackpressured) + void Middleware::StartPlaybackTracking(uint32_t targetPid, bool isBackpressured) { - try { - auto res = pActionClient->DispatchSync(StartTracking::Params{ - .targetPid = targetPid, - .isPlayback = true, - .isBackpressured = isBackpressured - }); - // TODO: error when already tracking - auto sourceIter = frameMetricsSources.find(targetPid); - if (sourceIter == frameMetricsSources.end()) { - frameMetricsSources.emplace(targetPid, - std::make_unique(*pComms, targetPid, kFrameMetricsPerSwapChainCapacity)); - } - } - catch (...) { - const auto code = util::GeneratePmStatus(); - pmlog_error(util::ReportException()).code(code).diag(); - return code; + if (frameMetricsSources.contains(targetPid)) { + throw util::Except(PM_STATUS_ALREADY_TRACKING_PROCESS, + std::format("Process [{}] is already being tracked", targetPid)); } + pActionClient->DispatchSync(StartTracking::Params{ + .targetPid = targetPid, + .isPlayback = true, + .isBackpressured = isBackpressured + }); + frameMetricsSources.emplace(targetPid, + std::make_unique(*pComms, targetPid, kFrameMetricsPerSwapChainCapacity)); pmlog_info(std::format("Started playback tracking pid [{}]", targetPid)).diag(); - return PM_STATUS_SUCCESS; } // TODO: rename => tracking - PM_STATUS Middleware::StopStreaming(uint32_t targetPid) + void Middleware::StopStreaming(uint32_t targetPid) { - try { - // TODO: error when not tracking (returns 0 not 1) - frameMetricsSources.erase(targetPid); - pActionClient->DispatchSync(StopTracking::Params{ targetPid }); - } - catch (...) { - const auto code = util::GeneratePmStatus(); - pmlog_error(util::ReportException()).code(code).diag(); - return code; + auto it = frameMetricsSources.find(targetPid); + if (it == frameMetricsSources.end()) { + throw util::Except(PM_STATUS_INVALID_PID, + std::format("Process [{}] is not currently being tracked", targetPid)); } + pActionClient->DispatchSync(StopTracking::Params{ targetPid }); + frameMetricsSources.erase(it); pmlog_info(std::format("Stopped tracking pid [{}]", targetPid)).diag(); - return PM_STATUS_SUCCESS; } const pmapi::intro::Root& mid::Middleware::GetIntrospectionRoot_() @@ -153,46 +122,22 @@ namespace pmon::mid return *pIntroRoot; } - PM_STATUS Middleware::SetTelemetryPollingPeriod(uint32_t deviceId, uint32_t timeMs) + void Middleware::SetTelemetryPollingPeriod(uint32_t deviceId, uint32_t timeMs) { - try { - // note: deviceId is being ignored for the time being, but might be used in the future - pActionClient->DispatchSync(SetTelemetryPeriod::Params{ timeMs }); - } - catch (...) { - const auto code = util::GeneratePmStatus(); - pmlog_error(util::ReportException()).code(code).diag(); - return code; - } - return PM_STATUS_SUCCESS; + // note: deviceId is being ignored for the time being, but might be used in the future + pActionClient->DispatchSync(SetTelemetryPeriod::Params{ timeMs }); } - PM_STATUS Middleware::SetEtwFlushPeriod(std::optional periodMs) + void Middleware::SetEtwFlushPeriod(std::optional periodMs) { - try { - pActionClient->DispatchSync(acts::SetEtwFlushPeriod::Params{ periodMs }); - } - catch (...) { - const auto code = util::GeneratePmStatus(); - pmlog_error(util::ReportException()).code(code).diag(); - return code; - } - return PM_STATUS_SUCCESS; + pActionClient->DispatchSync(acts::SetEtwFlushPeriod::Params{ periodMs }); } - PM_STATUS Middleware::FlushFrames(uint32_t processId) + void Middleware::FlushFrames(uint32_t processId) { - try { - if (auto it = frameMetricsSources.find(processId); it != frameMetricsSources.end() && it->second) { - it->second->Flush(); - } - } - catch (...) { - const auto code = util::GeneratePmStatus(); - pmlog_error(util::ReportException()).code(code).diag(); - return code; + if (auto it = frameMetricsSources.find(processId); it != frameMetricsSources.end() && it->second) { + it->second->Flush(); } - return PM_STATUS_SUCCESS; } PM_DYNAMIC_QUERY* Middleware::RegisterDynamicQuery(std::span queryElements, diff --git a/IntelPresentMon/PresentMonMiddleware/Middleware.h b/IntelPresentMon/PresentMonMiddleware/Middleware.h index 696c9dcd..254e0f41 100644 --- a/IntelPresentMon/PresentMonMiddleware/Middleware.h +++ b/IntelPresentMon/PresentMonMiddleware/Middleware.h @@ -25,12 +25,12 @@ namespace pmon::mid ~Middleware(); const PM_INTROSPECTION_ROOT* GetIntrospectionData(); void FreeIntrospectionData(const PM_INTROSPECTION_ROOT* pRoot); - PM_STATUS StartStreaming(uint32_t processId); - PM_STATUS StartPlaybackTracking(uint32_t processId, bool isBackpressured); - PM_STATUS StopStreaming(uint32_t processId); - PM_STATUS SetTelemetryPollingPeriod(uint32_t deviceId, uint32_t timeMs); - PM_STATUS SetEtwFlushPeriod(std::optional periodMs); - PM_STATUS FlushFrames(uint32_t processId); + void StartStreaming(uint32_t processId); + void StartPlaybackTracking(uint32_t processId, bool isBackpressured); + void StopStreaming(uint32_t processId); + void SetTelemetryPollingPeriod(uint32_t deviceId, uint32_t timeMs); + void SetEtwFlushPeriod(std::optional periodMs); + void FlushFrames(uint32_t processId); PM_DYNAMIC_QUERY* RegisterDynamicQuery(std::span queryElements, double windowSizeMs, double metricOffsetMs); void FreeDynamicQuery(const PM_DYNAMIC_QUERY* pQuery) { (void)pQuery; } void PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, From ea92851415ec8b2969039811eaba04c7476020fe Mon Sep 17 00:00:00 2001 From: Chili Date: Thu, 5 Feb 2026 16:02:12 +0900 Subject: [PATCH 185/205] middleware rename refactor align with api names --- .../PresentMonAPI2/PresentMonAPI.cpp | 4 +- .../PresentMonMiddleware/Middleware.cpp | 72 +++++++++---------- .../PresentMonMiddleware/Middleware.h | 12 ++-- 3 files changed, 42 insertions(+), 46 deletions(-) diff --git a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp index 7672f4b6..fe2c6758 100644 --- a/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp +++ b/IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp @@ -161,7 +161,7 @@ PRESENTMON_API2_EXPORT PM_STATUS pmCloseSession(PM_SESSION_HANDLE handle) PRESENTMON_API2_EXPORT PM_STATUS pmStartTrackingProcess(PM_SESSION_HANDLE handle, uint32_t processId) { try { - LookupMiddleware_(handle).StartStreaming(processId); + LookupMiddleware_(handle).StartTracking(processId); return PM_STATUS_SUCCESS; } catch (...) { @@ -187,7 +187,7 @@ PRESENTMON_API2_EXPORT PM_STATUS pmStartPlaybackTracking(PM_SESSION_HANDLE handl PRESENTMON_API2_EXPORT PM_STATUS pmStopTrackingProcess(PM_SESSION_HANDLE handle, uint32_t processId) { try { - LookupMiddleware_(handle).StopStreaming(processId); + LookupMiddleware_(handle).StopTracking(processId); return PM_STATUS_SUCCESS; } catch (...) { diff --git a/IntelPresentMon/PresentMonMiddleware/Middleware.cpp b/IntelPresentMon/PresentMonMiddleware/Middleware.cpp index 8069698a..a4c169bf 100644 --- a/IntelPresentMon/PresentMonMiddleware/Middleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/Middleware.cpp @@ -32,8 +32,6 @@ namespace pmon::mid namespace rn = std::ranges; namespace vi = std::views; - static const uint32_t kMaxRespBufferSize = 4096; - static const uint64_t kClientFrameDeltaQPCThreshold = 50000000; static constexpr size_t kFrameMetricsPerSwapChainCapacity = 4096u; Middleware::Middleware(std::optional pipeNameOverride) @@ -46,10 +44,10 @@ namespace pmon::mid throw util::Except(PM_STATUS_PIPE_ERROR, "Timeout waiting for service action pipe to become available"); } - pActionClient = std::make_shared(pipeName); + pActionClient_ = std::make_shared(pipeName); // connect to the shm server - pComms = ipc::MakeMiddlewareComms(pActionClient->GetShmPrefix(), pActionClient->GetShmSalt()); + pComms_ = ipc::MakeMiddlewareComms(pActionClient_->GetShmPrefix(), pActionClient_->GetShmSalt()); // Get and cache the introspection data (void)GetIntrospectionRoot_(); @@ -60,7 +58,7 @@ namespace pmon::mid const PM_INTROSPECTION_ROOT* Middleware::GetIntrospectionData() { // TODO: consider updating cache or otherwise connecting to middleware intro cache here - return pComms->GetIntrospectionRoot(); + return pComms_->GetIntrospectionRoot(); } void Middleware::FreeIntrospectionData(const PM_INTROSPECTION_ROOT* pRoot) @@ -68,74 +66,72 @@ namespace pmon::mid free(const_cast(pRoot)); } - // TODO: rename => tracking - void Middleware::StartStreaming(uint32_t targetPid) + void Middleware::StartTracking(uint32_t targetPid) { - if (frameMetricsSources.contains(targetPid)) { + if (frameMetricsSources_.contains(targetPid)) { throw util::Except(PM_STATUS_ALREADY_TRACKING_PROCESS, std::format("Process [{}] is already being tracked", targetPid)); } - pActionClient->DispatchSync(StartTracking::Params{ targetPid }); - frameMetricsSources.emplace(targetPid, - std::make_unique(*pComms, targetPid, kFrameMetricsPerSwapChainCapacity)); + pActionClient_->DispatchSync(StartTracking::Params{ targetPid }); + frameMetricsSources_.emplace(targetPid, + std::make_unique(*pComms_, targetPid, kFrameMetricsPerSwapChainCapacity)); pmlog_info(std::format("Started tracking pid [{}]", targetPid)).diag(); } void Middleware::StartPlaybackTracking(uint32_t targetPid, bool isBackpressured) { - if (frameMetricsSources.contains(targetPid)) { + if (frameMetricsSources_.contains(targetPid)) { throw util::Except(PM_STATUS_ALREADY_TRACKING_PROCESS, std::format("Process [{}] is already being tracked", targetPid)); } - pActionClient->DispatchSync(StartTracking::Params{ + pActionClient_->DispatchSync(StartTracking::Params{ .targetPid = targetPid, .isPlayback = true, .isBackpressured = isBackpressured }); - frameMetricsSources.emplace(targetPid, - std::make_unique(*pComms, targetPid, kFrameMetricsPerSwapChainCapacity)); + frameMetricsSources_.emplace(targetPid, + std::make_unique(*pComms_, targetPid, kFrameMetricsPerSwapChainCapacity)); pmlog_info(std::format("Started playback tracking pid [{}]", targetPid)).diag(); } - // TODO: rename => tracking - void Middleware::StopStreaming(uint32_t targetPid) + void Middleware::StopTracking(uint32_t targetPid) { - auto it = frameMetricsSources.find(targetPid); - if (it == frameMetricsSources.end()) { + auto it = frameMetricsSources_.find(targetPid); + if (it == frameMetricsSources_.end()) { throw util::Except(PM_STATUS_INVALID_PID, std::format("Process [{}] is not currently being tracked", targetPid)); } - pActionClient->DispatchSync(StopTracking::Params{ targetPid }); - frameMetricsSources.erase(it); + pActionClient_->DispatchSync(StopTracking::Params{ targetPid }); + frameMetricsSources_.erase(it); pmlog_info(std::format("Stopped tracking pid [{}]", targetPid)).diag(); } const pmapi::intro::Root& mid::Middleware::GetIntrospectionRoot_() { - if (!pIntroRoot) { + if (!pIntroRoot_) { pmlog_info("Creating and cacheing introspection root object").diag(); - pIntroRoot = std::make_unique(GetIntrospectionData(), [this](auto p){FreeIntrospectionData(p);}); + pIntroRoot_ = std::make_unique(GetIntrospectionData(), [this](auto p){FreeIntrospectionData(p);}); } - return *pIntroRoot; + return *pIntroRoot_; } void Middleware::SetTelemetryPollingPeriod(uint32_t deviceId, uint32_t timeMs) { // note: deviceId is being ignored for the time being, but might be used in the future - pActionClient->DispatchSync(SetTelemetryPeriod::Params{ timeMs }); + pActionClient_->DispatchSync(SetTelemetryPeriod::Params{ timeMs }); } void Middleware::SetEtwFlushPeriod(std::optional periodMs) { - pActionClient->DispatchSync(acts::SetEtwFlushPeriod::Params{ periodMs }); + pActionClient_->DispatchSync(acts::SetEtwFlushPeriod::Params{ periodMs }); } void Middleware::FlushFrames(uint32_t processId) { - if (auto it = frameMetricsSources.find(processId); it != frameMetricsSources.end() && it->second) { + if (auto it = frameMetricsSources_.find(processId); it != frameMetricsSources_.end() && it->second) { it->second->Flush(); } } @@ -145,7 +141,7 @@ namespace pmon::mid { pmlog_dbg("Registering dynamic query").pmwatch(queryElements.size()).pmwatch(windowSizeMs).pmwatch(metricOffsetMs); const auto qpcPeriod = util::GetTimestampPeriodSeconds(); - return new PM_DYNAMIC_QUERY{ queryElements, windowSizeMs, metricOffsetMs, qpcPeriod, *pComms, *this }; + return new PM_DYNAMIC_QUERY{ queryElements, windowSizeMs, metricOffsetMs, qpcPeriod, *pComms_, *this }; } void Middleware::PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, @@ -176,19 +172,19 @@ namespace pmon::mid } const auto now = nowTimestamp.value_or((uint64_t)util::GetCurrentTimestamp()); - *numSwapChains = pQuery->Poll(pBlob, *pComms, now, pFrameSource, processId, maxSwapChains); + *numSwapChains = pQuery->Poll(pBlob, *pComms_, now, pFrameSource, processId, maxSwapChains); } void Middleware::PollStaticQuery(const PM_QUERY_ELEMENT& element, uint32_t processId, uint8_t* pBlob) { const ipc::StaticMetricValue value = [&]() { if (element.deviceId == ipc::kSystemDeviceId) { - return pComms->GetSystemDataStore().FindStaticMetric(element.metric); + return pComms_->GetSystemDataStore().FindStaticMetric(element.metric); } if (element.deviceId == ipc::kUniversalDeviceId) { - return pComms->GetFrameDataStore(processId).FindStaticMetric(element.metric); + return pComms_->GetFrameDataStore(processId).FindStaticMetric(element.metric); } - return pComms->GetGpuDataStore(element.deviceId).FindStaticMetric(element.metric); + return pComms_->GetGpuDataStore(element.deviceId).FindStaticMetric(element.metric); }(); std::visit([&](auto&& v) { @@ -205,7 +201,7 @@ namespace pmon::mid PM_FRAME_QUERY* mid::Middleware::RegisterFrameEventQuery(std::span queryElements, uint32_t& blobSize) { - auto pQuery = new PM_FRAME_QUERY{ queryElements, *this, *pComms, GetIntrospectionRoot_() }; + auto pQuery = new PM_FRAME_QUERY{ queryElements, *this, *pComms_, GetIntrospectionRoot_() }; blobSize = (uint32_t)pQuery->GetBlobSize(); return pQuery; } @@ -236,23 +232,23 @@ namespace pmon::mid void Middleware::StopPlayback() { - pActionClient->DispatchSync(StopPlayback::Params{}); + pActionClient_->DispatchSync(StopPlayback::Params{}); } uint32_t Middleware::StartEtlLogging() { - return pActionClient->DispatchSync(StartEtlLogging::Params{}).etwLogSessionHandle; + return pActionClient_->DispatchSync(StartEtlLogging::Params{}).etwLogSessionHandle; } std::string Middleware::FinishEtlLogging(uint32_t etlLogSessionHandle) { - return pActionClient->DispatchSync(FinishEtlLogging::Params{ etlLogSessionHandle }).etlFilePath; + return pActionClient_->DispatchSync(FinishEtlLogging::Params{ etlLogSessionHandle }).etlFilePath; } FrameMetricsSource& Middleware::GetFrameMetricSource_(uint32_t pid) const { - if (auto it = frameMetricsSources.find(pid); - it == frameMetricsSources.end() || it->second == nullptr) { + if (auto it = frameMetricsSources_.find(pid); + it == frameMetricsSources_.end() || it->second == nullptr) { pmlog_error("Frame metrics source for process {} doesn't exist. Call pmStartTracking to initialize the client.").diag(); throw Except(std::format("Failed to find frame metrics source for pid {}", pid)); } diff --git a/IntelPresentMon/PresentMonMiddleware/Middleware.h b/IntelPresentMon/PresentMonMiddleware/Middleware.h index 254e0f41..5bd7f171 100644 --- a/IntelPresentMon/PresentMonMiddleware/Middleware.h +++ b/IntelPresentMon/PresentMonMiddleware/Middleware.h @@ -25,9 +25,9 @@ namespace pmon::mid ~Middleware(); const PM_INTROSPECTION_ROOT* GetIntrospectionData(); void FreeIntrospectionData(const PM_INTROSPECTION_ROOT* pRoot); - void StartStreaming(uint32_t processId); + void StartTracking(uint32_t processId); void StartPlaybackTracking(uint32_t processId, bool isBackpressured); - void StopStreaming(uint32_t processId); + void StopTracking(uint32_t processId); void SetTelemetryPollingPeriod(uint32_t deviceId, uint32_t timeMs); void SetEtwFlushPeriod(std::optional periodMs); void FlushFrames(uint32_t processId); @@ -48,12 +48,12 @@ namespace pmon::mid FrameMetricsSource& GetFrameMetricSource_(uint32_t pid) const; // data // action client connection to service RPC - std::shared_ptr pActionClient; + std::shared_ptr pActionClient_; // ipc shared memory for frame data, telemetry, and introspection - std::unique_ptr pComms; + std::unique_ptr pComms_; // cache of marshalled introspection data - std::unique_ptr pIntroRoot; + std::unique_ptr pIntroRoot_; // Frame metrics sources mapped to process id - std::map> frameMetricsSources; + std::map> frameMetricsSources_; }; } From fdd237b1845a6623c6a8a89c802bebdb22b2facb Mon Sep 17 00:00:00 2001 From: Chili Date: Fri, 6 Feb 2026 13:09:15 +0900 Subject: [PATCH 186/205] rip out streamer+associated projects and replace pid state tracking --- .../CommonUtilities/mc/MetricsCalculator.cpp | 1 - .../mc/MetricsCalculatorAnimation.cpp | 1 - .../mc/MetricsCalculatorCpuGpu.cpp | 1 - .../mc/MetricsCalculatorDisplay.cpp | 1 - .../mc/MetricsCalculatorInput.cpp | 1 - .../mc/MetricsCalculatorInstrumented.cpp | 1 - .../CommonUtilities/mc/MetricsTypes.cpp | 122 +- .../CommonUtilities/mc/MetricsTypes.h | 10 +- IntelPresentMon/CommonUtilities/win/WinAPI.h | 6 +- IntelPresentMon/ControlLib/ControlLib.vcxproj | 4 +- .../PresentMonAPI2Tests/EtlLoggerTests.cpp | 7 +- .../InterimBroadcasterTests.cpp | 11 +- .../PresentMonAPI2Tests/MultiClientTests.cpp | 25 +- .../PresentMonAPI2Tests/TestCommands.h | 9 +- .../PresentMonMiddleware/DynamicStat.cpp | 3 +- .../PresentMonMiddleware/MetricBinding.cpp | 3 +- .../PresentMonMiddleware.vcxproj | 6 - .../ActionExecutionContext.cpp | 25 +- .../ActionExecutionContext.h | 10 +- .../MockPresentMonSession.cpp | 178 +-- .../PresentMonService/MockPresentMonSession.h | 17 +- .../PresentMonService/PMMainThread.cpp | 7 +- .../PresentMonService/PresentMon.cpp | 10 +- .../PresentMonService/PresentMon.h | 10 +- .../PresentMonService.vcxproj | 7 +- .../PresentMonService/PresentMonSession.cpp | 73 +- .../PresentMonService/PresentMonSession.h | 38 +- .../RealtimePresentMonSession.cpp | 243 +--- .../RealtimePresentMonSession.h | 14 +- .../PresentMonService/acts/StartTracking.h | 42 +- .../PresentMonService/acts/StopTracking.h | 12 +- .../PresentMonUtils/LegacyAPIDefines.h | 155 -- .../PresentMonUtils/PresentDataUtils.h | 74 - .../PresentMonUtils/PresentMonUtils.vcxproj | 118 -- IntelPresentMon/PresentMonUtils/QPCUtils.cpp | 30 - IntelPresentMon/PresentMonUtils/QPCUtils.h | 16 - .../PresentMonUtils/StreamFormat.h | 172 --- .../PresentMonUtils/StringUtils.cpp | 18 - IntelPresentMon/PresentMonUtils/StringUtils.h | 4 - .../SampleStreamerClient/Logging.cpp | 11 - .../SampleStreamerClient.cpp | 58 - .../SampleStreamerClient.vcxproj | 116 -- .../SampleStreamerClient.vcxproj.filters | 7 - .../Streamer/NamedSharedMemory.cpp | 364 ----- IntelPresentMon/Streamer/NamedSharedMemory.h | 87 -- IntelPresentMon/Streamer/StreamClient.cpp | 785 ----------- IntelPresentMon/Streamer/StreamClient.h | 76 - IntelPresentMon/Streamer/Streamer.cpp | 480 ------- IntelPresentMon/Streamer/Streamer.h | 82 -- IntelPresentMon/Streamer/Streamer.vcxproj | 133 -- .../Streamer/Streamer.vcxproj.filters | 23 - IntelPresentMon/ULT/MemBufferTests.cpp | 174 --- IntelPresentMon/ULT/PMApiTests.cpp | 1219 ---------------- IntelPresentMon/ULT/PmFrameGenerator.cpp | 1243 ----------------- IntelPresentMon/ULT/PmFrameGenerator.h | 371 ----- IntelPresentMon/ULT/StreamerTests.cpp | 306 ---- IntelPresentMon/ULT/TelemetryHistory.cpp | 110 -- IntelPresentMon/ULT/ULT.vcxproj | 115 -- IntelPresentMon/ULT/ULT.vcxproj.filters | 15 - IntelPresentMon/ULT/utils.cpp | 20 - IntelPresentMon/ULT/utils.h | 6 - IntelPresentMon/UnitTests/MetricsCore.cpp | 198 +-- PresentData/PresentData.vcxproj | 1 + PresentData/PresentData.vcxproj.filters | 1 + PresentData/PresentEventEnums.hpp | 43 + PresentData/PresentMonTraceConsumer.hpp | 40 +- PresentMon.sln | 56 - 67 files changed, 383 insertions(+), 7242 deletions(-) delete mode 100644 IntelPresentMon/PresentMonUtils/LegacyAPIDefines.h delete mode 100644 IntelPresentMon/PresentMonUtils/PresentDataUtils.h delete mode 100644 IntelPresentMon/PresentMonUtils/PresentMonUtils.vcxproj delete mode 100644 IntelPresentMon/PresentMonUtils/QPCUtils.cpp delete mode 100644 IntelPresentMon/PresentMonUtils/QPCUtils.h delete mode 100644 IntelPresentMon/PresentMonUtils/StreamFormat.h delete mode 100644 IntelPresentMon/PresentMonUtils/StringUtils.cpp delete mode 100644 IntelPresentMon/PresentMonUtils/StringUtils.h delete mode 100644 IntelPresentMon/SampleStreamerClient/Logging.cpp delete mode 100644 IntelPresentMon/SampleStreamerClient/SampleStreamerClient.cpp delete mode 100644 IntelPresentMon/SampleStreamerClient/SampleStreamerClient.vcxproj delete mode 100644 IntelPresentMon/SampleStreamerClient/SampleStreamerClient.vcxproj.filters delete mode 100644 IntelPresentMon/Streamer/NamedSharedMemory.cpp delete mode 100644 IntelPresentMon/Streamer/NamedSharedMemory.h delete mode 100644 IntelPresentMon/Streamer/StreamClient.cpp delete mode 100644 IntelPresentMon/Streamer/StreamClient.h delete mode 100644 IntelPresentMon/Streamer/Streamer.cpp delete mode 100644 IntelPresentMon/Streamer/Streamer.h delete mode 100644 IntelPresentMon/Streamer/Streamer.vcxproj delete mode 100644 IntelPresentMon/Streamer/Streamer.vcxproj.filters delete mode 100644 IntelPresentMon/ULT/MemBufferTests.cpp delete mode 100644 IntelPresentMon/ULT/PMApiTests.cpp delete mode 100644 IntelPresentMon/ULT/PmFrameGenerator.cpp delete mode 100644 IntelPresentMon/ULT/PmFrameGenerator.h delete mode 100644 IntelPresentMon/ULT/StreamerTests.cpp delete mode 100644 IntelPresentMon/ULT/TelemetryHistory.cpp delete mode 100644 IntelPresentMon/ULT/ULT.vcxproj delete mode 100644 IntelPresentMon/ULT/ULT.vcxproj.filters delete mode 100644 IntelPresentMon/ULT/utils.cpp delete mode 100644 IntelPresentMon/ULT/utils.h create mode 100644 PresentData/PresentEventEnums.hpp diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp index b340e7ae..e063e38b 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculator.cpp @@ -4,7 +4,6 @@ #include "MetricsCalculatorInternal.h" #include "../PresentData/PresentMonTraceConsumer.hpp" -#include "../IntelPresentMon/PresentMonUtils/StreamFormat.h" #include "../Math.h" // Layout: internal helpers -> entry points -> metric assembly -> exported helpers diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorAnimation.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorAnimation.cpp index d3ab9a24..0e6e98d1 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorAnimation.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorAnimation.cpp @@ -4,7 +4,6 @@ #include "MetricsCalculatorInternal.h" #include "../PresentData/PresentMonTraceConsumer.hpp" -#include "../IntelPresentMon/PresentMonUtils/StreamFormat.h" #include "../Math.h" namespace pmon::util::metrics diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorCpuGpu.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorCpuGpu.cpp index 6cc08a28..b73325a4 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorCpuGpu.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorCpuGpu.cpp @@ -4,7 +4,6 @@ #include "MetricsCalculatorInternal.h" #include "../PresentData/PresentMonTraceConsumer.hpp" -#include "../IntelPresentMon/PresentMonUtils/StreamFormat.h" #include "../Math.h" namespace pmon::util::metrics diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorDisplay.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorDisplay.cpp index 594d4034..268a8282 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorDisplay.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorDisplay.cpp @@ -4,7 +4,6 @@ #include "MetricsCalculatorInternal.h" #include "../PresentData/PresentMonTraceConsumer.hpp" -#include "../IntelPresentMon/PresentMonUtils/StreamFormat.h" #include "../Math.h" namespace pmon::util::metrics diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorInput.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorInput.cpp index 237a8dd9..63532729 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorInput.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorInput.cpp @@ -4,7 +4,6 @@ #include "MetricsCalculatorInternal.h" #include "../PresentData/PresentMonTraceConsumer.hpp" -#include "../IntelPresentMon/PresentMonUtils/StreamFormat.h" #include "../Math.h" namespace pmon::util::metrics diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorInstrumented.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorInstrumented.cpp index 4833cab8..2b891feb 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorInstrumented.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsCalculatorInstrumented.cpp @@ -4,7 +4,6 @@ #include "MetricsCalculatorInternal.h" #include "../PresentData/PresentMonTraceConsumer.hpp" -#include "../IntelPresentMon/PresentMonUtils/StreamFormat.h" #include "../Math.h" namespace pmon::util::metrics diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp index 5d755ac5..9d0339e4 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.cpp @@ -3,126 +3,20 @@ #include "MetricsTypes.h" #include "../PresentData/PresentMonTraceConsumer.hpp" -#include "../IntelPresentMon/PresentMonUtils/StreamFormat.h" namespace pmon::util::metrics { - FrameData FrameData::CopyFrameData(const PmNsmPresentEvent& p) { - FrameData frame{}; - - frame.runtime = p.Runtime; - frame.presentMode = p.PresentMode; - - frame.presentStartTime = p.PresentStartTime; - frame.readyTime = p.ReadyTime; - frame.timeInPresent = p.TimeInPresent; - frame.gpuStartTime = p.GPUStartTime; - frame.gpuDuration = p.GPUDuration; - frame.gpuVideoDuration = p.GPUVideoDuration; - - frame.appPropagatedPresentStartTime = p.AppPropagatedPresentStartTime; - frame.appPropagatedTimeInPresent = p.AppPropagatedTimeInPresent; - frame.appPropagatedGPUStartTime = p.AppPropagatedGPUStartTime; - frame.appPropagatedReadyTime = p.AppPropagatedReadyTime; - frame.appPropagatedGPUDuration = p.AppPropagatedGPUDuration; - frame.appPropagatedGPUVideoDuration = p.AppPropagatedGPUVideoDuration; - - frame.appSleepStartTime = p.AppSleepStartTime; - frame.appSleepEndTime = p.AppSleepEndTime; - frame.appSimStartTime = p.AppSimStartTime; - frame.appSimEndTime = p.AppSimEndTime; - frame.appRenderSubmitStartTime = p.AppRenderSubmitStartTime; - frame.appRenderSubmitEndTime = p.AppRenderSubmitEndTime; - frame.appPresentStartTime = p.AppPresentStartTime; - frame.appPresentEndTime = p.AppPresentEndTime; - frame.appInputSample = { p.AppInputTime, p.AppInputType }; - - frame.inputTime = p.InputTime; - frame.mouseClickTime = p.MouseClickTime; - - frame.pclSimStartTime = p.PclSimStartTime; - frame.pclInputPingTime = p.PclInputPingTime; - frame.flipDelay = p.FlipDelay; - frame.flipToken = p.FlipToken; - - // Normalize parallel arrays to vector - frame.displayed.Reserve(p.DisplayedCount); - for (size_t i = 0; i < p.DisplayedCount; ++i) { - frame.displayed.EmplaceBack( - p.Displayed_FrameType[i], - p.Displayed_ScreenTime[i]); + FrameData FrameData::CopyFrameData(const std::shared_ptr& p) + { + if (p) { + return CopyFrameData(*p); } - - frame.swapChainAddress = p.SwapChainAddress; - frame.syncInterval = p.SyncInterval; - frame.presentFlags = p.PresentFlags; - - frame.finalState = p.FinalState; - frame.supportsTearing = p.SupportsTearing; - frame.frameId = p.FrameId; - frame.processId = p.ProcessId; - frame.threadId = p.ThreadId; - frame.appFrameId = p.AppFrameId; - frame.pclFrameId = p.PclFrameId; - - return frame; - } - - FrameData FrameData::CopyFrameData(const std::shared_ptr& p) { - FrameData frame{}; - - frame.runtime = p->Runtime; - frame.presentMode = p->PresentMode; - frame.presentStartTime = p->PresentStartTime; - frame.readyTime = p->ReadyTime; - frame.timeInPresent = p->TimeInPresent; - frame.gpuStartTime = p->GPUStartTime; - frame.gpuDuration = p->GPUDuration; - frame.gpuVideoDuration = p->GPUVideoDuration; - - frame.appPropagatedPresentStartTime = p->AppPropagatedPresentStartTime; - frame.appPropagatedTimeInPresent = p->AppPropagatedTimeInPresent; - frame.appPropagatedGPUStartTime = p->AppPropagatedGPUStartTime; - frame.appPropagatedReadyTime = p->AppPropagatedReadyTime; - frame.appPropagatedGPUDuration = p->AppPropagatedGPUDuration; - frame.appPropagatedGPUVideoDuration = p->AppPropagatedGPUVideoDuration; - - frame.appSleepStartTime = p->AppSleepStartTime; - frame.appSleepEndTime = p->AppSleepEndTime; - frame.appSimStartTime = p->AppSimStartTime; - frame.appSimEndTime = p->AppSimEndTime; - frame.appRenderSubmitStartTime = p->AppRenderSubmitStartTime; - frame.appRenderSubmitEndTime = p->AppRenderSubmitEndTime; - frame.appPresentStartTime = p->AppPresentStartTime; - frame.appPresentEndTime = p->AppPresentEndTime; - frame.appInputSample = p->AppInputSample; - - frame.inputTime = p->InputTime; - frame.mouseClickTime = p->MouseClickTime; - - frame.pclSimStartTime = p->PclSimStartTime; - frame.pclInputPingTime = p->PclInputPingTime; - frame.flipDelay = p->FlipDelay; - frame.flipToken = p->FlipToken; - - frame.displayed.Assign(p->Displayed.begin(), p->Displayed.end()); - - frame.swapChainAddress = p->SwapChainAddress; - frame.syncInterval = p->SyncInterval; - frame.presentFlags = p->PresentFlags; - - frame.finalState = p->FinalState; - frame.supportsTearing = p->SupportsTearing; - frame.frameId = p->FrameId; - frame.processId = p->ProcessId; - frame.threadId = p->ThreadId; - frame.appFrameId = p->AppFrameId; - frame.pclFrameId = p->PclFrameId; - - return frame; + pmlog_error("Tried to copy frame data from empty PresentEvent ptr"); + return {}; } - FrameData FrameData::CopyFrameData(const PresentEvent& p) { + FrameData FrameData::CopyFrameData(const PresentEvent& p) + { FrameData frame{}; frame.runtime = p.Runtime; diff --git a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h index 946bad29..e00f1ad1 100644 --- a/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h +++ b/IntelPresentMon/CommonUtilities/mc/MetricsTypes.h @@ -5,15 +5,10 @@ #include #include #include "../cnr/FixedVector.h" +#include "../../../PresentData/PresentEventEnums.hpp" // Forward declarations for external types -enum class Runtime; -enum class PresentMode; -enum class FrameType; // From PresentData -enum class PresentResult; // From PresentData -enum class InputDeviceType; // From PresentData -struct PmNsmPresentEvent; // From PresentMonUtils -struct PresentEvent; // From PresentMonTraceConsumer +struct PresentEvent; // From PresentMonTraceConsumer namespace pmon::util::metrics { @@ -93,7 +88,6 @@ namespace pmon::util::metrics { uint32_t pclFrameId = 0; // Factory Methods - static FrameData CopyFrameData(const PmNsmPresentEvent& p); static FrameData CopyFrameData(const std::shared_ptr& p); static FrameData CopyFrameData(const PresentEvent& p); }; diff --git a/IntelPresentMon/CommonUtilities/win/WinAPI.h b/IntelPresentMon/CommonUtilities/win/WinAPI.h index f91ca947..b9888c02 100644 --- a/IntelPresentMon/CommonUtilities/win/WinAPI.h +++ b/IntelPresentMon/CommonUtilities/win/WinAPI.h @@ -1,9 +1,11 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #define _WIN32_WINNT 0x0603 #include #define WIN32_LEAN_AND_MEAN #define NOMINMAX +#ifndef STRICT #define STRICT -#include \ No newline at end of file +#endif +#include diff --git a/IntelPresentMon/ControlLib/ControlLib.vcxproj b/IntelPresentMon/ControlLib/ControlLib.vcxproj index 71cfe9d3..58776432 100644 --- a/IntelPresentMon/ControlLib/ControlLib.vcxproj +++ b/IntelPresentMon/ControlLib/ControlLib.vcxproj @@ -155,11 +155,11 @@ - + {66e9f6c5-28db-4218-81b9-31e0e146ecc0} - \ No newline at end of file + diff --git a/IntelPresentMon/PresentMonAPI2Tests/EtlLoggerTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/EtlLoggerTests.cpp index 3629324f..2d849820 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/EtlLoggerTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/EtlLoggerTests.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2023 Intel Corporation +// Copyright (C) 2022-2023 Intel Corporation // SPDX-License-Identifier: MIT #include "../CommonUtilities/win/WinAPI.h" #include "CppUnitTest.h" @@ -52,7 +52,8 @@ namespace EtlLoggerTests { // verify initial status const auto status = fixture_.service->QueryStatus(); - Assert::AreEqual(0ull, status.nsmStreamedPids.size()); + Assert::AreEqual(0ull, status.trackedPids.size()); + Assert::AreEqual(0ull, status.frameStorePids.size()); Assert::AreEqual(16u, status.telemetryPeriodMs); Assert::IsTrue((bool)status.etwFlushPeriodMs); Assert::AreEqual(1000u, *status.etwFlushPeriodMs); @@ -106,4 +107,4 @@ namespace EtlLoggerTests Assert::IsTrue(std::filesystem::file_size(csvFilePath) > 10'000); } }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 95f64761..49c534b2 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -100,7 +100,8 @@ namespace InterimBroadcasterTests { // verify initial status const auto status = fixture_.service->QueryStatus(); - Assert::AreEqual(0ull, status.nsmStreamedPids.size()); + Assert::AreEqual(0ull, status.trackedPids.size()); + Assert::AreEqual(0ull, status.frameStorePids.size()); Assert::AreEqual(16u, status.telemetryPeriodMs); Assert::IsTrue((bool)status.etwFlushPeriodMs); Assert::AreEqual(1000u, *status.etwFlushPeriodMs); @@ -767,6 +768,8 @@ namespace InterimBroadcasterTests const auto sta = fixture_.service->QueryStatus(); Assert::AreEqual(1ull, sta.trackedPids.size()); Assert::IsTrue(sta.trackedPids.contains(pres.GetId())); + Assert::AreEqual(1ull, sta.frameStorePids.size()); + Assert::IsTrue(sta.frameStorePids.contains(pres.GetId())); } // stop tracking @@ -776,7 +779,11 @@ namespace InterimBroadcasterTests pComms->CloseFrameDataStore(pres.GetId()); // verify the service not tracking, as expected - Assert::AreEqual(0ull, fixture_.service->QueryStatus().trackedPids.size()); + { + const auto sta = fixture_.service->QueryStatus(); + Assert::AreEqual(0ull, sta.trackedPids.size()); + Assert::AreEqual(0ull, sta.frameStorePids.size()); + } // verify segment can no longer be opened Assert::ExpectException([&] {pComms->OpenFrameDataStore(pres.GetId()); }); diff --git a/IntelPresentMon/PresentMonAPI2Tests/MultiClientTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/MultiClientTests.cpp index e0819c7d..c5fcbdb1 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/MultiClientTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/MultiClientTests.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2023 Intel Corporation +// Copyright (C) 2022-2023 Intel Corporation // SPDX-License-Identifier: MIT #include "../CommonUtilities/win/WinAPI.h" #include "CppUnitTest.h" @@ -51,7 +51,8 @@ namespace MultiClientTests { // verify initial status const auto status = fixture_.service->QueryStatus(); - Assert::AreEqual(0ull, status.nsmStreamedPids.size()); + Assert::AreEqual(0ull, status.trackedPids.size()); + Assert::AreEqual(0ull, status.frameStorePids.size()); Assert::AreEqual(16u, status.telemetryPeriodMs); Assert::IsTrue((bool)status.etwFlushPeriodMs); Assert::AreEqual(1000u, *status.etwFlushPeriodMs); @@ -475,21 +476,24 @@ namespace MultiClientTests // verify tracking status at service { const auto status = fixture_.service->QueryStatus(); - Assert::AreEqual(1ull, status.nsmStreamedPids.size()); + Assert::AreEqual(1ull, status.trackedPids.size()); + Assert::AreEqual(1ull, status.frameStorePids.size()); } // one client quits client1.Quit(); // verify tracking status at service { const auto status = fixture_.service->QueryStatus(); - Assert::AreEqual(1ull, status.nsmStreamedPids.size()); + Assert::AreEqual(1ull, status.trackedPids.size()); + Assert::AreEqual(1ull, status.frameStorePids.size()); } // other client quits client2.Quit(); // verify tracking stopped at service { const auto status = fixture_.service->QueryStatus(); - Assert::AreEqual(0ull, status.nsmStreamedPids.size()); + Assert::AreEqual(0ull, status.trackedPids.size()); + Assert::AreEqual(0ull, status.frameStorePids.size()); } } // verify process untrack (stream stop) when clients die suddenly @@ -508,7 +512,8 @@ namespace MultiClientTests // verify tracking status at service { const auto status = fixture_.service->QueryStatus(); - Assert::AreEqual(1ull, status.nsmStreamedPids.size()); + Assert::AreEqual(1ull, status.trackedPids.size()); + Assert::AreEqual(1ull, status.frameStorePids.size()); } // one client dies client1.Murder(); @@ -516,7 +521,8 @@ namespace MultiClientTests // verify tracking status at service { const auto status = fixture_.service->QueryStatus(); - Assert::AreEqual(1ull, status.nsmStreamedPids.size()); + Assert::AreEqual(1ull, status.trackedPids.size()); + Assert::AreEqual(1ull, status.frameStorePids.size()); } // other client dies client2.Murder(); @@ -524,7 +530,8 @@ namespace MultiClientTests // verify tracking stopped at service { const auto status = fixture_.service->QueryStatus(); - Assert::AreEqual(0ull, status.nsmStreamedPids.size()); + Assert::AreEqual(0ull, status.trackedPids.size()); + Assert::AreEqual(0ull, status.frameStorePids.size()); } } // test a large number of clients running @@ -551,4 +558,4 @@ namespace MultiClientTests } } }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonAPI2Tests/TestCommands.h b/IntelPresentMon/PresentMonAPI2Tests/TestCommands.h index 7ea92572..5664af66 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/TestCommands.h +++ b/IntelPresentMon/PresentMonAPI2Tests/TestCommands.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include #include #include @@ -13,10 +13,9 @@ namespace pmon::test { struct Status { - // old streamer tracking - std::set nsmStreamedPids; // new ipc tracking std::set trackedPids; + std::set frameStorePids; uint32_t activeAdapterId; uint32_t telemetryPeriodMs; std::optional etwFlushPeriodMs; @@ -24,7 +23,7 @@ namespace pmon::test template void serialize(Archive& ar) { - ar(nsmStreamedPids, trackedPids, activeAdapterId, telemetryPeriodMs, etwFlushPeriodMs); + ar(trackedPids, frameStorePids, activeAdapterId, telemetryPeriodMs, etwFlushPeriodMs); } }; } @@ -58,4 +57,4 @@ namespace pmon::test } }; } -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp index 36bd9bb5..288df9aa 100644 --- a/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp +++ b/IntelPresentMon/PresentMonMiddleware/DynamicStat.cpp @@ -1,8 +1,9 @@ #include "DynamicStat.h" #include "DynamicQueryWindow.h" #include "../CommonUtilities/Exception.h" +#include "../CommonUtilities/log/Log.h" #include "../Interprocess/source/PmStatusError.h" -#include "../../PresentData/PresentMonTraceConsumer.hpp" +#include "../../PresentData/PresentEventEnums.hpp" #include #include #include diff --git a/IntelPresentMon/PresentMonMiddleware/MetricBinding.cpp b/IntelPresentMon/PresentMonMiddleware/MetricBinding.cpp index c41fa9e8..18a875e4 100644 --- a/IntelPresentMon/PresentMonMiddleware/MetricBinding.cpp +++ b/IntelPresentMon/PresentMonMiddleware/MetricBinding.cpp @@ -1,4 +1,5 @@ -#include "MetricBinding.h" +#include "../CommonUtilities/win/WinAPI.h" +#include "MetricBinding.h" #include "FrameMetricsSource.h" #include "Middleware.h" #include "../Interprocess/source/Interprocess.h" diff --git a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj index 9d620d0c..e9a294a9 100644 --- a/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj +++ b/IntelPresentMon/PresentMonMiddleware/PresentMonMiddleware.vcxproj @@ -48,12 +48,6 @@ {3a848e5b-a376-4a22-bbac-e9b3c01bd385} - - {66e9f6c5-28db-4218-81b9-31e0e146ecc0} - - - {bf43064b-01f0-4c69-91fb-c2122baf621d} - {c73aa532-e532-4d93-9279-905444653c08} diff --git a/IntelPresentMon/PresentMonService/ActionExecutionContext.cpp b/IntelPresentMon/PresentMonService/ActionExecutionContext.cpp index 3531cf49..fec3cd32 100644 --- a/IntelPresentMon/PresentMonService/ActionExecutionContext.cpp +++ b/IntelPresentMon/PresentMonService/ActionExecutionContext.cpp @@ -3,24 +3,29 @@ #include #include "../Interprocess/source/act/ActionHelper.h" #include +#include namespace pmon::svc::acts { void ActionExecutionContext::Dispose(SessionContextType& stx) { + // etw log trace cleanup auto& etw = pPmon->GetEtwLogger(); for (auto id : stx.etwLogSessionIds) { if (etw.HasActiveSession(id)) { etw.CancelLogSession(id); } } - for (auto&&[pid, pSeg] : stx.trackedPids) { - pPmon->StopStreaming(stx.remotePid, pid); - } + // tracked pids cleanup + stx.trackedPids.clear(); + pPmon->UpdateTracking(GetTrackedPidSet()); + // telemetry period cleanup stx.requestedTelemetryPeriodMs.reset(); UpdateTelemetryPeriod(); + // etw flush cleanup stx.requestedEtwFlushPeriodMs.reset(); UpdateEtwFlushPeriod(); + // metric use cleanup pmlog_verb(pmon::util::log::V::met_use)("Session closing, removing metric usage") .pmwatch(stx.remotePid) .serialize("sessionMetricUsage", stx.metricUsage); @@ -70,4 +75,18 @@ namespace pmon::svc::acts } pPmon->SetDeviceMetricUsage(std::move(deviceMetricUsage)); } + + std::unordered_set ActionExecutionContext::GetTrackedPidSet() const + { + std::unordered_set trackedPids; + if (pSessionMap == nullptr) { + return trackedPids; + } + for (auto const& [sid, session] : *pSessionMap) { + for (auto const& [pid, target] : session.trackedPids) { + trackedPids.emplace(pid); + } + } + return trackedPids; + } } diff --git a/IntelPresentMon/PresentMonService/ActionExecutionContext.h b/IntelPresentMon/PresentMonService/ActionExecutionContext.h index 245d90d8..5d665180 100644 --- a/IntelPresentMon/PresentMonService/ActionExecutionContext.h +++ b/IntelPresentMon/PresentMonService/ActionExecutionContext.h @@ -2,6 +2,7 @@ #include "../Interprocess/source/act/SymmetricActionConnector.h" #include "../Interprocess/source/ShmNamer.h" #include "../CommonUtilities/Hash.h" +#include "../CommonUtilities/win/Handle.h" #include #include #include @@ -76,7 +77,13 @@ namespace pmon::svc::acts uint32_t nextCommandToken = 0; // custom items - std::map> trackedPids; + struct TrackedTarget + { + std::shared_ptr pSegment; + util::win::Handle processHandle; + }; + std::map trackedPids; + // etl recording functionality support std::set etwLogSessionIds; std::optional requestedAdapterId; std::optional requestedTelemetryPeriodMs; @@ -104,5 +111,6 @@ namespace pmon::svc::acts void UpdateTelemetryPeriod() const; void UpdateEtwFlushPeriod() const; void UpdateMetricUsage() const; + std::unordered_set GetTrackedPidSet() const; }; } diff --git a/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp b/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp index a0ec649c..72ddf78e 100644 --- a/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp +++ b/IntelPresentMon/PresentMonService/MockPresentMonSession.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2023 Intel Corporation +// Copyright (C) 2022-2023 Intel Corporation // SPDX-License-Identifier: MIT #include "MockPresentMonSession.h" #include "CliOptions.h" @@ -19,9 +19,7 @@ bool MockPresentMonSession::IsTraceSessionActive() { return session_active_.load(std::memory_order_acquire); } -PM_STATUS MockPresentMonSession::StartStreaming(uint32_t client_process_id, - uint32_t target_process_id, - std::string& nsmFileName) { +PM_STATUS MockPresentMonSession::UpdateTracking(const std::unordered_set& trackedPids) { auto& opt = clio::Options::Get(); @@ -32,14 +30,6 @@ PM_STATUS MockPresentMonSession::StartStreaming(uint32_t client_process_id, return PM_STATUS::PM_STATUS_FAILURE; } - // TODO: hook up all cli options - PM_STATUS status = streamer_.StartStreaming(client_process_id, - target_process_id, nsmFileName, true, opt.pacePlayback, opt.pacePlayback, - !opt.pacePlayback && !opt.disableLegacyBackpressure, true); - if (status != PM_STATUS::PM_STATUS_SUCCESS) { - return status; - } - std::wstring sessionName; if (opt.etwSessionName.AsOptional().has_value()) { sessionName = pmon::util::str::ToWide(opt.etwSessionName.AsOptional().value()); @@ -48,25 +38,54 @@ PM_STATUS MockPresentMonSession::StartStreaming(uint32_t client_process_id, sessionName = kMockEtwSessionName; } - // TODO: hook up all cli options - status = StartTraceSession(target_process_id, *opt.etlTestFile, sessionName, - true, opt.pacePlayback, opt.pacePlayback, !opt.pacePlayback, true); - if (status == PM_STATUS_FAILURE) { - // Unable to start a trace session. Destroy the NSM and - // return status - streamer_.StopStreaming(target_process_id); - return status; + const bool wasActive = HasLiveTargets(); + std::unordered_map previousState; + { + std::lock_guard lock(tracked_processes_mutex_); + previousState = tracked_pid_live_; + } + SyncTrackedPidState(trackedPids); + const bool isActive = HasLiveTargets(); + if (isActive && (!wasActive || !IsTraceSessionActive())) { + uint32_t target_process_id = 0; + if (!trackedPids.empty()) { + target_process_id = *trackedPids.begin(); + } + // TODO: hook up all cli options + auto status = StartTraceSession(target_process_id, *opt.etlTestFile, sessionName, + true, opt.pacePlayback, opt.pacePlayback, !opt.pacePlayback, true); + if (status != PM_STATUS_SUCCESS) { + { + std::lock_guard lock(tracked_processes_mutex_); + tracked_pid_live_ = std::move(previousState); + } + return status; + } + if (evtStreamingStarted_) { + evtStreamingStarted_.Set(); + } } - return PM_STATUS::PM_STATUS_SUCCESS; -} + { + std::lock_guard lock(tracked_processes_mutex_); + for (auto it = started_processes_.begin(); it != started_processes_.end(); ) { + if (tracked_pid_live_.find(*it) == tracked_pid_live_.end()) { + it = started_processes_.erase(it); + } + else { + ++it; + } + } + } -void MockPresentMonSession::StopStreaming(uint32_t client_process_id, - uint32_t target_process_id) { - streamer_.StopStreaming(client_process_id, target_process_id); - if (streamer_.NumActiveStreams() == 0) { + if (!isActive) { + if (evtStreamingStarted_) { + evtStreamingStarted_.Reset(); + } StopTraceSession(); } + + return PM_STATUS::PM_STATUS_SUCCESS; } bool MockPresentMonSession::CheckTraceSessions(bool forceTerminate) { @@ -77,6 +96,7 @@ bool MockPresentMonSession::CheckTraceSessions(bool forceTerminate) { if (forceTerminate) { StopTraceSession(); + ClearTrackedProcesses(); return true; } return false; @@ -199,12 +219,14 @@ void MockPresentMonSession::StopTraceSession() { // PHASE 2: Safe cleanup after threads have finished std::lock_guard lock(session_mutex_); - // Stop all streams - streamer_.StopAllStreams(); + if (evtStreamingStarted_) { + evtStreamingStarted_.Reset(); + } if (pm_consumer_) { pm_consumer_.reset(); } + started_processes_.clear(); } } @@ -235,10 +257,9 @@ void MockPresentMonSession::AddPresents( auto i = *presentEventIndex; // If session is active and mStartTimestamp contains a value, an etl file is being processed. - // Set this value in the streamer to have the correct start time (atomic guard). + // Set this value in the broadcaster bookkeeping to have the correct start time (atomic guard). if (session_active_.load(std::memory_order_acquire)) { assert(trace_session_.mStartTimestamp.QuadPart != 0); - streamer_.SetStartQpc(trace_session_.mStartTimestamp.QuadPart); if (pBroadcaster) { pBroadcaster->SetStartQpc(trace_session_.mStartTimestamp.QuadPart); } @@ -259,49 +280,10 @@ void MockPresentMonSession::AddPresents( break; } - // Look up the swapchain this present belongs to. - auto processInfo = GetProcessInfo(presentEvent->ProcessId); - if (!processInfo->mTargetProcess) { + if (!IsProcessTracked(presentEvent->ProcessId)) { continue; } - PresentMonPowerTelemetryInfo power_telemetry = {}; - std::bitset(GpuTelemetryCapBits::gpu_telemetry_count)> - gpu_telemetry_cap_bits = {}; - if (telemetry_container_) { - auto current_adapters = telemetry_container_->GetPowerTelemetryAdapters(); - if (current_adapters.size() != 0 && - current_telemetry_adapter_id_ < current_adapters.size()) { - auto current_telemetry_adapter = - current_adapters.at(current_telemetry_adapter_id_).get(); - if (auto data = current_telemetry_adapter->GetClosest( - presentEvent->PresentStartTime)) { - power_telemetry = *data; - } - gpu_telemetry_cap_bits = current_telemetry_adapter - ->GetPowerTelemetryCapBits(); - } - } - - CpuTelemetryInfo cpu_telemetry = {}; - std::bitset(CpuTelemetryCapBits::cpu_telemetry_count)> - cpu_telemetry_cap_bits = {}; - if (cpu_) { - if (auto data = cpu_->GetClosest(presentEvent->PresentStartTime)) { - cpu_telemetry = *data; - } - cpu_telemetry_cap_bits = cpu_->GetCpuTelemetryCapBits(); - } - - auto result = processInfo->mSwapChain.emplace( - presentEvent->SwapChainAddress, SwapChainData()); - auto chain = &result.first->second; - if (result.second) { - chain->mPresentHistoryCount = 0; - chain->mLastPresentQPC = 0; - chain->mLastDisplayedPresentQPC = 0; - } - // Remove Repeated flips if they are in Application->Repeated or Repeated->Application sequences. for (size_t i = 0, n = presentEvent->Displayed.size(); i + 1 < n; ) { if (presentEvent->Displayed[i].first == FrameType::Application && @@ -319,25 +301,9 @@ void MockPresentMonSession::AddPresents( } } - // Last producer and last consumer are internal fields - // Remove for public build - // Send data to streamer if we have more than single present event - streamer_.ProcessPresentEvent( - presentEvent.get(), &power_telemetry, &cpu_telemetry, - chain->mLastPresentQPC, chain->mLastDisplayedPresentQPC, - processInfo->mModuleName, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - // timeout set for 1000 ms pBroadcaster->Broadcast(*presentEvent, 1000); - chain->mLastPresentQPC = presentEvent->PresentStartTime; - if (presentEvent->FinalState == PresentResult::Presented) { - chain->mLastDisplayedPresentQPC = presentEvent->Displayed.empty() ? 0 : presentEvent->Displayed[0].second; - } - else if (chain->mLastDisplayedPresentQPC == chain->mLastPresentQPC) { - chain->mLastDisplayedPresentQPC = 0; - } } *presentEventIndex = i; @@ -480,40 +446,16 @@ void MockPresentMonSession::StopOutputThread() { } } -ProcessInfo* MockPresentMonSession::GetProcessInfo(uint32_t processId) { - auto result = processes_.emplace(processId, ProcessInfo()); - auto processInfo = &result.first->second; - auto newProcess = result.second; - - if (newProcess) { - InitProcessInfo(processInfo, processId, INVALID_HANDLE_VALUE, L"MockProcess.exe"); - } - return processInfo; -} - -void MockPresentMonSession::InitProcessInfo(ProcessInfo* processInfo, uint32_t processId, - HANDLE handle, std::wstring const& processName) { - - processInfo->mHandle = handle; - processInfo->mModuleName = processName; - processInfo->mTargetProcess = true; - - target_process_count_ += 1; -} - void MockPresentMonSession::UpdateProcesses( std::vector const& processEvents, std::vector>* terminatedProcesses) { + (void)terminatedProcesses; for (auto const& processEvent : processEvents) { + if (!IsProcessTracked(processEvent.ProcessId)) { + continue; + } if (processEvent.IsStartEvent) { - // This event is a new process starting, the pid should not already be - // in processes_. - auto result = processes_.emplace(processEvent.ProcessId, ProcessInfo()); - auto processInfo = &result.first->second; - auto newProcess = result.second; - if (newProcess) { - InitProcessInfo(processInfo, processEvent.ProcessId, NULL, - processEvent.ImageFileName); + if (started_processes_.insert(processEvent.ProcessId).second) { pBroadcaster->HandleTargetProcessEvent(processEvent); } } @@ -521,4 +463,8 @@ void MockPresentMonSession::UpdateProcesses( } void MockPresentMonSession::HandleTerminatedProcess(uint32_t processId) { -} \ No newline at end of file + MarkProcessExited(processId); + if (!HasLiveTrackedProcesses() && evtStreamingStarted_) { + evtStreamingStarted_.Reset(); + } +} diff --git a/IntelPresentMon/PresentMonService/MockPresentMonSession.h b/IntelPresentMon/PresentMonService/MockPresentMonSession.h index 4dedfc1a..94568689 100644 --- a/IntelPresentMon/PresentMonService/MockPresentMonSession.h +++ b/IntelPresentMon/PresentMonService/MockPresentMonSession.h @@ -1,8 +1,9 @@ -// Copyright (C) 2022-2023 Intel Corporation +// Copyright (C) 2022-2023 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #include "PresentMonSession.h" #include "../CommonUtilities/win/Event.h" +#include using namespace pmon; @@ -16,8 +17,7 @@ class MockPresentMonSession : public PresentMonSession ~MockPresentMonSession() override = default; bool IsTraceSessionActive() override; - PM_STATUS StartStreaming(uint32_t client_process_id, uint32_t target_process_id, std::string& nsmFileName) override; - void StopStreaming(uint32_t client_process_id, uint32_t target_process_id) override; + PM_STATUS UpdateTracking(const std::unordered_set& trackedPids) override; bool CheckTraceSessions(bool forceTerminate) override; HANDLE GetStreamingStartHandle() override; void ResetEtwFlushPeriod() override; @@ -56,9 +56,6 @@ class MockPresentMonSession : public PresentMonSession void Consume(TRACEHANDLE traceHandle); void Output(); - ProcessInfo* GetProcessInfo(uint32_t processId); - void InitProcessInfo(ProcessInfo* processInfo, uint32_t processId, - HANDLE handle, std::wstring const& processName); void UpdateProcesses( std::vector const& processEvents, std::vector>* terminatedProcesses); @@ -75,17 +72,15 @@ class MockPresentMonSession : public PresentMonSession std::atomic quit_output_thread_ = false; std::atomic stop_playback_requested_ = false; - std::unordered_map processes_; + std::unordered_set started_processes_; // Note we only support a single ETL session at a time uint32_t etlProcessId_ = 0; - // TODO: Determine if this actually does anything! - uint32_t target_process_count_ = 0; - // Event for when streaming has started (needed to satisfy virtual interface) pmon::util::win::Event evtStreamingStarted_; mutable std::mutex session_mutex_; + // TODO: evaluate necessity/improvements on this construct std::atomic session_active_{false}; // Lock-free session state for hot path queries -}; \ No newline at end of file +}; diff --git a/IntelPresentMon/PresentMonService/PMMainThread.cpp b/IntelPresentMon/PresentMonService/PMMainThread.cpp index b41034db..dfcf2713 100644 --- a/IntelPresentMon/PresentMonService/PMMainThread.cpp +++ b/IntelPresentMon/PresentMonService/PMMainThread.cpp @@ -7,7 +7,6 @@ #include "PowerTelemetryContainer.h" #include "FrameBroadcaster.h" #include "..\ControlLib\WmiCpu.h" -#include "..\PresentMonUtils\StringUtils.h" #include #include "../Interprocess/source/Interprocess.h" #include "../Interprocess/source/ShmNamer.h" @@ -66,7 +65,7 @@ void EventFlushThreadEntry_(Service* const srv, PresentMon* const pm) waiter.Wait(); // go dormant if there are no active streams left // TODO: GetActiveStreams is not technically thread-safe, reconsider fixing this stuff in Service - if (pm->GetActiveStreams() == 0) { + if (!pm->HasLiveTargets()) { pmlog_dbg("ETW flush loop entering dormancy due to 0 active streams"); break; } @@ -472,7 +471,7 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, // go dormant if there are no active streams left // TODO: consider race condition here if client stops and starts streams rapidly // TODO: remove this legacy branch - if (pm->GetActiveStreams() == 0) { + if (!pm->HasLiveTargets()) { break; } } @@ -581,7 +580,7 @@ void CpuTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, ipc::Ser // go dormant if there are no active streams left // TODO: consider race condition here if client stops and starts streams rapidly // TODO: remove this legacy branch - if (pm->GetActiveStreams() == 0) { + if (!pm->HasLiveTargets()) { break; } } diff --git a/IntelPresentMon/PresentMonService/PresentMon.cpp b/IntelPresentMon/PresentMonService/PresentMon.cpp index 444e1354..55ff7219 100644 --- a/IntelPresentMon/PresentMonService/PresentMon.cpp +++ b/IntelPresentMon/PresentMonService/PresentMon.cpp @@ -33,15 +33,9 @@ PresentMon::~PresentMon() pmlog_dbg("PresentMon object destructor finishing"); } -PM_STATUS PresentMon::StartStreaming(uint32_t client_process_id, uint32_t target_process_id, - std::string& nsm_file_name) +PM_STATUS PresentMon::UpdateTracking(const std::unordered_set& trackedPids) { - return pSession_->StartStreaming(client_process_id, target_process_id, nsm_file_name); -} - -void PresentMon::StopStreaming(uint32_t client_process_id, uint32_t target_process_id) -{ - return pSession_->StopStreaming(client_process_id, target_process_id); + return pSession_->UpdateTracking(trackedPids); } std::vector> PresentMon::EnumerateAdapters() diff --git a/IntelPresentMon/PresentMonService/PresentMon.h b/IntelPresentMon/PresentMonService/PresentMon.h index 1c66aaa6..bc2a0813 100644 --- a/IntelPresentMon/PresentMonService/PresentMon.h +++ b/IntelPresentMon/PresentMonService/PresentMon.h @@ -32,10 +32,7 @@ class PresentMon void CheckTraceSessions(); // Force stop trace sessions void StopTraceSessions(); - PM_STATUS StartStreaming(uint32_t client_process_id, - uint32_t target_process_id, - std::string& nsm_file_name); - void StopStreaming(uint32_t client_process_id, uint32_t target_process_id); + PM_STATUS UpdateTracking(const std::unordered_set& trackedPids); std::vector> EnumerateAdapters(); std::string GetCpuName() { return pSession_->GetCpuName(); } double GetCpuPowerLimit() { return pSession_->GetCpuPowerLimit(); } @@ -68,10 +65,9 @@ class PresentMon { return pSession_->GetStreamingStartHandle(); } - int GetActiveStreams() + bool HasLiveTargets() const { - // Only the real time trace uses the control libary interface - return pSession_->GetActiveStreams(); + return pSession_->HasLiveTargets(); } void SetPowerTelemetryContainer(PowerTelemetryContainer* ptc) { diff --git a/IntelPresentMon/PresentMonService/PresentMonService.vcxproj b/IntelPresentMon/PresentMonService/PresentMonService.vcxproj index c5bf29da..5b9c9e9f 100644 --- a/IntelPresentMon/PresentMonService/PresentMonService.vcxproj +++ b/IntelPresentMon/PresentMonService/PresentMonService.vcxproj @@ -180,12 +180,9 @@ {2b343210-86ab-4153-9a6c-945e4af54c7c} - + {66e9f6c5-28db-4218-81b9-31e0e146ecc0} - - {bf43064b-01f0-4c69-91fb-c2122baf621d} - {c73aa532-e532-4d93-9279-905444653c08} @@ -200,4 +197,4 @@ - \ No newline at end of file + diff --git a/IntelPresentMon/PresentMonService/PresentMonSession.cpp b/IntelPresentMon/PresentMonService/PresentMonSession.cpp index 86ed15e8..cc82dc22 100644 --- a/IntelPresentMon/PresentMonService/PresentMonSession.cpp +++ b/IntelPresentMon/PresentMonService/PresentMonSession.cpp @@ -1,12 +1,26 @@ -// Copyright (C) 2022-2023 Intel Corporation +// Copyright (C) 2022-2023 Intel Corporation // SPDX-License-Identifier: MIT #include "PresentMonSession.h" +#include pmon::test::service::Status PresentMonSession::GetTestingStatus() const { + std::set trackedPids; + { + std::lock_guard lock(tracked_processes_mutex_); + for (auto const& entry : tracked_pid_live_) { + trackedPids.emplace(entry.first); + } + } + std::set frameStorePids; + if (pBroadcaster) { + for (auto pid : pBroadcaster->GetPids()) { + frameStorePids.emplace(pid); + } + } return pmon::test::service::Status{ - .nsmStreamedPids = streamer_.GetActiveStreamPids(), - .trackedPids = { std::from_range, pBroadcaster->GetPids() }, + .trackedPids = std::move(trackedPids), + .frameStorePids = std::move(frameStorePids), .activeAdapterId = current_telemetry_adapter_id_, .telemetryPeriodMs = gpu_telemetry_period_ms_, .etwFlushPeriodMs = etw_flush_period_ms_, @@ -80,10 +94,57 @@ std::optional PresentMonSession::GetEtwFlushPeriod() return etw_flush_period_ms_; } -int PresentMonSession::GetActiveStreams() { - return streamer_.NumActiveStreams(); +bool PresentMonSession::HasLiveTargets() const { + return HasLiveTrackedProcesses(); } void PresentMonSession::SetPowerTelemetryContainer(PowerTelemetryContainer* ptc) { telemetry_container_ = ptc; -} \ No newline at end of file +} + +void PresentMonSession::SyncTrackedPidState(const std::unordered_set& trackedPids) +{ + // TODO: consider theoretical rare race condition where exited process is added and never gets + // marked "dead" while action client maintains session and nevers stops tracking + std::lock_guard lock(tracked_processes_mutex_); + std::erase_if(tracked_pid_live_, [&](auto const& entry) { + return !trackedPids.contains(entry.first); + }); + for (auto pid : trackedPids) { + if (!tracked_pid_live_.contains(pid)) { + tracked_pid_live_.emplace(pid, true); + } + } +} + +void PresentMonSession::MarkProcessExited(uint32_t pid) +{ + std::lock_guard lock(tracked_processes_mutex_); + if (auto it = tracked_pid_live_.find(pid); it != tracked_pid_live_.end()) { + it->second = false; + } +} + +bool PresentMonSession::IsProcessTracked(uint32_t pid) const +{ + std::lock_guard lock(tracked_processes_mutex_); + return tracked_pid_live_.find(pid) != tracked_pid_live_.end(); +} + +bool PresentMonSession::HasTrackedProcesses() const +{ + std::lock_guard lock(tracked_processes_mutex_); + return std::ranges::any_of(tracked_pid_live_, [](auto const&) { return true; }); +} + +bool PresentMonSession::HasLiveTrackedProcesses() const +{ + std::lock_guard lock(tracked_processes_mutex_); + return std::ranges::any_of(tracked_pid_live_, [](auto const& entry) { return entry.second; }); +} + +void PresentMonSession::ClearTrackedProcesses() +{ + std::lock_guard lock(tracked_processes_mutex_); + tracked_pid_live_.clear(); +} diff --git a/IntelPresentMon/PresentMonService/PresentMonSession.h b/IntelPresentMon/PresentMonService/PresentMonSession.h index 2b93d0f3..b2f8b857 100644 --- a/IntelPresentMon/PresentMonService/PresentMonSession.h +++ b/IntelPresentMon/PresentMonService/PresentMonSession.h @@ -1,16 +1,21 @@ -// Copyright (C) 2022-2023 Intel Corporation +// Copyright (C) 2022-2023 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #include "../CommonUtilities/win/WinAPI.h" - +#include #include #include +#include #include +#include +#include +#include +#include +#include #include #include "../ControlLib/PowerTelemetryProvider.h" #include "../ControlLib/CpuTelemetry.h" -#include "../Streamer/Streamer.h" #include "../../PresentData/PresentMonTraceConsumer.hpp" #include "../../PresentData/PresentMonTraceSession.hpp" #include "PowerTelemetryContainer.h" @@ -19,17 +24,9 @@ #include "../PresentMonAPI2Tests/TestCommands.h" -struct SwapChainData { - uint32_t mPresentHistoryCount; - uint64_t mLastPresentQPC; - uint64_t mLastDisplayedPresentQPC; -}; - struct ProcessInfo { std::wstring mModuleName; - std::unordered_map mSwapChain; HANDLE mHandle = INVALID_HANDLE_VALUE; - bool mTargetProcess = false; }; using namespace pmon; @@ -38,8 +35,7 @@ class PresentMonSession { public: virtual ~PresentMonSession() = default; virtual bool IsTraceSessionActive() = 0; - virtual PM_STATUS StartStreaming(uint32_t client_process_id, uint32_t target_process_id, std::string& nsmFileName) = 0; - virtual void StopStreaming(uint32_t client_process_id, uint32_t target_process_id) = 0; + virtual PM_STATUS UpdateTracking(const std::unordered_set& trackedPids) = 0; virtual bool CheckTraceSessions(bool forceTerminate) = 0; virtual HANDLE GetStreamingStartHandle() = 0; virtual void FlushEvents() {} @@ -56,9 +52,17 @@ class PresentMonSession { PM_STATUS SetEtwFlushPeriod(std::optional periodMs); std::optional GetEtwFlushPeriod(); uint32_t GetGpuTelemetryPeriod(); - int GetActiveStreams(); + bool HasLiveTargets() const; void SetPowerTelemetryContainer(PowerTelemetryContainer* ptc); + void MarkProcessExited(uint32_t pid); + bool IsProcessTracked(uint32_t pid) const; + bool HasTrackedProcesses() const; + bool HasLiveTrackedProcesses() const; + void ClearTrackedProcesses(); + +protected: + // TODO: review all of these members and consider fixing the unsound thread safety aspects // data std::wstring pm_session_name_; @@ -77,7 +81,11 @@ class PresentMonSession { // empty optional means automatic flushing active std::atomic> etw_flush_period_ms_; - Streamer streamer_; svc::FrameBroadcaster* pBroadcaster = nullptr; + + void SyncTrackedPidState(const std::unordered_set& trackedPids); + + mutable std::mutex tracked_processes_mutex_; + std::unordered_map tracked_pid_live_; }; diff --git a/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp b/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp index 1d09fc72..7a3d56e1 100644 --- a/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp +++ b/IntelPresentMon/PresentMonService/RealtimePresentMonSession.cpp @@ -1,13 +1,13 @@ -// Copyright (C) 2022-2023 Intel Corporation +// Copyright (C) 2022-2023 Intel Corporation // SPDX-License-Identifier: MIT #include "Logging.h" #include "RealtimePresentMonSession.h" #include "CliOptions.h" #include "../CommonUtilities/str/String.h" #include "../CommonUtilities/win/Event.h" +#include "../CommonUtilities/win/Utilities.h" #include "../CommonUtilities/Qpc.h" #include "../CommonUtilities/Exception.h" -#include using namespace pmon; using namespace std::literals; @@ -23,57 +23,44 @@ bool RealtimePresentMonSession::IsTraceSessionActive() { return session_active_.load(std::memory_order_acquire); } -PM_STATUS RealtimePresentMonSession::StartStreaming(uint32_t client_process_id, - uint32_t target_process_id, - std::string& nsmFileName) { - - // Check to see if the target process is valid - HANDLE target_process_handle = OpenProcess( - PROCESS_QUERY_LIMITED_INFORMATION, FALSE, target_process_id); - if (target_process_handle == nullptr) { - return PM_STATUS::PM_STATUS_INVALID_PID; - } - CloseHandle(target_process_handle); - - PM_STATUS status = streamer_.StartStreaming(client_process_id, - target_process_id, nsmFileName, false, false, false, false, false); - if (status != PM_STATUS::PM_STATUS_SUCCESS) { - return status; +PM_STATUS RealtimePresentMonSession::UpdateTracking(const std::unordered_set& trackedPids) { + const bool wasActive = HasLiveTargets(); + std::unordered_map previousState; + { + std::lock_guard lock(tracked_processes_mutex_); + previousState = tracked_pid_live_; } - else { - // Add the client process id to be monitored - GetProcessInfo(client_process_id); + SyncTrackedPidState(trackedPids); + const bool isActive = HasLiveTargets(); + if (isActive && (!wasActive || !IsTraceSessionActive())) { auto status = StartTraceSession(); - if (status == PM_STATUS_FAILURE) { - // Unable to start a trace session. Destroy the NSM and - // return status - streamer_.StopStreaming(target_process_id); + if (status != PM_STATUS_SUCCESS) { + { + std::lock_guard lock(tracked_processes_mutex_); + tracked_pid_live_ = std::move(previousState); + } return status; } - // Only signal the streaming started event when we have - // exactly one stream after returning from StartStreaming. - if ((streamer_.NumActiveStreams() == 1) && evtStreamingStarted_) { - evtStreamingStarted_.Set(); - } - // Also monitor the target process id - GetProcessInfo(target_process_id); - - return PM_STATUS::PM_STATUS_SUCCESS; } -} - -void RealtimePresentMonSession::StopStreaming(uint32_t client_process_id, - uint32_t target_process_id) { - streamer_.StopStreaming(client_process_id, target_process_id); - if ((streamer_.NumActiveStreams() == 0) && evtStreamingStarted_) { - evtStreamingStarted_.Reset(); + if (isActive && evtStreamingStarted_) { + evtStreamingStarted_.Set(); + } + if (!isActive) { + if (evtStreamingStarted_) { + evtStreamingStarted_.Reset(); + } StopTraceSession(); } + return PM_STATUS::PM_STATUS_SUCCESS; } bool RealtimePresentMonSession::CheckTraceSessions(bool forceTerminate) { - if (((GetActiveStreams() == 0) && (IsTraceSessionActive() == true)) || - forceTerminate) { + if (forceTerminate) { + StopTraceSession(); + ClearTrackedProcesses(); + return true; + } + if (!HasLiveTargets() && (IsTraceSessionActive() == true)) { StopTraceSession(); return true; } @@ -195,8 +182,6 @@ void RealtimePresentMonSession::StopTraceSession() { // PHASE 2: Safe cleanup after threads have finished std::lock_guard lock(session_mutex_); - // Stop all streams - streamer_.StopAllStreams(); if (evtStreamingStarted_) { evtStreamingStarted_.Reset(); } @@ -235,7 +220,6 @@ void RealtimePresentMonSession::AddPresents( if (session_active_.load(std::memory_order_acquire)) { if (trace_session_.mStartTimestamp.QuadPart != 0) { - streamer_.SetStartQpc(trace_session_.mStartTimestamp.QuadPart); if (pBroadcaster) { pBroadcaster->SetStartQpc(trace_session_.mStartTimestamp.QuadPart); } @@ -273,49 +257,10 @@ void RealtimePresentMonSession::AddPresents( break; } - // Look up the swapchain this present belongs to. - auto processInfo = GetProcessInfo(presentEvent->ProcessId); - if (!processInfo->mTargetProcess) { + if (!IsProcessTracked(presentEvent->ProcessId)) { continue; } - PresentMonPowerTelemetryInfo power_telemetry = {}; - std::bitset(GpuTelemetryCapBits::gpu_telemetry_count)> - gpu_telemetry_cap_bits = {}; - if (telemetry_container_) { - auto current_adapters = telemetry_container_->GetPowerTelemetryAdapters(); - if (current_adapters.size() != 0 && - current_telemetry_adapter_id_ < current_adapters.size()) { - auto current_telemetry_adapter = - current_adapters.at(current_telemetry_adapter_id_).get(); - if (auto data = current_telemetry_adapter->GetClosest( - presentEvent->PresentStartTime)) { - power_telemetry = *data; - } - gpu_telemetry_cap_bits = current_telemetry_adapter - ->GetPowerTelemetryCapBits(); - } - } - - CpuTelemetryInfo cpu_telemetry = {}; - std::bitset(CpuTelemetryCapBits::cpu_telemetry_count)> - cpu_telemetry_cap_bits = {}; - if (cpu_) { - if (auto data = cpu_->GetClosest(presentEvent->PresentStartTime)) { - cpu_telemetry = *data; - } - cpu_telemetry_cap_bits = cpu_->GetCpuTelemetryCapBits(); - } - - auto result = processInfo->mSwapChain.emplace( - presentEvent->SwapChainAddress, SwapChainData()); - auto chain = &result.first->second; - if (result.second) { - chain->mPresentHistoryCount = 0; - chain->mLastPresentQPC = 0; - chain->mLastDisplayedPresentQPC = 0; - } - // Remove Repeated flips if they are in Application->Repeated or Repeated->Application sequences. for (size_t i = 0, n = presentEvent->Displayed.size(); i + 1 < n; ) { if (presentEvent->Displayed[i].first == FrameType::Application && @@ -333,23 +278,8 @@ void RealtimePresentMonSession::AddPresents( } } - streamer_.ProcessPresentEvent( - presentEvent.get(), &power_telemetry, &cpu_telemetry, - chain->mLastPresentQPC, chain->mLastDisplayedPresentQPC, - processInfo->mModuleName, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - pBroadcaster->Broadcast(*presentEvent); - chain->mLastPresentQPC = presentEvent->PresentStartTime; - if (presentEvent->FinalState == PresentResult::Presented) { - chain->mLastDisplayedPresentQPC = presentEvent->Displayed.empty() ? 0 : presentEvent->Displayed[0].second; - } - else if (chain->mLastDisplayedPresentQPC == chain->mLastPresentQPC) { - chain->mLastDisplayedPresentQPC = 0; - } - - chain->mPresentHistoryCount += 1; } *presentEventIndex = i; @@ -512,15 +442,6 @@ void RealtimePresentMonSession::Output() { } } - // Process handles - std::lock_guard lock(process_mutex_); - for (auto& pair : processes_) { - auto processInfo = &pair.second; - if (processInfo->mHandle != NULL) { - CloseHandle(processInfo->mHandle); - } - } - processes_.clear(); } catch (...) { pmlog_error(util::ReportException()); @@ -539,62 +460,18 @@ void RealtimePresentMonSession::StopOutputThread() { } } -ProcessInfo* RealtimePresentMonSession::GetProcessInfo(uint32_t processId) { - std::lock_guard lock(process_mutex_); - auto result = processes_.emplace(processId, ProcessInfo()); - auto processInfo = &result.first->second; - auto newProcess = result.second; - - if (newProcess) { - // Try to open a limited handle into the process in order to query its - // name and also periodically check if it has terminated. This will - // fail (with GetLastError() == ERROR_ACCESS_DENIED) if the process was - // run on another account, unless we're running with SeDebugPrivilege. - auto pProcessName = L""; - wchar_t path[MAX_PATH]; - const auto handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, processId); - if (handle) { - auto numChars = (DWORD)std::size(path); - if (QueryFullProcessImageNameW(handle, 0, path, &numChars)) { - pProcessName = PathFindFileNameW(path); - } - } - - InitProcessInfo(processInfo, processId, handle, pProcessName); - } - - return processInfo; -} - -void RealtimePresentMonSession::InitProcessInfo(ProcessInfo* processInfo, uint32_t processId, - HANDLE handle, - std::wstring const& processName) { - processInfo->mHandle = handle; - processInfo->mModuleName = processName; - processInfo->mTargetProcess = true; - - target_process_count_ += 1; -} void RealtimePresentMonSession::UpdateProcesses( std::vector const& processEvents, std::vector>* terminatedProcesses) { for (auto const& processEvent : processEvents) { - if (processEvent.IsStartEvent) { - // This event is a new process starting, the pid should not already be - // in processes_. - std::lock_guard lock(process_mutex_); - auto result = processes_.emplace(processEvent.ProcessId, ProcessInfo()); - auto processInfo = &result.first->second; - auto newProcess = result.second; - if (newProcess) { - InitProcessInfo(processInfo, processEvent.ProcessId, NULL, - processEvent.ImageFileName); - } + if (!IsProcessTracked(processEvent.ProcessId)) { + continue; } - else { + if (!processEvent.IsStartEvent) { // Note any process termination in terminatedProcess, to be handled // once the present event stream catches up to the termination time. + MarkProcessExited(processEvent.ProcessId); terminatedProcesses->emplace_back(processEvent.ProcessId, processEvent.QpcTime); } @@ -602,53 +479,21 @@ void RealtimePresentMonSession::UpdateProcesses( } void RealtimePresentMonSession::HandleTerminatedProcess(uint32_t processId) { - std::lock_guard lock(process_mutex_); - auto iter = processes_.find(processId); - if (iter == processes_.end()) { - return; // shouldn't happen. - } - - auto processInfo = &iter->second; - if (processInfo->mTargetProcess) { - // TODO(megalvan): Need to figure this out - // Close this process' CSV. - // CloseOutputCsv(processInfo); - - target_process_count_ -= 1; + // TODO(megalvan): Need to figure this out + // Close this process' CSV. + // CloseOutputCsv(processInfo); + MarkProcessExited(processId); + if (!HasLiveTrackedProcesses() && evtStreamingStarted_) { + evtStreamingStarted_.Reset(); } - processes_.erase(std::move(iter)); - streamer_.StopStreaming(processId); } // Check if any realtime processes terminated and add them to the terminated // list. // -// We assume that the process terminated now, which is wrong but conservative -// and functionally ok because no other process should start with the same PID -// as long as we're still holding a handle to it. +// Note: handle-based polling of target process lifetime is intentionally +// handled outside of the session layer. void RealtimePresentMonSession::CheckForTerminatedRealtimeProcesses( std::vector>* terminatedProcesses) { - std::lock_guard lock(process_mutex_); - for (auto& pair : processes_) { - auto processId = pair.first; - auto processInfo = &pair.second; - - DWORD exitCode = 0; - if (processInfo->mHandle != NULL && - GetExitCodeProcess(processInfo->mHandle, &exitCode) && - exitCode != STILL_ACTIVE) { - uint64_t qpc = 0; - QueryPerformanceCounter(reinterpret_cast(&qpc)); - terminatedProcesses->emplace_back(processId, qpc); - CloseHandle(processInfo->mHandle); - processInfo->mHandle = NULL; - // The tracked process has terminated. As multiple clients could be - // tracking this process call stop streaming until the streamer - // returns false and no longer holds an NSM for the process. - streamer_.StopStreaming(processId); - if ((streamer_.NumActiveStreams() == 0) && evtStreamingStarted_) { - evtStreamingStarted_.Reset(); - } - } - } + (void)terminatedProcesses; } diff --git a/IntelPresentMon/PresentMonService/RealtimePresentMonSession.h b/IntelPresentMon/PresentMonService/RealtimePresentMonSession.h index d89abff3..54be3135 100644 --- a/IntelPresentMon/PresentMonService/RealtimePresentMonSession.h +++ b/IntelPresentMon/PresentMonService/RealtimePresentMonSession.h @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2023 Intel Corporation +// Copyright (C) 2022-2023 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #include "PresentMonSession.h" @@ -14,8 +14,7 @@ class RealtimePresentMonSession : public PresentMonSession ~RealtimePresentMonSession() override = default; bool IsTraceSessionActive() override; - PM_STATUS StartStreaming(uint32_t client_process_id, uint32_t target_process_id, std::string& nsmFileName) override; - void StopStreaming(uint32_t client_process_id, uint32_t target_process_id) override; + PM_STATUS UpdateTracking(const std::unordered_set& trackedPids) override; bool CheckTraceSessions(bool forceTerminate) override; HANDLE GetStreamingStartHandle() override; void FlushEvents() override; @@ -45,9 +44,6 @@ class RealtimePresentMonSession : public PresentMonSession void Consume(TRACEHANDLE traceHandle); void Output(); - ProcessInfo* GetProcessInfo(uint32_t processId); - void InitProcessInfo(ProcessInfo* processInfo, uint32_t processId, - HANDLE handle, std::wstring const& processName); void UpdateProcesses( std::vector const& processEvents, std::vector>* terminatedProcesses); @@ -66,13 +62,9 @@ class RealtimePresentMonSession : public PresentMonSession std::atomic quit_output_thread_ = false; - std::unordered_map processes_; - uint32_t target_process_count_ = 0; - // Event for when streaming has started pmon::util::win::Event evtStreamingStarted_; mutable std::mutex session_mutex_; - mutable std::mutex process_mutex_; std::atomic session_active_{false}; // Lock-free session state for hot path queries -}; \ No newline at end of file +}; diff --git a/IntelPresentMon/PresentMonService/acts/StartTracking.h b/IntelPresentMon/PresentMonService/acts/StartTracking.h index 5905567f..e2046fa7 100644 --- a/IntelPresentMon/PresentMonService/acts/StartTracking.h +++ b/IntelPresentMon/PresentMonService/acts/StartTracking.h @@ -1,6 +1,8 @@ #pragma once #include "../../Interprocess/source/act/ActionHelper.h" +#include "../../CommonUtilities/win/Utilities.h" #include +#include #define ACT_NAME StartTracking #define ACT_EXEC_CTX ActionExecutionContext @@ -27,37 +29,49 @@ namespace pmon::svc::acts }; struct Response { - // TODO: remove this when Streamer is gone - std::string nsmFileName; - template void serialize(A& ar) { - ar(nsmFileName); } }; private: friend class ACT_TYPE; static Response Execute_(const ACT_EXEC_CTX& ctx, SessionContext& stx, Params&& in) { + // playback mode compatibility check const bool serviceIsPlayback = ctx.pPmon->IsPlayback(); if (serviceIsPlayback != in.isPlayback) { pmlog_error("StartTracking playback mode mismatch") .pmwatch(serviceIsPlayback).pmwatch(in.isPlayback); throw util::Except(PM_STATUS_MODE_MISMATCH); } - std::string nsmFileName; - // TODO: replace PresentMon container system and directly return the segment - // from a single "StartStreaming" replacement call + // check if this session already tracking requested pid + if (stx.trackedPids.find(in.targetPid) != stx.trackedPids.end()) { + pmlog_error("StartTracking called for already tracked pid").pmwatch(in.targetPid); + throw util::Except(PM_STATUS_ALREADY_TRACKING_PROCESS); + } + // lock in handle to process for pid parking purposes + ActionSessionContext::TrackedTarget target{}; + if (!in.isPlayback) { + target.processHandle = util::win::OpenProcess(in.targetPid); + if (!target.processHandle) { + pmlog_error("StartTracking called for invalid pid").pmwatch(in.targetPid); + throw util::Except(PM_STATUS_INVALID_PID); + } + } + // build full tracking state for session sync + auto trackedPids = ctx.GetTrackedPidSet(); + trackedPids.emplace(in.targetPid); + // get the (possibly shared) segment (new or find operation in broadcaster) auto pSegment = ctx.pPmon->GetBroadcaster().RegisterTarget( in.targetPid, in.isPlayback, in.isBackpressured); - if (auto sta = ctx.pPmon->StartStreaming(stx.remotePid, in.targetPid, nsmFileName); sta != PM_STATUS_SUCCESS) { - pmlog_error("Start stream failed").code(sta); + if (auto sta = ctx.pPmon->UpdateTracking(trackedPids); sta != PM_STATUS_SUCCESS) { + pmlog_error("Start tracking call failed").code(sta); throw util::Except(sta); } - stx.trackedPids[in.targetPid] = std::move(pSegment); - const Response out{ .nsmFileName = std::move(nsmFileName) }; - pmlog_info(std::format("StartTracking action from [{}] targeting [{}] assigned nsm [{}]", - stx.remotePid, in.targetPid, out.nsmFileName)); - return out; + target.pSegment = std::move(pSegment); + stx.trackedPids.emplace(in.targetPid, std::move(target)); + pmlog_info(std::format("StartTracking action from [{}] targeting [{}]", + stx.remotePid, in.targetPid)); + return {}; } }; diff --git a/IntelPresentMon/PresentMonService/acts/StopTracking.h b/IntelPresentMon/PresentMonService/acts/StopTracking.h index fd041ba5..b6f3bd90 100644 --- a/IntelPresentMon/PresentMonService/acts/StopTracking.h +++ b/IntelPresentMon/PresentMonService/acts/StopTracking.h @@ -1,6 +1,7 @@ -#pragma once +#pragma once #include "../../Interprocess/source/act/ActionHelper.h" #include +#include #define ACT_NAME StopTracking #define ACT_EXEC_CTX ActionExecutionContext @@ -28,8 +29,11 @@ namespace pmon::svc::acts friend class ACT_TYPE; static Response Execute_(const ACT_EXEC_CTX& ctx, SessionContext& stx, Params&& in) { - ctx.pPmon->StopStreaming(stx.remotePid, in.targetPid); - stx.trackedPids.erase(in.targetPid); + if (stx.trackedPids.erase(in.targetPid) == 0) { + pmlog_error("StopTracking called for untracked pid").pmwatch(in.targetPid); + throw util::Except(PM_STATUS_INVALID_PID); + } + ctx.pPmon->UpdateTracking(ctx.GetTrackedPidSet()); pmlog_info(std::format("StopTracking action from [{}] un-targeting [{}]", stx.remotePid, in.targetPid)); return {}; } @@ -45,4 +49,4 @@ ACTION_TRAITS_DEF(); #undef ACT_NAME #undef ACT_EXEC_CTX #undef ACT_NS -#undef ACT_TYPE \ No newline at end of file +#undef ACT_TYPE diff --git a/IntelPresentMon/PresentMonUtils/LegacyAPIDefines.h b/IntelPresentMon/PresentMonUtils/LegacyAPIDefines.h deleted file mode 100644 index 67c6a8f9..00000000 --- a/IntelPresentMon/PresentMonUtils/LegacyAPIDefines.h +++ /dev/null @@ -1,155 +0,0 @@ -#pragma once -#include - -// TODO: review these definitions; they are legacy code copied from old PresentMonAPI header. -// Replace with more suitable structures that align better with new API or work better without -// the extern "C" restrictions, as necessary/appropriate/convenient -#define MAX_PM_ADAPTER_NAME 64 -#define MAX_PM_CPU_NAME 256 -#define MAX_PM_PATH 260 -#define MAX_RUNTIME_LENGTH 7 -#define MAX_PM_FAN_COUNT 5 -#define MAX_PM_PSU_COUNT 5 -enum PM_GPU_VENDOR { - PM_GPU_VENDOR_INTEL, - PM_GPU_VENDOR_NVIDIA, - PM_GPU_VENDOR_AMD, - PM_GPU_VENDOR_UNKNOWN -}; -enum PM_PSU_TYPE { - PM_PSU_TYPE_NONE, - PM_PSU_TYPE_PCIE, - PM_PSU_TYPE_6PIN, - PM_PSU_TYPE_8PIN -}; -struct PM_ADAPTER_INFO -{ - uint32_t id; - PM_GPU_VENDOR vendor; - char name[MAX_PM_ADAPTER_NAME]; - double gpuSustainedPowerLimit; - uint64_t gpuMemorySize; - uint64_t gpuMemoryMaxBandwidth; -}; -struct PM_FRAME_DATA_OPT_DOUBLE { - double data; - bool valid; -}; -struct PM_FRAME_DATA_OPT_UINT64 { - uint64_t data; - bool valid; -}; -struct PM_FRAME_DATA_OPT_INT { - int data; - bool valid; -}; -struct PM_FRAME_DATA_OPT_PSU_TYPE { - PM_PSU_TYPE data; - bool valid; -}; -struct PM_FRAME_DATA -{ - // @brief The name of the process that called Present(). - char application[MAX_PM_PATH]; - - // @brief The process ID of the process that called Present(). - uint32_t process_id; - // @brief The address of the swap chain that was presented into. - uint64_t swap_chain_address; - // @brief The runtime used to present (e.g., D3D9 or DXGI). - char runtime[MAX_RUNTIME_LENGTH]; - // @brief The sync interval provided by the application in the Present() - // call. This value may be modified later by the driver, e.g., based on - // control panel overrides. - int32_t sync_interval; - // @brief Flags used in the Present() call. - uint32_t present_flags; - // @brief Whether the frame was dropped (1) or displayed (0). Note, if - // dropped, msUntilDisplayed will be 0. - uint32_t dropped; - // @brief The time of the Present() call, in seconds, relative to when the - // PresentMon started recording. - double time_in_seconds; - // @brief The time spent inside the Present() call, in milliseconds. - double ms_in_present_api; - // @brief The time between this Present() call and the previous one, in - // milliseconds. - double ms_between_presents; - // @brief Whether tearing is possible (1) or not (0). - uint32_t allows_tearing; - // @brief The presentation mode used by the system for this Present(). - PM_PRESENT_MODE present_mode; - // @brief The time between the Present() call and when the GPU work - // completed, in milliseconds. - double ms_until_render_complete; - // @brief The time between the Present() call and when the frame was - // displayed, in milliseconds. - double ms_until_displayed; - // @brief How long the previous frame was displayed before this Present() - // was displayed, in milliseconds. - double ms_between_display_change; - // @brief The time between the Present() call and when the GPU work - // started, in milliseconds. - double ms_until_render_start; - // @brief The time of the Present() call, as a performance counter value. - uint64_t qpc_time; - - // @brief The time between the Present() call and the earliest keyboard or - // mouse interaction that contributed to this frame. - double ms_since_input; - // @brief The time that any GPU engine was active working on this frame, - // in milliseconds. Not supported on Win7 - double ms_gpu_active; - // @brief The time video encode/decode was active separate from the other - // engines in milliseconds. Not supported on Win7 - double ms_gpu_video_active; - - // Power telemetry - PM_FRAME_DATA_OPT_DOUBLE gpu_power_w; - PM_FRAME_DATA_OPT_DOUBLE gpu_sustained_power_limit_w; - PM_FRAME_DATA_OPT_DOUBLE gpu_voltage_v; - PM_FRAME_DATA_OPT_DOUBLE gpu_frequency_mhz; - PM_FRAME_DATA_OPT_DOUBLE gpu_temperature_c; - PM_FRAME_DATA_OPT_DOUBLE gpu_utilization; - PM_FRAME_DATA_OPT_DOUBLE gpu_render_compute_utilization; - PM_FRAME_DATA_OPT_DOUBLE gpu_media_utilization; - - PM_FRAME_DATA_OPT_DOUBLE vram_power_w; - PM_FRAME_DATA_OPT_DOUBLE vram_voltage_v; - PM_FRAME_DATA_OPT_DOUBLE vram_frequency_mhz; - PM_FRAME_DATA_OPT_DOUBLE vram_effective_frequency_gbs; - PM_FRAME_DATA_OPT_DOUBLE vram_temperature_c; - - PM_FRAME_DATA_OPT_DOUBLE fan_speed_rpm[MAX_PM_FAN_COUNT]; - - PM_FRAME_DATA_OPT_PSU_TYPE psu_type[MAX_PM_PSU_COUNT]; - PM_FRAME_DATA_OPT_DOUBLE psu_power[MAX_PM_PSU_COUNT]; - PM_FRAME_DATA_OPT_DOUBLE psu_voltage[MAX_PM_PSU_COUNT]; - - // Gpu memory telemetry - PM_FRAME_DATA_OPT_UINT64 gpu_mem_total_size_b; - PM_FRAME_DATA_OPT_UINT64 gpu_mem_used_b; - PM_FRAME_DATA_OPT_UINT64 gpu_mem_max_bandwidth_bps; - PM_FRAME_DATA_OPT_DOUBLE gpu_mem_read_bandwidth_bps; - PM_FRAME_DATA_OPT_DOUBLE gpu_mem_write_bandwidth_bps; - - // Throttling flags - PM_FRAME_DATA_OPT_INT gpu_power_limited; - PM_FRAME_DATA_OPT_INT gpu_temperature_limited; - PM_FRAME_DATA_OPT_INT gpu_current_limited; - PM_FRAME_DATA_OPT_INT gpu_voltage_limited; - PM_FRAME_DATA_OPT_INT gpu_utilization_limited; - - PM_FRAME_DATA_OPT_INT vram_power_limited; - PM_FRAME_DATA_OPT_INT vram_temperature_limited; - PM_FRAME_DATA_OPT_INT vram_current_limited; - PM_FRAME_DATA_OPT_INT vram_voltage_limited; - PM_FRAME_DATA_OPT_INT vram_utilization_limited; - - // Cpu Telemetry - PM_FRAME_DATA_OPT_DOUBLE cpu_utilization; - PM_FRAME_DATA_OPT_DOUBLE cpu_power_w; - PM_FRAME_DATA_OPT_DOUBLE cpu_power_limit_w; - PM_FRAME_DATA_OPT_DOUBLE cpu_temperature_c; - PM_FRAME_DATA_OPT_DOUBLE cpu_frequency; -}; \ No newline at end of file diff --git a/IntelPresentMon/PresentMonUtils/PresentDataUtils.h b/IntelPresentMon/PresentMonUtils/PresentDataUtils.h deleted file mode 100644 index ce80829a..00000000 --- a/IntelPresentMon/PresentMonUtils/PresentDataUtils.h +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once -#include -#include "../../PresentData/PresentMonTraceConsumer.hpp" -#include "../PresentMonAPI2/PresentMonAPI.h" -#include "../ControlLib/PresentMonPowerTelemetry.h" - -inline const char* PresentModeToString(PresentMode mode) { - switch (mode) { - case PresentMode::Hardware_Legacy_Flip: - return "Hardware: Legacy Flip"; - case PresentMode::Hardware_Legacy_Copy_To_Front_Buffer: - return "Hardware: Legacy Copy to front buffer"; - case PresentMode::Hardware_Independent_Flip: - return "Hardware: Independent Flip"; - case PresentMode::Composed_Flip: - return "Composed: Flip"; - case PresentMode::Composed_Copy_GPU_GDI: - return "Composed: Copy with GPU GDI"; - case PresentMode::Composed_Copy_CPU_GDI: - return "Composed: Copy with CPU GDI"; - case PresentMode::Hardware_Composed_Independent_Flip: - return "Hardware Composed: Independent Flip"; - default: - return "Other"; - } -} - -inline const char* RuntimeToString(Runtime rt) { - switch (rt) { - case Runtime::DXGI: - return "DXGI"; - case Runtime::D3D9: - return "D3D9"; - default: - return "Other"; - } -} - -inline PM_PSU_TYPE TranslatePsuType(PresentMonPsuType psu_type_in) { - switch (psu_type_in) { - case PresentMonPsuType::Pcie: - return PM_PSU_TYPE::PM_PSU_TYPE_PCIE; - case PresentMonPsuType::Pin6: - return PM_PSU_TYPE::PM_PSU_TYPE_6PIN; - case PresentMonPsuType::Pin8: - return PM_PSU_TYPE::PM_PSU_TYPE_8PIN; - default: - return PM_PSU_TYPE::PM_PSU_TYPE_NONE; - } -} - -inline PM_PRESENT_MODE TranslatePresentMode( - PresentMode present_mode_in) { - switch (present_mode_in) { - case PresentMode::Hardware_Legacy_Flip: - return PM_PRESENT_MODE::PM_PRESENT_MODE_HARDWARE_LEGACY_FLIP; - case PresentMode::Hardware_Legacy_Copy_To_Front_Buffer: - return PM_PRESENT_MODE:: - PM_PRESENT_MODE_HARDWARE_LEGACY_COPY_TO_FRONT_BUFFER; - case PresentMode::Hardware_Independent_Flip: - return PM_PRESENT_MODE::PM_PRESENT_MODE_HARDWARE_INDEPENDENT_FLIP; - case PresentMode::Composed_Flip: - return PM_PRESENT_MODE::PM_PRESENT_MODE_COMPOSED_FLIP; - case PresentMode::Hardware_Composed_Independent_Flip: - return PM_PRESENT_MODE:: - PM_PRESENT_MODE_HARDWARE_COMPOSED_INDEPENDENT_FLIP; - case PresentMode::Composed_Copy_GPU_GDI: - return PM_PRESENT_MODE::PM_PRESENT_MODE_COMPOSED_COPY_WITH_GPU_GDI; - case PresentMode::Composed_Copy_CPU_GDI: - return PM_PRESENT_MODE::PM_PRESENT_MODE_COMPOSED_COPY_WITH_CPU_GDI; - default: - return PM_PRESENT_MODE::PM_PRESENT_MODE_UNKNOWN; - } -} \ No newline at end of file diff --git a/IntelPresentMon/PresentMonUtils/PresentMonUtils.vcxproj b/IntelPresentMon/PresentMonUtils/PresentMonUtils.vcxproj deleted file mode 100644 index 76dca997..00000000 --- a/IntelPresentMon/PresentMonUtils/PresentMonUtils.vcxproj +++ /dev/null @@ -1,118 +0,0 @@ - - - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {66e9f6c5-28db-4218-81b9-31e0e146ecc0} - PresentMonUtils - 10.0 - - - - StaticLibrary - true - v143 - Unicode - x64 - - - StaticLibrary - false - v143 - true - Unicode - x64 - - - - - - - - - - - - - - - - - - - true - $(SolutionDir)PresentMonAPI;$(SolutionDir)PresentMonService;$(SolutionDir)ControlLib;$(VC_IncludePath);$(WindowsSDK_IncludePath); - --checks==clang-analyzer-*,google-* - false - false - - - false - $(SolutionDir)PresentMonAPI;$(SolutionDir)PresentMonService;$(SolutionDir)ControlLib;$(VC_IncludePath);$(WindowsSDK_IncludePath); - --checks==clang-analyzer-*,google-* - false - false - - - - - - - - Level3 - true - true - MultiThreadedDebug - EditAndContinue - stdcpp17 - - - Console - true - - - - - Level3 - true - true - true - true - MaxSpeed - MultiThreaded - stdcpp17 - NDEBUG;%(PreprocessorDefinitions) - - - Console - true - true - true - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/IntelPresentMon/PresentMonUtils/QPCUtils.cpp b/IntelPresentMon/PresentMonUtils/QPCUtils.cpp deleted file mode 100644 index d856fb30..00000000 --- a/IntelPresentMon/PresentMonUtils/QPCUtils.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "QPCUtils.h" - -double QpcDeltaToSeconds(uint64_t qpcDelta, LARGE_INTEGER qpcFrequency) -{ - return static_cast(qpcDelta) / qpcFrequency.QuadPart; -} - -uint64_t SecondsDeltaToQpc(double secondsDelta, LARGE_INTEGER qpcFrequency) -{ - return static_cast((secondsDelta * qpcFrequency.QuadPart)); -} - -double QpcToSeconds(uint64_t qpc, LARGE_INTEGER qpcFrequency, LARGE_INTEGER startQpc) -{ - return QpcDeltaToSeconds(qpc - startQpc.QuadPart, qpcFrequency); -} - -void QpcToLocalSystemTime(uint64_t qpc, LARGE_INTEGER startQpc, LARGE_INTEGER qpcFrequency, FILETIME startTime, SYSTEMTIME* st, uint64_t* ns) -{ - auto tns = (qpc - startQpc.QuadPart) * 1000000000ull / qpcFrequency.QuadPart; - auto t100ns = tns / 100; - auto ft = (*reinterpret_cast(&startTime)) + t100ns; - - FileTimeToSystemTime(reinterpret_cast(&ft), st); - *ns = (ft - (ft / 10000000ull) * 10000000ull) * 100ull + (tns - t100ns * 100ull); -} - -double QpcDeltaToMs(uint64_t qpc_data, LARGE_INTEGER qpc_frequency) { - return 1000.0f * (static_cast(qpc_data)/ qpc_frequency.QuadPart); -} \ No newline at end of file diff --git a/IntelPresentMon/PresentMonUtils/QPCUtils.h b/IntelPresentMon/PresentMonUtils/QPCUtils.h deleted file mode 100644 index bf97ff32..00000000 --- a/IntelPresentMon/PresentMonUtils/QPCUtils.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include -#include - -double QpcDeltaToSeconds(uint64_t qpcDelta, LARGE_INTEGER qpcFrequency); -uint64_t SecondsDeltaToQpc(double secondsDelta, LARGE_INTEGER qpcFrequency); -double QpcToSeconds(uint64_t qpc, LARGE_INTEGER qpcFrequency, LARGE_INTEGER startQpc); -void QpcToLocalSystemTime( - uint64_t qpc, - LARGE_INTEGER startQpc, - LARGE_INTEGER qpcFrequency, - FILETIME startTime, - SYSTEMTIME* st, - uint64_t* ns); -double QpcDeltaToMs(uint64_t qpc_data, LARGE_INTEGER qpc_frequency); \ No newline at end of file diff --git a/IntelPresentMon/PresentMonUtils/StreamFormat.h b/IntelPresentMon/PresentMonUtils/StreamFormat.h deleted file mode 100644 index 7c53e1f8..00000000 --- a/IntelPresentMon/PresentMonUtils/StreamFormat.h +++ /dev/null @@ -1,172 +0,0 @@ -/* -* -* Copyright (C) 2021,2023 Intel Corporation -* -* SPDX-License-Identifier: MIT -* -* - * File Name: StreamFormat.h - * - * Abstract: Header for Intel PresentMon Named Shared Memory IPC - * - */ -#pragma once -#include -#include -#include -#include "../../PresentData/PresentMonTraceConsumer.hpp" -#include "../PresentMonAPI2/PresentMonAPI.h" -#include "../ControlLib/PowerTelemetryProvider.h" -#include "../ControlLib/CpuTelemetryInfo.h" -#include "LegacyAPIDefines.h" - - // We use system reserved pid (0: System Idle Process, 4: System) that will - // never show up in present mon for StreamAll and ETL PIDs -enum class StreamPidOverride : uint32_t { kStreamAllPid = 0, kEtlPid = 4 }; - -struct NamedSharedMemoryHeader -{ - NamedSharedMemoryHeader() - : start_qpc(0), - last_displayed_qpc(0), - buf_size(0), - max_entries(0), - current_write_offset(0), - num_frames_written(0), - head_idx(0), - tail_idx(0), - process_active(true) {} - // start QPC time of the very first frame recorderd after PmStartStream - char application[MAX_PATH] = {}; - uint64_t start_qpc; - LARGE_INTEGER qpc_frequency = {}; - uint64_t last_displayed_qpc; - uint64_t buf_size; - uint64_t max_entries; - uint64_t current_write_offset; - uint64_t num_frames_written; - uint64_t head_idx; - uint64_t tail_idx; - bool process_active; - std::bitset(GpuTelemetryCapBits::gpu_telemetry_count)> - gpuTelemetryCapBits{}; - std::bitset(CpuTelemetryCapBits::cpu_telemetry_count)> - cpuTelemetryCapBits{}; - bool isPlayback = false; - bool isPlaybackPaced = false; - bool isPlaybackRetimed = false; - bool isPlaybackBackpressured = false; - bool isPlaybackResetOldest = false; -}; - -struct PmNsmPresentEvent -{ - uint64_t PresentStartTime; // QPC value of the first event related to the - // Present (D3D9, DXGI, or DXGK Present_Start) - uint32_t ProcessId; // ID of the process that presented - uint32_t ThreadId; // ID of the thread that presented - uint64_t TimeInPresent; // QPC duration between runtime present start and end - uint64_t GPUStartTime; // QPC value when the frame's first DMA packet started - uint64_t ReadyTime; // QPC value when the frame's last DMA packet completed - - uint64_t GPUDuration; // QPC duration during which a frame's DMA packet was - // running on any node - uint64_t GPUVideoDuration; // QPC duration during which a frame's DMA packet was - // running on a video node (if mTrackGPUVideo==true) - uint64_t InputTime; // Earliest QPC value for all keyboard/mouse input used by this frame - uint64_t MouseClickTime; // Earliest QPC value when the mouse was clicked and used by this frame - - // Used to track the application work when Intel XeSS-FG is enabled - uint64_t AppPropagatedPresentStartTime; // Propogated QPC value of the first event related to the Present (D3D9, DXGI, or DXGK Present_Start) - uint64_t AppPropagatedTimeInPresent; // Propogated QPC duration of the Present call (only applicable for D3D9/DXGI) - uint64_t AppPropagatedGPUStartTime; // Propogated QPC value when the frame's first DMA packet started - uint64_t AppPropagatedReadyTime; // Propogated QPC value when the frame's last DMA packet completed - uint64_t AppPropagatedGPUDuration; // Propogated QPC duration during which a frame's DMA packet was running on - uint64_t AppPropagatedGPUVideoDuration; // Propogated QPC duration during which a frame's DMA packet was running on - // a video node (if mTrackGPUVideo==true) - - uint64_t AppSleepStartTime; // QPC value of app sleep start time provided by Intel App Provider - uint64_t AppSleepEndTime; // QPC value of app sleep end time provided by Intel App Provider - uint64_t AppSimStartTime; // QPC value of app sim start time provided by Intel App Provider - uint64_t AppSimEndTime; // QPC value of app sim end time provided by Intel App Provider - uint64_t AppRenderSubmitStartTime; // QPC value of app render submit start time provided by Intel App Provider - uint64_t AppRenderSubmitEndTime; // QPC value of app render submit end time provided by Intel App Provider - uint64_t AppPresentStartTime; // QPC value of app present start time provided by Intel App Provider - uint64_t AppPresentEndTime; // QPC value of app present end time provided by Intel App Provider - uint64_t AppInputTime; // QPC value of app input time provided by Intel App Provider - InputDeviceType AppInputType; // Input type provided by Intel App Provider - - uint64_t PclInputPingTime; // QPC value of input ping time provided by the PC Latency ETW event - uint64_t PclSimStartTime; // QPC value of app sim start time provided by the PC Latency ETW event - uint64_t FlipDelay; // QPC timestamp delta of FlipDelay calculated using the NV DisplayDriver FlipRequest event. - uint32_t FlipToken; // Flip token from the NV DisplayDriver FlipRequest event. - - // Extra present parameters obtained through DXGI or D3D9 present - uint64_t SwapChainAddress; - int32_t SyncInterval; - uint32_t PresentFlags; - - // (FrameType, DisplayedQPC) for each time the frame was displayed - uint64_t Displayed_ScreenTime[16]; - FrameType Displayed_FrameType[16]; - uint32_t DisplayedCount; - - // Keys used to index into PMTraceConsumer's tracking data structures: - uint64_t CompositionSurfaceLuid; // mPresentByWin32KPresentHistoryToken - uint64_t Win32KPresentCount; // mPresentByWin32KPresentHistoryToken - uint64_t Win32KBindId; // mPresentByWin32KPresentHistoryToken - uint64_t DxgkPresentHistoryToken; // mPresentByDxgkPresentHistoryToken - uint64_t - DxgkPresentHistoryTokenData; // mPresentByDxgkPresentHistoryTokenData - uint64_t DxgkContext; // mPresentByDxgkContext - uint64_t Hwnd; // mLastPresentByWindow - uint32_t QueueSubmitSequence; // mPresentBySubmitSequence - uint32_t RingIndex; // mTrackedPresents and mCompletedPresents - - // Properties deduced by watching events through present pipeline - uint32_t DestWidth; - uint32_t DestHeight; - uint32_t DriverThreadId; - - uint32_t FrameId; // ID for the logical frame that this Present is associated with. - uint32_t AppFrameId; // Application provided frame ID - uint32_t PclFrameId; // PC Latency provided frame ID - - Runtime Runtime; - PresentMode PresentMode; - PresentResult FinalState; - InputDeviceType InputType; - FrameType FrameType; - - bool SupportsTearing; - bool WaitForFlipEvent; - bool WaitForMPOFlipEvent; - bool SeenDxgkPresent; - bool SeenWin32KEvents; - bool SeenInFrameEvent; // This present has gotten a Win32k TokenStateChanged - // event into InFrame state - bool GpuFrameCompleted; // This present has already seen an event that caused - // GpuTrace::CompleteFrame() to be called. - bool IsCompleted; // All expected events have been observed - bool IsLost; // This PresentEvent was found in an unexpected state or is too - // old - - // Whether this PresentEvent is currently stored in - // PMTraceConsumer::mPresentsWaitingForDWM - bool PresentInDwmWaitingStruct; - - // QPC time of last presented frame - uint64_t last_present_qpc; - // QPC time of the last displayed frame - uint64_t last_displayed_qpc; - - // Application name - char application[MAX_PATH]; -}; - -struct PmNsmFrameData -{ - PmNsmPresentEvent present_event; - PresentMonPowerTelemetryInfo power_telemetry; - CpuTelemetryInfo cpu_telemetry; -}; diff --git a/IntelPresentMon/PresentMonUtils/StringUtils.cpp b/IntelPresentMon/PresentMonUtils/StringUtils.cpp deleted file mode 100644 index eea1e5d3..00000000 --- a/IntelPresentMon/PresentMonUtils/StringUtils.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include -//#include -//#include -#include "StringUtils.h" - -std::string ConvertFromWideString(std::wstring wide_string) { - // Determine the required size of the buffer - int bufferSize = WideCharToMultiByte(CP_UTF8, 0, wide_string.c_str(), -1, - nullptr, 0, nullptr, nullptr); - // Convert the wide string to UTF-8 - std::string str(bufferSize, '\0'); - WideCharToMultiByte(CP_UTF8, 0, wide_string.c_str(), -1, &str[0], bufferSize, - nullptr, nullptr); - // Remove the null-terminator from the resulting string - str.pop_back(); - - return str; -} diff --git a/IntelPresentMon/PresentMonUtils/StringUtils.h b/IntelPresentMon/PresentMonUtils/StringUtils.h deleted file mode 100644 index 6b5511a5..00000000 --- a/IntelPresentMon/PresentMonUtils/StringUtils.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once -#include - -std::string ConvertFromWideString(std::wstring wide_string); \ No newline at end of file diff --git a/IntelPresentMon/SampleStreamerClient/Logging.cpp b/IntelPresentMon/SampleStreamerClient/Logging.cpp deleted file mode 100644 index b9391228..00000000 --- a/IntelPresentMon/SampleStreamerClient/Logging.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include - -namespace pmon::util::log -{ - // this is injected into to util::log namespace and hooks into that system - // not using logging yet in the SampleStreamClient so just return empty pointer - std::shared_ptr GetDefaultChannel() noexcept - { - return {}; - } -} \ No newline at end of file diff --git a/IntelPresentMon/SampleStreamerClient/SampleStreamerClient.cpp b/IntelPresentMon/SampleStreamerClient/SampleStreamerClient.cpp deleted file mode 100644 index 710c0adb..00000000 --- a/IntelPresentMon/SampleStreamerClient/SampleStreamerClient.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -// SampleStreamerClient.cpp : This file contains the 'main' function. Program execution begins and ends there. - -#include -#include -#include -#include - -#include "..\Streamer\StreamClient.h" - -#include "../CommonUtilities/log/GlogShim.h" - -uint32_t kClientLoopCount = 50; - -int main(int argc, TCHAR* argv[]) -{ - if (argc < 2) { - LOG(ERROR) << "Mapfile name needed."; - } - - std::string mapfile_name = argv[1]; - LOG(INFO) << "Mapfile name is " << mapfile_name; - - StreamClient client; - - try{ - client.Initialize(std::move(mapfile_name)); - } catch (const std::exception& e) { - LOG(ERROR) << " a standard exception was caught, with message '" - << e.what() << "'\n"; - return 0; - } - - uint32_t count = 0; - - while (count < kClientLoopCount) { - - PmNsmFrameData* data; - - data = client.ReadLatestFrame(); - if (data != nullptr) { - try { - LOG(INFO) - << "\nSampleStreamerClient read out ...\n" - << data->present_event.ProcessId << ", " - << data->present_event.SyncInterval << ", " - << data->present_event.PresentFlags << ", " << std::hex - << data->present_event.SwapChainAddress; - } catch (const std::exception& e) { - LOG(ERROR) - << " a standard exception was caught, with message '" - << e.what() << "'\n"; - } - } - count++; - } -} \ No newline at end of file diff --git a/IntelPresentMon/SampleStreamerClient/SampleStreamerClient.vcxproj b/IntelPresentMon/SampleStreamerClient/SampleStreamerClient.vcxproj deleted file mode 100644 index 55015663..00000000 --- a/IntelPresentMon/SampleStreamerClient/SampleStreamerClient.vcxproj +++ /dev/null @@ -1,116 +0,0 @@ - - - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {57abaf22-837a-4da3-9c92-1ba37ccfa550} - SampleStreamerClient - 10.0 - - - - Application - true - v143 - MultiByte - x64 - - - Application - false - v143 - true - MultiByte - x64 - - - - - - - - - - - - - - - - - - - true - false - false - --checks=clang-analyzer-*,google-* - - - false - false - false - --checks=clang-analyzer-*,google-* - - - - - - - - Level3 - true - true - MultiThreadedDebug - None - stdcpplatest - - - Console - true - shlwapi.lib;%(AdditionalDependencies) - - - - - Level3 - true - true - true - true - MultiThreaded - stdcpplatest - - - Console - true - true - true - shlwapi.lib;%(AdditionalDependencies) - - - - - - - - - {08a704d8-ca1c-45e9-8ede-542a1a43b53e} - - - {bf43064b-01f0-4c69-91fb-c2122baf621d} - - - - - - \ No newline at end of file diff --git a/IntelPresentMon/SampleStreamerClient/SampleStreamerClient.vcxproj.filters b/IntelPresentMon/SampleStreamerClient/SampleStreamerClient.vcxproj.filters deleted file mode 100644 index f81c3464..00000000 --- a/IntelPresentMon/SampleStreamerClient/SampleStreamerClient.vcxproj.filters +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/IntelPresentMon/Streamer/NamedSharedMemory.cpp b/IntelPresentMon/Streamer/NamedSharedMemory.cpp deleted file mode 100644 index 0669f573..00000000 --- a/IntelPresentMon/Streamer/NamedSharedMemory.cpp +++ /dev/null @@ -1,364 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#include -#include "NamedSharedMemory.h" -#include - -#include "../CommonUtilities/log/GlogShim.h" - -#define PAGE 4096 - -template -constexpr T align(T what, U to) { - return (what + to - 1) & ~(to - 1); -} - -NamedSharedMem::NamedSharedMem() - : mapfile_handle_(NULL), - data_offset_base_(sizeof(NamedSharedMemoryHeader)), - header_(NULL), - buf_(NULL), - alloc_granularity_(0), - refcount_(0), - buf_created_(false), - buf_size_(0){}; - - -NamedSharedMem::NamedSharedMem(std::string mapfile_name, uint64_t buf_size, - bool isPlayback, - bool isPlaybackPaced, - bool isPlaybackRetimed, - bool isPlaybackBackpressured, - bool isPlaybackResetOldest) - : mapfile_handle_(NULL), - data_offset_base_(sizeof(NamedSharedMemoryHeader)), - header_(NULL), - buf_(NULL), - alloc_granularity_(0), - refcount_(0), - buf_created_(false), - buf_size_(0) { - - SYSTEM_INFO system_info; - GetSystemInfo(&system_info); - - alloc_granularity_ = system_info.dwAllocationGranularity; - - CreateSharedMem(std::move(mapfile_name), buf_size); - - header_->isPlayback = isPlayback; - header_->isPlaybackPaced = isPlaybackPaced; - header_->isPlaybackRetimed = isPlaybackRetimed; - header_->isPlaybackBackpressured = isPlaybackBackpressured; - header_->isPlaybackResetOldest = isPlaybackResetOldest; -}; - -void NamedSharedMem::OutputErrorLog(const char* error_string, - DWORD last_error) { - try { - if (last_error != 0) { - LOG(ERROR) << error_string << last_error << ""; - } - } catch (...) { - LOG(ERROR) << error_string << "unknown last error\n"; - } -} - -HRESULT NamedSharedMem::CreateSharedMem(std::string mapfile_name, uint64_t buf_size) -{ - HRESULT hr = S_OK; - - LOG(INFO) << "Creating NSM: " << mapfile_name; - - if (buf_size == 0) { - LOG(ERROR) << " CreateSharedMem failed with zero buf_size."; - return E_FAIL; - } - - mapfile_name_ = std::move(mapfile_name); - - DWORD buf_size_low = buf_size & 0xFFFFFFFF; - DWORD buf_size_high = (buf_size & 0xFFFFFFFF00000000ULL) >> 32; - - SECURITY_ATTRIBUTES sa = { sizeof(sa) }; - if (ConvertStringSecurityDescriptorToSecurityDescriptorW(L"D:PNO_ACCESS_CONTROLS:(ML;;NW;;;LW)", - SDDL_REVISION_1, &sa.lpSecurityDescriptor, NULL)) - { - - mapfile_handle_ = CreateFileMappingA( - INVALID_HANDLE_VALUE, // use paging file - &sa, // default security - PAGE_READWRITE, // read/write access - buf_size_high, // maximum object size (high-order DWORD) - buf_size_low, // maximum object size (low-order DWORD) - mapfile_name_.c_str()); // name of mapping object - - LocalFree(sa.lpSecurityDescriptor); - } - else { - OutputErrorLog("Failed to set security. Error code: ", GetLastError()); - return E_FAIL; - } - - if (mapfile_handle_ == NULL) { - OutputErrorLog("Could not create file mapping object. Error code: ", - GetLastError()); - return E_FAIL; - } - - buf_ = static_cast(MapViewOfFile(mapfile_handle_, // handle to map object - FILE_MAP_ALL_ACCESS, // read/write permission - 0, - 0, - buf_size)); - - if (buf_ == NULL) { - OutputErrorLog("Could not map view of file. Error code: ", - GetLastError()); - CloseHandle(mapfile_handle_); - return E_FAIL; - } - - - // Populate header info - memset(buf_, 0, buf_size); - - UnmapViewOfFile(buf_); - buf_ = NULL; - - // Always map header - header_ = static_cast(MapViewOfFile(mapfile_handle_, // handle to map object - FILE_MAP_ALL_ACCESS, // write permission - 0, - 0, - PAGE)); - - if (header_ == NULL) { - OutputErrorLog("Could not map view of file. Error code: ", - GetLastError()); - return E_FAIL; - } - - header_->max_entries = (buf_size - sizeof(NamedSharedMemoryHeader)) / sizeof(PmNsmFrameData); - header_->current_write_offset = data_offset_base_; - header_->buf_size = buf_size; - header_->process_active = true; - header_->num_frames_written = 0; - - // Query qpc frequency - if (!QueryPerformanceFrequency(&header_->qpc_frequency)) { - OutputErrorLog("QueryPerformanceFrequency failed with error: ", - GetLastError()); - } - - try { - LOG(INFO) << std::format("Shared mem initialized with size {} bytes.", buf_size); - } catch (...) { - LOG(INFO) << "Shared mem initialized"; - } - - refcount_++; - buf_created_ = true; - buf_size_ = buf_size; - return hr; -} - -void NamedSharedMem::OpenSharedMemView(std::string mapfile_name) -{ - mapfile_name_ = mapfile_name; - - mapfile_handle_ = OpenFileMapping( - FILE_MAP_READ | FILE_MAP_WRITE, // read/write access - FALSE, // do not inherit the name - mapfile_name.c_str()); // name of mapping object - - if (mapfile_handle_ == NULL) - { - OutputErrorLog("Could not open file mapping object. Error code: ", - GetLastError()); - throw std::runtime_error{"failed open file mapping object"}; - } - else { - try { - LOG(INFO) << std::format("Client opened mapfile from {}", - mapfile_name); - } catch (...) { - LOG(INFO) << "Client opened mapfile\n"; - } - } - - // Map header - header_ = static_cast(MapViewOfFile(mapfile_handle_, // handle to map object - FILE_MAP_READ | FILE_MAP_WRITE , // read permission - 0, - 0, - PAGE)); - - if (header_ == NULL) { - OutputErrorLog("Could not map view of file. Error code: ", - GetLastError()); - return; - } - - if (header_->buf_size > kBufSize) { - OutputErrorLog("Named Shared Memory header is incorrect.", - 0); - return; - } - - buf_ = static_cast(MapViewOfFile(mapfile_handle_, // handle to map object - FILE_MAP_READ, // read permission - 0, - 0, - header_->buf_size)); - - if (buf_ == NULL) { - OutputErrorLog("Could not map view of file. Error code: ", - GetLastError()); - } -} - - -NamedSharedMem::~NamedSharedMem() { - if (buf_ != NULL) { - UnmapViewOfFile(buf_); - buf_ = NULL; - } - - if (header_ != NULL) { - UnmapViewOfFile(header_); - header_ = NULL; - } - - if (mapfile_handle_ != NULL) { - CloseHandle(mapfile_handle_); - mapfile_handle_ = NULL; - } -} - -void NamedSharedMem::WriteFrameData(PmNsmFrameData* data) { - uint64_t data_size_bytes = sizeof(PmNsmFrameData); - - uint64_t write_to_offset = header_->current_write_offset; - if (write_to_offset + data_size_bytes >= header_->buf_size) { - // not enough space, move to front of the buffer - write_to_offset = data_offset_base_; - } - - uint64_t map_offset = (write_to_offset / alloc_granularity_) * alloc_granularity_; - DWORD map_offset_low = map_offset & 0xFFFFFFFF; - DWORD map_offset_high = (map_offset & 0xFFFFFFFF00000000ULL) >> 32; - - uint64_t in_map_offset = write_to_offset % alloc_granularity_; - - SIZE_T num_bytes_to_map = ((in_map_offset + sizeof(PmNsmFrameData)) > alloc_granularity_) ? 2 * alloc_granularity_ : alloc_granularity_; - - num_bytes_to_map = num_bytes_to_map > header_->buf_size ? header_->buf_size - : num_bytes_to_map; - - // Map data region - buf_ = static_cast(MapViewOfFile(mapfile_handle_, // handle to map object - FILE_MAP_WRITE, // write permission - map_offset_high, - map_offset_low, - num_bytes_to_map)); - - if (buf_ == NULL) { - OutputErrorLog("Could not map view of file. Error code: ", - GetLastError()); - return; - } - - std::memcpy(static_cast(static_cast(buf_) + in_map_offset), - static_cast(data), sizeof(PmNsmFrameData)); - - if (IsFull()) { - header_->head_idx = (header_->head_idx + 1) % header_->max_entries; - } - - header_->tail_idx = (header_->tail_idx + 1) % header_->max_entries; - header_->current_write_offset = write_to_offset + sizeof(PmNsmFrameData); - header_->num_frames_written++; - - LARGE_INTEGER qpc; - QueryPerformanceCounter(&qpc); - //LOG(INFO) << data->present_event.ProcessId << "," << qpc.QuadPart - // << "," << data->present_event.SwapChainAddress << "," - // << "," << data->present_event.PresentStartTime << "," - // << (int)data->present_event.FinalState << "," - // << data->present_event.ScreenTime << "," << header_->tail_idx - // << "," << header_->head_idx << "," << header_->num_frames_written; - - UnmapViewOfFile(buf_); - buf_ = NULL; -} - -// Pop the first frame and move the head_idx -void NamedSharedMem::DequeueFrameData() { - if (!IsEmpty()) { - header_->head_idx = (header_->head_idx + 1) % header_->max_entries; - } -} - -uint64_t NamedSharedMem::GetNumServiceWrittenFrames() { - return header_->num_frames_written; -} - -bool NamedSharedMem::IsFull() { - if (header_ == nullptr) { - return false; - } - - if (((header_->tail_idx+1) % header_->max_entries) == (header_->head_idx)) { - return true; - } - - return false; -} - -bool NamedSharedMem::IsEmpty() { - if ((header_ == nullptr) || - (header_->head_idx == header_->tail_idx)) { - return true; - } - - return false; -} - -bool NamedSharedMem::HasUninitializedFrames() -{ - if (header_ == nullptr) { - return true; - } - return header_->max_entries > header_->num_frames_written; -} - -void NamedSharedMem::NotifyProcessKilled() { - header_->process_active = false; - FlushViewOfFile(header_, sizeof(NamedSharedMemoryHeader)); -} - -void NamedSharedMem::RecordFirstFrameTime(uint64_t start_qpc) { - header_->start_qpc = start_qpc; - // Query qpc frequency - if (!QueryPerformanceFrequency(&header_->qpc_frequency)) { - OutputErrorLog("QueryPerformanceFrequency failed with error: ", - GetLastError()); - } -} - -uint64_t NamedSharedMem::RecordAndGetLastDisplayedQpc(uint64_t qpc) { - uint64_t last_qpc = ULLONG_MAX; - last_qpc = header_->last_displayed_qpc; - header_->last_displayed_qpc = qpc; - return last_qpc; -} - -void NamedSharedMem::WriteTelemetryCapBits( - std::bitset(GpuTelemetryCapBits::gpu_telemetry_count)> - gpu_telemetry_cap_bits, - std::bitset(CpuTelemetryCapBits::cpu_telemetry_count)> - cpu_telemetry_cap_bits) { - header_->gpuTelemetryCapBits = gpu_telemetry_cap_bits; - header_->cpuTelemetryCapBits = cpu_telemetry_cap_bits; -} \ No newline at end of file diff --git a/IntelPresentMon/Streamer/NamedSharedMemory.h b/IntelPresentMon/Streamer/NamedSharedMemory.h deleted file mode 100644 index f0b9aa2f..00000000 --- a/IntelPresentMon/Streamer/NamedSharedMemory.h +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once -#include - -#include "../PresentMonUtils/StreamFormat.h" - -static const uint64_t kBufSize = 65536 * 60; -static const std::string kGlobalPrefix = "Global\\NamedSharedMem_"; - -class NamedSharedMem { - public: - NamedSharedMem(); - NamedSharedMem(std::string mapfile_name, uint64_t buf_size, - bool isPlayback, - bool isPlaybackPaced, - bool isPlaybackRetimed, - bool isPlaybackBackpressured, - bool isPlaybackResetOldest); - ~NamedSharedMem(); - NamedSharedMem(const NamedSharedMem& t) = delete; - NamedSharedMem& operator=(const NamedSharedMem& t) = delete; - - std::string GetMapFileName() { return mapfile_name_; } - HANDLE GetMapFileHandle() { return mapfile_handle_; }; - // Get base offset of the frame data in shared memory. Normally this is - // sizeof(NamedSharedMemoryHeader) - uint32_t GetBaseOffset() { return data_offset_base_; }; - void* GetBuffer() { return buf_; }; - // Server only method to write frame data - void WriteFrameData(PmNsmFrameData* data); - // Server only method to write the telemetry bit caps to - // the header - void WriteTelemetryCapBits( - std::bitset(GpuTelemetryCapBits::gpu_telemetry_count)> - gpu_telemetry_cap_bits, - std::bitset(CpuTelemetryCapBits::cpu_telemetry_count)> - cpu_telemetry_cap_bits); - - // Client only method to pop already read frame data - void DequeueFrameData(); - // Client only method to get the number of frames written by the - // service - uint64_t GetNumServiceWrittenFrames(); - // Client method to open a view into the shared mem - void OpenSharedMemView(std::string mapfile_name); - void NotifyProcessKilled(); - // TODO(jtseng2): header_ is client used only. Separate GetHeader() API from - // server. - const NamedSharedMemoryHeader* GetHeader() { return header_; }; - // indicates whether the ring buffer has available write space - // only meaninful in a backpressured (typically replay) scenario - bool IsFull(); - // indicates whether the ring buffer has any unconsumed frames - // only meaninful in a backpressured (typically replay) scenario - bool IsEmpty(); - // indicates whether every element in the ring buffer contains valid frame information - // typically, this is only false until the buffer has wrapped once - bool HasUninitializedFrames(); - bool IsNSMCreated() { return buf_created_; }; - - // Helper frunctions for generating frame statistics - // Cache first frame's qpc time as the start time - void RecordFirstFrameTime(uint64_t start_qpc); - // Cache last displayed frame id during WriteFrameData - uint64_t RecordAndGetLastDisplayedQpc(uint64_t qpc); - void IncrementRefcount() { refcount_++;}; - void DecrementRefcount() { refcount_--; }; - int GetRefCount() { return refcount_; }; - uint64_t GetBufSize() { return buf_size_; }; - - private: - // Server method to create a shared mem in buf_size bytes - HRESULT CreateSharedMem(std::string mapfile_name, uint64_t buf_size); - void OutputErrorLog(const char* error_string, DWORD last_error); - std::string mapfile_name_; - HANDLE mapfile_handle_; - uint32_t data_offset_base_; - NamedSharedMemoryHeader* header_; - void* buf_; - // allocation granularity obtained from GetSystemInfo. MapViewOfFile offset - // must be multiple of alloc_granularity - size_t alloc_granularity_; - int refcount_; - bool buf_created_; - uint64_t buf_size_; -}; \ No newline at end of file diff --git a/IntelPresentMon/Streamer/StreamClient.cpp b/IntelPresentMon/Streamer/StreamClient.cpp deleted file mode 100644 index 13dd765b..00000000 --- a/IntelPresentMon/Streamer/StreamClient.cpp +++ /dev/null @@ -1,785 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#include "StreamClient.h" -#include "../PresentMonUtils/QPCUtils.h" -#include "../PresentMonUtils/PresentDataUtils.h" - -#include "../CommonUtilities/log/GlogShim.h" - -StreamClient::StreamClient() - : initialized_(false), - next_dequeue_idx_(0), - recording_frame_data_(false), - current_dequeue_frame_num_(0) {} - -StreamClient::StreamClient(std::string mapfile_name, bool is_etl_stream_client) - : next_dequeue_idx_(0), - recording_frame_data_(false), - current_dequeue_frame_num_(0) { - Initialize(std::move(mapfile_name)); -} - -StreamClient::~StreamClient() { -} - -void StreamClient::Initialize(std::string mapfile_name) { - shared_mem_view_ = std::make_unique(); - shared_mem_view_->OpenSharedMemView(mapfile_name); - mapfile_name_ = std::move(mapfile_name); - - // Query qpc frequency - if (!QueryPerformanceFrequency(&qpcFrequency_)) { - pmlog_error("QueryPerformanceFrequency failed").hr(); - } - - initialized_ = true; - LOG(INFO) << "Stream client initialized."; -} - -void StreamClient::CloseSharedMemView() { shared_mem_view_.reset(nullptr); } - -PmNsmFrameData* StreamClient::ReadLatestFrame() { - PmNsmFrameData* data = nullptr; - - if (shared_mem_view_ == nullptr) { - LOG(ERROR) - << "Shared mem view is null. Initialze client with mapfile name."; - return data; - } - - if (shared_mem_view_->IsEmpty()) { - LOG(INFO) << "Shared mem view is empty. start ETW tracing or wait for " - "more data"; - return data; - } - - uint64_t index = GetLatestFrameIndex(); - - return ReadFrameByIdx(index, true); -} - -PmNsmFrameData* StreamClient::ReadFrameByIdx(uint64_t frame_idx, bool checked) { - PmNsmFrameData* data = nullptr; - uint64_t read_offset = 0; - - if (shared_mem_view_ == nullptr) { - LOG(ERROR) - << "Shared mem view is null. Initialze client with mapfile name."; - return nullptr; - } - - if (shared_mem_view_->IsEmpty()) { - if (checked) { - pmlog_warn("Trying to read from empty nsm ring").pmwatch(frame_idx); - } - return nullptr; - } - - auto p_header = shared_mem_view_->GetHeader(); - - if (!p_header->process_active) { - pmlog_warn("Process is not active. Shared mem view to be destroyed."); - CloseSharedMemView(); - return nullptr; - } - - if (frame_idx > p_header->max_entries - 1) { - pmlog_warn("Bad out of bounds index in circular buffer").pmwatch(frame_idx); - return nullptr; - } - - if (checked) { - // when ring is full, every frame is valid for reading - // in realtime usage (head not updated on read), this is always the case once the ring wraps once - if (!shared_mem_view_->IsFull()) { - bool invalid = false; - // when ring is not full, there are two cases (head before tail, or vice versa) - // head before tail (invalid range potentially non-contiguous/wrapping) - if (p_header->tail_idx > p_header->head_idx) { - if (frame_idx >= p_header->tail_idx || frame_idx < p_header->head_idx) { - invalid = true; - } - } - else { // tail before head (invalid range contiguous in middle) - if (frame_idx >= p_header->tail_idx && frame_idx < p_header->head_idx) { - invalid = true; - } - } - if (invalid) { - pmlog_warn("Invalid frame idx").pmwatch(frame_idx); - return nullptr; - } - } - } - - read_offset = frame_idx * sizeof(PmNsmFrameData) + shared_mem_view_->GetBaseOffset(); - - data = reinterpret_cast( - static_cast((shared_mem_view_->GetBuffer())) + read_offset); - - return data; -} - -void StreamClient::PeekNextFrames(const PmNsmFrameData** pNextFrame, - const PmNsmFrameData** pNextDisplayedFrame) -{ - *pNextFrame = nullptr; - *pNextDisplayedFrame = nullptr; - if (recording_frame_data_) { - auto nsm_view = GetNamedSharedMemView(); - auto nsm_hdr = nsm_view->GetHeader(); - if (!nsm_hdr->process_active) { - // Service destroyed the named shared memory. - return; - } - - uint64_t peekIndex{ next_dequeue_idx_ }; - auto pTempFrameData = ReadFrameByIdx(peekIndex, false); - *pNextFrame = pTempFrameData; - while (pTempFrameData) { - if (pTempFrameData->present_event.FinalState == PresentResult::Presented && - pTempFrameData->present_event.DisplayedCount > 0) { - *pNextDisplayedFrame = pTempFrameData; - return; - } - // advance to next frame with circular buffer wrapping behavior - peekIndex = (peekIndex + 1) % nsm_view->GetHeader()->max_entries; - pTempFrameData = ReadFrameByIdx(peekIndex, false); - } - } - return; -} - -bool StreamClient::IsAppPresentedFrame(const PmNsmFrameData* frame) const { - if (!frame || frame->present_event.DisplayedCount == 0) { - return false; - } - size_t lastDisplayedIndex = frame->present_event.DisplayedCount - 1; - return frame->present_event.Displayed_FrameType[lastDisplayedIndex] == FrameType::NotSet || - frame->present_event.Displayed_FrameType[lastDisplayedIndex] == FrameType::Application; -} - -bool StreamClient::IsDisplayedFrame(const PmNsmFrameData* frame) const { - return frame && frame->present_event.FinalState == PresentResult::Presented && - frame->present_event.DisplayedCount > 0; -} - -bool StreamClient::IsAppDisplayedFrame(const PmNsmFrameData* frame) const { - if (!frame || frame->present_event.DisplayedCount == 0) { - return false; - } - size_t lastDisplayedIndex = frame->present_event.DisplayedCount - 1; - return frame->present_event.FinalState == PresentResult::Presented && - (frame->present_event.Displayed_FrameType[lastDisplayedIndex] == FrameType::NotSet || - frame->present_event.Displayed_FrameType[lastDisplayedIndex] == FrameType::Application); -} - -// Function to peek previous frames from the current frame -void StreamClient::PeekPreviousFrames(const PmNsmFrameData** pFrameDataOfLastPresented, - const PmNsmFrameData** pFrameDataOfLastAppPresented, - const PmNsmFrameData** pFrameDataOfLastDisplayed, - const PmNsmFrameData** pFrameDataOfLastAppDisplayed, - const PmNsmFrameData** pFrameDataOfPreviousAppFrameOfLastAppDisplayed) -{ - *pFrameDataOfLastPresented = nullptr; - *pFrameDataOfLastAppPresented = nullptr; - *pFrameDataOfLastDisplayed = nullptr; - *pFrameDataOfLastAppDisplayed = nullptr; - *pFrameDataOfPreviousAppFrameOfLastAppDisplayed = nullptr; - if (recording_frame_data_) { - auto nsm_view = GetNamedSharedMemView(); - auto nsm_hdr = nsm_view->GetHeader(); - if (!nsm_hdr->process_active) { - // Service destroyed the named shared memory. - return; - } - - // if nsm is not fully initialized, last frame to read is at idx 0, so stop condition is when we wrap to last idx - // otherwise, last frame to read is the one that follows the latest one (which is the oldest one, next one to be overwritten) - const auto stopIdx = nsm_view->HasUninitializedFrames() ? nsm_hdr->max_entries - 1 : this->GetLatestFrameIndex(); - // here, peek index is starting at the frame which follows the current one - // this frame is guaranteed to be valid since peekNext call prior to this would exit early if it were not - uint64_t peekIndex{ next_dequeue_idx_ }; - uint32_t numFramesTraversed = 0; - while (true) { - // seek back one frame with ring buffer cycling behavior - peekIndex = peekIndex == 0 ? (nsm_hdr->max_entries - 1) : (peekIndex - 1); - // exit when we hit stopIdx (which is the first frame encountered we should *not* use) - if (peekIndex == stopIdx) { - return; - } - numFramesTraversed++; - // TODO: we can improve checking by independently enabling for forward and backward cases - auto pTempFrameData = ReadFrameByIdx(peekIndex, false); - // We need to traverse back two frames from the next_dequeue_idx to - // get to the start of the previous frames - if (numFramesTraversed > 1) { - if (pTempFrameData) { - if (numFramesTraversed == 2) { - // If we made it here we were able to go back two frames - // from the current next_deque_index. This is the frame - // data of the last presented frame before the current frame. - *pFrameDataOfLastPresented = pTempFrameData; - } - if (*pFrameDataOfLastAppPresented == nullptr) { - // If we haven't found the last app presented frame check to see if - // the current one is presented. This could point to the same frame - // as the last presented one above. - auto displayedCount = pTempFrameData->present_event.DisplayedCount; - if (displayedCount > 0) { - if (pTempFrameData->present_event.Displayed_FrameType[displayedCount - 1] == FrameType::NotSet || - pTempFrameData->present_event.Displayed_FrameType[displayedCount - 1] == FrameType::Application) { - *pFrameDataOfLastAppPresented = pTempFrameData; - } - } else { - // If the displayed count is 0 we assume this is an app frame - *pFrameDataOfLastAppPresented = pTempFrameData; - } - } - if (*pFrameDataOfLastDisplayed == nullptr) { - // If we haven't found the last displayed frame check to see if - // the current one is presented. This could point to the same frame - // as the last presented one above. - if (pTempFrameData->present_event.FinalState == PresentResult::Presented && - pTempFrameData->present_event.DisplayedCount > 0) { - *pFrameDataOfLastDisplayed = pTempFrameData; - } - } - if (*pFrameDataOfLastAppDisplayed == nullptr) { - // If we haven't found the last displayed app frame check to see if - // the current one is presented. This could point to the same frame - // as the last displayed frame above. - if (pTempFrameData->present_event.DisplayedCount > 0) { - size_t lastDisplayedIndex = pTempFrameData->present_event.DisplayedCount - 1; - if (pTempFrameData->present_event.FinalState == PresentResult::Presented && - (pTempFrameData->present_event.Displayed_FrameType[lastDisplayedIndex] == FrameType::NotSet || - pTempFrameData->present_event.Displayed_FrameType[lastDisplayedIndex] == FrameType::Application)) { - *pFrameDataOfLastAppDisplayed = pTempFrameData; - } - } - } else { - // If we have found the last displayed app frame we now need to find the previously - // presented frame to determine the cpu start time. The frame does not need to be - // displayed but must be from the application - if (pTempFrameData->present_event.DisplayedCount > 0) { - size_t lastDisplayedIndex = pTempFrameData->present_event.DisplayedCount - 1; - if (pTempFrameData->present_event.Displayed_FrameType[lastDisplayedIndex] == FrameType::NotSet || - pTempFrameData->present_event.Displayed_FrameType[lastDisplayedIndex] == FrameType::Application) { - *pFrameDataOfPreviousAppFrameOfLastAppDisplayed = pTempFrameData; - } - } else { - // If the displayed count is 0 we assume this is an app frame - *pFrameDataOfPreviousAppFrameOfLastAppDisplayed = pTempFrameData; - } - } - if (*pFrameDataOfLastPresented != nullptr && - *pFrameDataOfLastAppPresented != nullptr && - *pFrameDataOfLastDisplayed != nullptr && - *pFrameDataOfPreviousAppFrameOfLastAppDisplayed != nullptr && - *pFrameDataOfLastAppDisplayed != nullptr) { - // We have found all the frames we need to find. We can exit early. - return; - } - } - else { - // The read frame data is null. - // TODO log this as the index is supposed to be valid! - return; - } - } - } - } - return; -} - - -PM_STATUS StreamClient::ConsumePtrToNextNsmFrameData(const PmNsmFrameData** pNsmData, - const PmNsmFrameData** pNextFrame, - const PmNsmFrameData** pFrameDataOfNextDisplayed, - const PmNsmFrameData** pFrameDataOfLastPresented, - const PmNsmFrameData** pFrameDataOfLastAppPresented, - const PmNsmFrameData** pFrameDataOfLastDisplayed, - const PmNsmFrameData** pFrameDataOfLastAppDisplayed, - const PmNsmFrameData** pFrameDataOfPreviousAppFrameOfLastAppDisplayed) -{ - if (pNsmData == nullptr || pNextFrame == nullptr || pFrameDataOfNextDisplayed == nullptr || - pFrameDataOfLastPresented == nullptr || pFrameDataOfLastAppPresented == nullptr || - pFrameDataOfLastDisplayed == nullptr || pFrameDataOfLastAppDisplayed == nullptr || - pFrameDataOfPreviousAppFrameOfLastAppDisplayed == nullptr) { - return PM_STATUS::PM_STATUS_FAILURE; - } - - // nullify point so that if we exit early it will be null - *pNsmData = nullptr; - - auto nsm_view = GetNamedSharedMemView(); - auto nsm_hdr = nsm_view->GetHeader(); - if (!nsm_hdr->process_active) { - // Service destroyed the named shared memory. - return PM_STATUS::PM_STATUS_INVALID_PID; - } - - if (recording_frame_data_ == false) { - // Get the current number of frames written and set it as the current - // dequeue frame number. This will be used to track data overruns if - // the client does not read data fast enough. - recording_frame_data_ = true; - if (nsm_hdr->isPlaybackResetOldest) { - // during playback it is desirable to start at the very first frame even if we start consuming late - if (nsm_view->IsFull()) { - // if nsm is full, we have to dequeue all frames currently in the buffer - current_dequeue_frame_num_ = nsm_hdr->num_frames_written - nsm_hdr->max_entries; - } - else { - // if nsm is not full, then we must be in the initial state so set to zero (start from the beginning) - current_dequeue_frame_num_ = 0; - } - // always start from the head (oldest frame) - next_dequeue_idx_ = nsm_hdr->head_idx; - } - else { - // at start or after overrun, reset to most recent frame data - current_dequeue_frame_num_ = nsm_hdr->num_frames_written; - next_dequeue_idx_ = (nsm_hdr->tail_idx + 1) % nsm_hdr->max_entries; - } - } - - // Check to see if the number of pending read frames is greater - // than the maximum number of entries. If so we have lost frame - // data - uint64_t num_pending_frames = CheckPendingReadFrames(); - if (num_pending_frames > nsm_hdr->max_entries) { - recording_frame_data_ = false; - return PM_STATUS::PM_STATUS_SUCCESS; - } - else if (num_pending_frames < 2) { - // we need at least 2 pending frames to enable peek-ahead - return PM_STATUS::PM_STATUS_SUCCESS; - } - - // Set the rest of the incoming frame pointers in - // preparation for the various frame data peeks and - // reads - *pNextFrame = nullptr; - *pFrameDataOfNextDisplayed = nullptr; - *pFrameDataOfLastPresented = nullptr; - *pFrameDataOfLastAppPresented = nullptr; - *pFrameDataOfLastDisplayed = nullptr; - *pFrameDataOfLastAppDisplayed = nullptr; - *pFrameDataOfPreviousAppFrameOfLastAppDisplayed = nullptr; - - // First read the current frame. next_dequeue_idx_ sits - // at next frame we need to dequeue. - *pNsmData = ReadFrameByIdx(next_dequeue_idx_, true); - if (*pNsmData) { - // Good so far. Save off the queue index in case - // we need to reset - auto previous_dequeue_idx = next_dequeue_idx_; - // Increment the index to the next frame to dequeue as PeekNextDisplayedFrame expects - // the frame to be incremented. Can change this when done debugging so we don't have to - // reset the dequeue index. - next_dequeue_idx_ = (next_dequeue_idx_ + 1) % nsm_hdr->max_entries; - PeekNextFrames(pNextFrame, pFrameDataOfNextDisplayed); - if (*pFrameDataOfNextDisplayed == nullptr) { - // We were unable to get the next displayed frame. It might not have been displayed - // yet. Reset the next_dequeue_idx back to where we first started. - next_dequeue_idx_ = previous_dequeue_idx; - // Also reset the current and next frame data pointers - *pNsmData = nullptr; - *pNextFrame = nullptr; - return PM_STATUS::PM_STATUS_SUCCESS; - } - PeekPreviousFrames( - pFrameDataOfLastPresented, - pFrameDataOfLastAppPresented, - pFrameDataOfLastDisplayed, - pFrameDataOfLastAppDisplayed, - pFrameDataOfPreviousAppFrameOfLastAppDisplayed); - - current_dequeue_frame_num_++; - if (nsm_hdr->isPlaybackBackpressured) { - nsm_view->DequeueFrameData(); - } - return PM_STATUS::PM_STATUS_SUCCESS; - } - else { - return PM_STATUS::PM_STATUS_FAILURE; - } -} - -void StreamClient::CopyFrameData(uint64_t start_qpc, - const PmNsmFrameData* src_frame, - GpuTelemetryBitset gpu_telemetry_cap_bits, - CpuTelemetryBitset cpu_telemetry_cap_bits, - PM_FRAME_DATA* dst_frame) { - memset(dst_frame, 0, sizeof(PM_FRAME_DATA)); - dst_frame->qpc_time = src_frame->present_event.PresentStartTime; - - dst_frame->ms_between_presents = - QpcDeltaToMs(src_frame->present_event.PresentStartTime - - src_frame->present_event.last_present_qpc, - GetQpcFrequency()); - - strncpy_s(dst_frame->application, src_frame->present_event.application, _TRUNCATE); - - dst_frame->process_id = src_frame->present_event.ProcessId; - dst_frame->swap_chain_address = src_frame->present_event.SwapChainAddress; - strncpy_s(dst_frame->runtime, - RuntimeToString(src_frame->present_event.Runtime), _TRUNCATE); - dst_frame->sync_interval = src_frame->present_event.SyncInterval; - dst_frame->present_flags = src_frame->present_event.PresentFlags; - - if (src_frame->present_event.FinalState == PresentResult::Presented) { - dst_frame->dropped = false; - // If the last_displayed_qpc is zero this means there has not been - // displayed frame. - if (src_frame->present_event.last_displayed_qpc > 0) { - dst_frame->ms_between_display_change = - QpcDeltaToMs(src_frame->present_event.Displayed_ScreenTime[0] - - src_frame->present_event.last_displayed_qpc, - GetQpcFrequency()); - } - dst_frame->ms_until_displayed = QpcDeltaToMs(src_frame->present_event.Displayed_ScreenTime[0] - - src_frame->present_event.PresentStartTime, - GetQpcFrequency()); - } else { - dst_frame->dropped = true; - } - - dst_frame->time_in_seconds = QpcDeltaToSeconds( - src_frame->present_event.PresentStartTime - start_qpc, GetQpcFrequency()); - - dst_frame->ms_in_present_api = - QpcDeltaToMs(src_frame->present_event.TimeInPresent, - GetQpcFrequency()); - - dst_frame->allows_tearing = src_frame->present_event.SupportsTearing; - dst_frame->present_mode = - TranslatePresentMode(src_frame->present_event.PresentMode); - - // If the ReadyTime is BEFORE the present event start time denote this - // with a negative render complete - if (src_frame->present_event.ReadyTime == 0) { - dst_frame->ms_until_render_complete = 0.0; - } else { - if (src_frame->present_event.ReadyTime < - src_frame->present_event.PresentStartTime) { - dst_frame->ms_until_render_complete = - -1.0 * QpcDeltaToMs(src_frame->present_event.PresentStartTime - - src_frame->present_event.ReadyTime, - GetQpcFrequency()); - } else { - dst_frame->ms_until_render_complete = - QpcDeltaToMs(src_frame->present_event.ReadyTime - - src_frame->present_event.PresentStartTime, - GetQpcFrequency()); - } - } - // If the GPUStartTime is BEFORE the present event start time denote this - // with a negative render start - if (src_frame->present_event.GPUStartTime == 0) { - dst_frame->ms_until_render_start = 0.0; - } else { - if (src_frame->present_event.GPUStartTime < - src_frame->present_event.PresentStartTime) { - dst_frame->ms_until_render_start = - -1.0 * QpcDeltaToMs(src_frame->present_event.PresentStartTime - - src_frame->present_event.GPUStartTime, - GetQpcFrequency()); - - } else { - dst_frame->ms_until_render_start = - QpcDeltaToMs(src_frame->present_event.GPUStartTime - - src_frame->present_event.PresentStartTime, - GetQpcFrequency()); - } - } - dst_frame->ms_gpu_active = - QpcDeltaToMs(src_frame->present_event.GPUDuration, GetQpcFrequency()); - dst_frame->ms_gpu_video_active = QpcDeltaToMs( - src_frame->present_event.GPUVideoDuration, GetQpcFrequency()); - dst_frame->ms_since_input = 0.; - if (src_frame->present_event.InputTime != 0) { - dst_frame->ms_since_input = - QpcDeltaToMs(src_frame->present_event.PresentStartTime - - src_frame->present_event.InputTime, - GetQpcFrequency()); - } - - // power telemetry - dst_frame->gpu_power_w.data = src_frame->power_telemetry.gpu_power_w; - dst_frame->gpu_power_w.valid = gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_power)]; - - dst_frame->gpu_sustained_power_limit_w.data = - src_frame->power_telemetry.gpu_sustained_power_limit_w; - dst_frame->gpu_sustained_power_limit_w.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_sustained_power_limit)]; - - dst_frame->gpu_voltage_v.data = - src_frame->power_telemetry.gpu_voltage_v; - dst_frame->gpu_voltage_v.valid = gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_voltage)]; - - dst_frame->gpu_frequency_mhz.data = - src_frame->power_telemetry.gpu_frequency_mhz; - dst_frame->gpu_frequency_mhz.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_frequency)]; - - dst_frame->gpu_temperature_c.data = - src_frame->power_telemetry.gpu_temperature_c; - dst_frame->gpu_temperature_c.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_temperature)]; - - dst_frame->gpu_utilization.data = - src_frame->power_telemetry.gpu_utilization; - dst_frame->gpu_utilization.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_utilization)]; - - dst_frame->gpu_render_compute_utilization.data = - src_frame->power_telemetry.gpu_render_compute_utilization; - dst_frame->gpu_render_compute_utilization.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_render_compute_utilization)]; - - dst_frame->gpu_media_utilization.data = - src_frame->power_telemetry.gpu_media_utilization; - dst_frame->gpu_media_utilization.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_media_utilization)]; - - dst_frame->vram_power_w.data = src_frame->power_telemetry.vram_power_w; - dst_frame->vram_power_w.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_power)]; - - dst_frame->vram_voltage_v.data = src_frame->power_telemetry.vram_voltage_v; - dst_frame->vram_voltage_v.valid = gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_voltage)]; - - dst_frame->vram_frequency_mhz.data = src_frame->power_telemetry.vram_frequency_mhz; - dst_frame->vram_frequency_mhz.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_frequency)]; - - dst_frame->vram_effective_frequency_gbs.data = - src_frame->power_telemetry.vram_effective_frequency_gbps; - dst_frame->vram_effective_frequency_gbs.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_effective_frequency)]; - - dst_frame->vram_temperature_c.data = src_frame->power_telemetry.vram_temperature_c; - dst_frame->vram_temperature_c.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_temperature)]; - - for (size_t i = 0; i < MAX_PM_FAN_COUNT; i++) { - dst_frame->fan_speed_rpm[i].data = src_frame->power_telemetry.fan_speed_rpm[i]; - dst_frame->fan_speed_rpm[i].valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::fan_speed_0) + i]; - } - - for (size_t i = 0; i < MAX_PM_PSU_COUNT; i++) { - dst_frame->psu_type[i].data = - TranslatePsuType(src_frame->power_telemetry.psu[i].psu_type); - dst_frame->psu_type[i].valid = dst_frame->psu_power[i].valid = - gpu_telemetry_cap_bits - [static_cast(GpuTelemetryCapBits::psu_info_0) + i]; - - dst_frame->psu_power[i].data = src_frame->power_telemetry.psu[i].psu_power; - dst_frame->psu_power[i].valid = gpu_telemetry_cap_bits - [static_cast(GpuTelemetryCapBits::psu_info_0) + i]; - - dst_frame->psu_voltage[i].data = src_frame->power_telemetry.psu[i].psu_voltage; - dst_frame->psu_voltage[i].valid = gpu_telemetry_cap_bits - [static_cast(GpuTelemetryCapBits::psu_info_0) + i]; - } - - // Gpu memory telemetry - dst_frame->gpu_mem_total_size_b.data = - src_frame->power_telemetry.gpu_mem_total_size_b; - dst_frame->gpu_mem_total_size_b.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_size)]; - - dst_frame->gpu_mem_used_b.data = - src_frame->power_telemetry.gpu_mem_used_b; - dst_frame->gpu_mem_used_b.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_used)]; - - dst_frame->gpu_mem_max_bandwidth_bps.data = - src_frame->power_telemetry.gpu_mem_max_bandwidth_bps; - dst_frame->gpu_mem_max_bandwidth_bps.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_max_bandwidth)]; - - dst_frame->gpu_mem_read_bandwidth_bps.data = - src_frame->power_telemetry.gpu_mem_read_bandwidth_bps; - dst_frame->gpu_mem_read_bandwidth_bps.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_read_bandwidth)]; - - dst_frame->gpu_mem_write_bandwidth_bps.data = - src_frame->power_telemetry.gpu_mem_write_bandwidth_bps; - dst_frame->gpu_mem_write_bandwidth_bps.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_write_bandwidth)]; - - // Throttling flags - dst_frame->gpu_power_limited.data = src_frame->power_telemetry.gpu_power_limited; - dst_frame->gpu_power_limited.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_power_limited)]; - - dst_frame->gpu_temperature_limited.data = - src_frame->power_telemetry.gpu_temperature_limited; - dst_frame->gpu_temperature_limited.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_temperature_limited)]; - - dst_frame->gpu_current_limited.data = - src_frame->power_telemetry.gpu_current_limited; - dst_frame->gpu_current_limited.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_current_limited)]; - - dst_frame->gpu_voltage_limited.data = - src_frame->power_telemetry.gpu_voltage_limited; - dst_frame->gpu_voltage_limited.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_voltage_limited)]; - - dst_frame->gpu_utilization_limited.data = - src_frame->power_telemetry.gpu_utilization_limited; - dst_frame->gpu_utilization_limited.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_utilization_limited)]; - - dst_frame->vram_power_limited.data = - src_frame->power_telemetry.vram_power_limited; - dst_frame->vram_power_limited.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_utilization_limited)]; - - dst_frame->vram_temperature_limited.data = - src_frame->power_telemetry.vram_temperature_limited; - dst_frame->vram_temperature_limited.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_temperature_limited)]; - - dst_frame->vram_current_limited.data = - src_frame->power_telemetry.vram_current_limited; - dst_frame->vram_current_limited.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_current_limited)]; - - dst_frame->vram_voltage_limited.data = - src_frame->power_telemetry.vram_voltage_limited; - dst_frame->vram_voltage_limited.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_voltage_limited)]; - - dst_frame->vram_utilization_limited.data = - src_frame->power_telemetry.vram_utilization_limited; - dst_frame->vram_utilization_limited.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_utilization_limited)]; - - // cpu telemetry - only available in INTERNAL builds - dst_frame->cpu_utilization.data = src_frame->cpu_telemetry.cpu_utilization; - dst_frame->cpu_utilization.valid = - cpu_telemetry_cap_bits[static_cast( - CpuTelemetryCapBits::cpu_utilization)]; - - dst_frame->cpu_power_w.data = src_frame->cpu_telemetry.cpu_power_w; - dst_frame->cpu_power_w.valid = cpu_telemetry_cap_bits[static_cast( - CpuTelemetryCapBits::cpu_power)]; - - dst_frame->cpu_power_limit_w.data = src_frame->cpu_telemetry.cpu_power_limit_w; - dst_frame->cpu_power_limit_w.valid = - cpu_telemetry_cap_bits[static_cast( - CpuTelemetryCapBits::cpu_power_limit)]; - - dst_frame->cpu_temperature_c.data = src_frame->cpu_telemetry.cpu_temperature; - dst_frame->cpu_temperature_c.valid = - cpu_telemetry_cap_bits[static_cast( - CpuTelemetryCapBits::cpu_temperature)]; - - dst_frame->cpu_frequency.data = src_frame->cpu_telemetry.cpu_frequency; - dst_frame->cpu_frequency.valid = - cpu_telemetry_cap_bits[static_cast( - CpuTelemetryCapBits::cpu_frequency)]; -} - -uint64_t StreamClient::GetLatestFrameIndex() { - if (shared_mem_view_->IsEmpty()) { - return UINT_MAX; - } - - auto p_header = shared_mem_view_->GetHeader(); - - if (!p_header->process_active) { - LOG(ERROR) << "\n Process is not active. Shared mem view to be destroyed."; - CloseSharedMemView(); - return UINT_MAX; - } - - if (p_header->tail_idx == 0) { - return p_header->max_entries - 1; - } else { - return (p_header->tail_idx - 1); - } -} - -// Calculate the number of frames written since the last dequue -uint64_t StreamClient::CheckPendingReadFrames() { - uint64_t num_pending_read_frames = 0; - - auto p_header = shared_mem_view_->GetHeader(); - if (p_header->num_frames_written < current_dequeue_frame_num_) { - // Wrap case where p_header->num_frames_written has wrapped to zero - num_pending_read_frames = ULLONG_MAX - current_dequeue_frame_num_; - num_pending_read_frames += p_header->num_frames_written; - } else { - num_pending_read_frames = - p_header->num_frames_written - current_dequeue_frame_num_; - } - - return num_pending_read_frames; -} - -std::optional< - std::bitset(GpuTelemetryCapBits::gpu_telemetry_count)>> -StreamClient::GetGpuTelemetryCaps() { - if (shared_mem_view_->IsEmpty()) { - return {}; - } - - auto p_header = shared_mem_view_->GetHeader(); - if (!p_header->process_active) { - return {}; - } - - return p_header->gpuTelemetryCapBits; -} - -std::optional< - std::bitset(CpuTelemetryCapBits::cpu_telemetry_count)>> -StreamClient::GetCpuTelemetryCaps() { - if (shared_mem_view_->IsEmpty()) { - return {}; - } - - auto p_header = shared_mem_view_->GetHeader(); - if (!p_header->process_active) { - return {}; - } - - return p_header->cpuTelemetryCapBits; -} diff --git a/IntelPresentMon/Streamer/StreamClient.h b/IntelPresentMon/Streamer/StreamClient.h deleted file mode 100644 index 304359c7..00000000 --- a/IntelPresentMon/Streamer/StreamClient.h +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once -#include -#include -#include -#include -#include "../PresentMonUtils/StreamFormat.h" -#include "../PresentMonUtils/LegacyAPIDefines.h" -#include "NamedSharedMemory.h" - -class StreamClient { - public: - StreamClient(); - StreamClient(std::string mapfile_name, bool is_etl_stream_client); - ~StreamClient(); - - void Initialize(std::string mapfile_name); - bool IsInitialized() { return initialized_; }; - // Read the latest frame from shared memory. - PmNsmFrameData* ReadLatestFrame(); - PmNsmFrameData* ReadFrameByIdx(uint64_t frame_id, bool checked); - // Dequeue a frame of data from shared mem and update the last_read_idx (just get pointer to NsmData) - PM_STATUS ConsumePtrToNextNsmFrameData(const PmNsmFrameData** pNsmData, - const PmNsmFrameData** pNextFrame, - const PmNsmFrameData** pFrameDataOfNextDisplayed, - const PmNsmFrameData** pFrameDataOfLastPresented, - const PmNsmFrameData** pFrameDataOfLastAppPresented, - const PmNsmFrameData** pFrameDataOfLastDisplayed, - const PmNsmFrameData** pFrameDataOfLastAppDisplayed, - const PmNsmFrameData** pFrameDataOfPreviousAppFrameOfLastAppDisplayed); - // Return the last frame id that holds valid data - uint64_t GetLatestFrameIndex(); - NamedSharedMem* GetNamedSharedMemView() { return shared_mem_view_.get(); } - void CloseSharedMemView(); - LARGE_INTEGER GetQpcFrequency() { return qpcFrequency_; }; - void CopyFrameData(uint64_t start_qpc, const PmNsmFrameData* src_frame, - GpuTelemetryBitset gpu_telemetry_cap_bits, - CpuTelemetryBitset cpu_telemetry_cap_bits, - PM_FRAME_DATA* dst_frame); - - std::optional(GpuTelemetryCapBits::gpu_telemetry_count)>> - GetGpuTelemetryCaps(); - std::optional(CpuTelemetryCapBits::cpu_telemetry_count)>> - GetCpuTelemetryCaps(); - - private: - uint64_t CheckPendingReadFrames(); - - // Functions to peek at the next and previous frames - void PeekNextFrames(const PmNsmFrameData** pNextFrame, - const PmNsmFrameData** pNextDisplayedFrame); - void PeekPreviousFrames(const PmNsmFrameData** pFrameDataOfLastPresented, - const PmNsmFrameData** pFrameDataOfLastAppPresented, - const PmNsmFrameData** pFrameDataOfLastDisplayed, - const PmNsmFrameData** pFrameDataOfLastAppDisplayed, - const PmNsmFrameData** pFrameDataOfPreviousAppFrameOfLastAppDisplayed); - - // Helper functions evaluater various frame types - bool IsAppPresentedFrame(const PmNsmFrameData* frame) const; - bool IsDisplayedFrame(const PmNsmFrameData* frame) const; - bool IsAppDisplayedFrame(const PmNsmFrameData* frame) const; - - // Shared memory view that the client opened into based on mapfile name - std::unique_ptr shared_mem_view_; - // mapfile name the client has for named shared memory - std::string mapfile_name_; - // Last read offset from the shared_mem_view_ - bool initialized_; - LARGE_INTEGER qpcFrequency_ = {}; - uint64_t next_dequeue_idx_; - bool recording_frame_data_; - uint64_t current_dequeue_frame_num_; -}; \ No newline at end of file diff --git a/IntelPresentMon/Streamer/Streamer.cpp b/IntelPresentMon/Streamer/Streamer.cpp deleted file mode 100644 index 10b08063..00000000 --- a/IntelPresentMon/Streamer/Streamer.cpp +++ /dev/null @@ -1,480 +0,0 @@ -// Copyright (C) 2022-2023 Intel Corporation -// SPDX-License-Identifier: MIT -// Streamer.cpp : Defines the functions for the static library. - -#include -#include -#include -#include - -#include "Streamer.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include "../PresentMonService/CliOptions.h" -#include "../CommonUtilities/str/String.h" -#include "../CommonUtilities/log/GlogShim.h" - -namespace vi = std::views; -namespace rn = std::ranges; - -static const std::chrono::milliseconds kTimeoutLimitMs = - std::chrono::milliseconds(500); - -Streamer::Streamer() - : shared_mem_size_(kBufSize), - start_qpc_(0), - write_timedout_(false), - mapfileNamePrefix_{ kGlobalPrefix } -{ - if (clio::Options::IsInitialized()) { - mapfileNamePrefix_ = clio::Options::Get().shmNamePrefix.AsOptional().value_or(mapfileNamePrefix_); - } -} - -void Streamer::WriteFrameData( - uint32_t process_id, PmNsmFrameData* data, - std::bitset(GpuTelemetryCapBits::gpu_telemetry_count)> - gpu_telemetry_cap_bits, - std::bitset(CpuTelemetryCapBits::cpu_telemetry_count)> - cpu_telemetry_cap_bits) { - if (data == NULL) { - LOG(ERROR) << "Invalid data."; - return; - } - - // Lock the nsm mutex as stop streaming calls can occur at any time - // and destory the named shared memory during writing of frame data. - std::lock_guard lock(nsm_map_mutex_); - auto iter = process_shared_mem_map_.find(process_id); - if (iter == process_shared_mem_map_.end()) { - LOG(INFO) << "Corresponding named shared memory doesn't exist. Please call StartStreaming(process_id) first."; - - return; - } - - auto shared_mem = iter->second.get(); - - // Record start time if it's the first frame. - if (shared_mem->IsEmpty()) { - shared_mem->RecordFirstFrameTime(data->present_event.PresentStartTime); - } - shared_mem->WriteTelemetryCapBits(gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - shared_mem->WriteFrameData(data); -} - -void Streamer::CopyFromPresentMonPresentEvent( - PresentEvent* present_event, PmNsmPresentEvent* nsm_present_event) { - if (present_event == nullptr || nsm_present_event == nullptr) { - return; - } - - nsm_present_event->PresentStartTime = present_event->PresentStartTime; - nsm_present_event->ProcessId = present_event->ProcessId; - nsm_present_event->ThreadId = present_event->ThreadId; - nsm_present_event->TimeInPresent = present_event->TimeInPresent; - nsm_present_event->GPUStartTime = present_event->GPUStartTime; - nsm_present_event->ReadyTime = present_event->ReadyTime; - nsm_present_event->GPUDuration = present_event->GPUDuration; - nsm_present_event->GPUVideoDuration = present_event->GPUVideoDuration; - nsm_present_event->InputTime = present_event->InputTime; - nsm_present_event->MouseClickTime = present_event->MouseClickTime; - - nsm_present_event->AppPropagatedPresentStartTime = present_event->AppPropagatedPresentStartTime; - nsm_present_event->AppPropagatedTimeInPresent = present_event->AppPropagatedTimeInPresent; - nsm_present_event->AppPropagatedGPUStartTime = present_event->AppPropagatedGPUStartTime; - nsm_present_event->AppPropagatedReadyTime = present_event->AppPropagatedReadyTime; - nsm_present_event->AppPropagatedGPUDuration = present_event->AppPropagatedGPUDuration; - nsm_present_event->AppPropagatedGPUVideoDuration = present_event->AppPropagatedGPUVideoDuration; - - nsm_present_event->AppSleepStartTime = present_event->AppSleepStartTime; - nsm_present_event->AppSleepEndTime = present_event->AppSleepEndTime; - nsm_present_event->AppSimStartTime = present_event->AppSimStartTime; - nsm_present_event->AppSimEndTime = present_event->AppSimEndTime; - nsm_present_event->AppRenderSubmitStartTime = present_event->AppRenderSubmitStartTime; - nsm_present_event->AppRenderSubmitEndTime = present_event->AppRenderSubmitEndTime; - nsm_present_event->AppPresentStartTime = present_event->AppPresentStartTime; - nsm_present_event->AppPresentEndTime = present_event->AppPresentEndTime; - nsm_present_event->AppInputTime = present_event->AppInputSample.first; - nsm_present_event->AppInputType = present_event->AppInputSample.second; - - nsm_present_event->PclInputPingTime = present_event->PclInputPingTime; - nsm_present_event->PclSimStartTime = present_event->PclSimStartTime; - - nsm_present_event->FlipDelay = present_event->FlipDelay; - nsm_present_event->FlipToken = present_event->FlipToken; - - nsm_present_event->SwapChainAddress = present_event->SwapChainAddress; - nsm_present_event->SyncInterval = present_event->SyncInterval; - nsm_present_event->PresentFlags = present_event->PresentFlags; - - nsm_present_event->DisplayedCount = (uint32_t) min(present_event->Displayed.size(), _countof(nsm_present_event->Displayed_ScreenTime)); - for (uint32_t i = 0; i < nsm_present_event->DisplayedCount; ++i) { - nsm_present_event->Displayed_ScreenTime[i] = present_event->Displayed[i].second; - nsm_present_event->Displayed_FrameType[i] = present_event->Displayed[i].first; - } - - nsm_present_event->CompositionSurfaceLuid = - present_event->CompositionSurfaceLuid; - nsm_present_event->Win32KPresentCount = present_event->Win32KPresentCount; - nsm_present_event->Win32KBindId = present_event->Win32KBindId; - nsm_present_event->DxgkPresentHistoryToken = - present_event->DxgkPresentHistoryToken; - nsm_present_event->DxgkPresentHistoryTokenData = - present_event->DxgkPresentHistoryTokenData; - nsm_present_event->DxgkContext = present_event->DxgkContext; - nsm_present_event->Hwnd = present_event->Hwnd; - nsm_present_event->QueueSubmitSequence = present_event->QueueSubmitSequence; - nsm_present_event->RingIndex = - present_event->RingIndex; - - nsm_present_event->DestWidth = present_event->DestWidth; - nsm_present_event->DestHeight = present_event->DestHeight; - nsm_present_event->DriverThreadId = present_event->DriverThreadId; - - nsm_present_event->FrameId = present_event->FrameId; - - nsm_present_event->Runtime = present_event->Runtime; - nsm_present_event->PresentMode = present_event->PresentMode; - nsm_present_event->FinalState = present_event->FinalState; - nsm_present_event->InputType = present_event->InputType; - - nsm_present_event->SupportsTearing = present_event->SupportsTearing; - nsm_present_event->WaitForFlipEvent = present_event->WaitForFlipEvent; - nsm_present_event->WaitForMPOFlipEvent = present_event->WaitForMPOFlipEvent; - nsm_present_event->SeenDxgkPresent = present_event->SeenDxgkPresent; - nsm_present_event->SeenWin32KEvents = present_event->SeenWin32KEvents; - nsm_present_event->SeenInFrameEvent = present_event->SeenInFrameEvent; - nsm_present_event->GpuFrameCompleted = present_event->GpuFrameCompleted; - nsm_present_event->IsCompleted = present_event->IsCompleted; - nsm_present_event->IsLost = present_event->IsLost; - nsm_present_event->PresentInDwmWaitingStruct = - present_event->PresentInDwmWaitingStruct; - - return; -} - -void Streamer::ProcessPresentEvent( - PresentEvent* present_event, - PresentMonPowerTelemetryInfo* power_telemetry_info, - CpuTelemetryInfo* cpu_telemetry_info, uint64_t last_present_qpc, - uint64_t last_displayed_qpc, std::wstring app_name, - std::bitset(GpuTelemetryCapBits::gpu_telemetry_count)> - gpu_telemetry_cap_bits, - std::bitset(CpuTelemetryCapBits::cpu_telemetry_count)> - cpu_telemetry_cap_bits) { - uint32_t process_id = present_event->ProcessId; - - // Lock the nsm mutex as stop streaming calls can occur at any time - // and destroy the named shared memory during writing of frame data. - std::lock_guard lock(nsm_map_mutex_); - - // Search for the requested process - auto iter = process_shared_mem_map_.find(process_id); - NamedSharedMem* process_nsm = nullptr; - if (iter != process_shared_mem_map_.end()) { - process_nsm = iter->second.get(); - // Record start time if it's the first frame. - if (process_nsm->IsEmpty()) { - if (start_qpc_ && process_nsm->GetHeader()->isPlayback) { - process_nsm->RecordFirstFrameTime(start_qpc_); - } - else { - process_nsm->RecordFirstFrameTime(present_event->PresentStartTime); - } - } - } - - // In addition search for the stream all process - auto stream_all_iter = process_shared_mem_map_.find( - (uint32_t)StreamPidOverride::kStreamAllPid); - NamedSharedMem* stream_all_nsm = nullptr; - if (stream_all_iter != process_shared_mem_map_.end()) { - stream_all_nsm = stream_all_iter->second.get(); - // Record start time if it's the first frame. - if (stream_all_nsm->IsEmpty()) { - if (start_qpc_ && stream_all_nsm->GetHeader()->isPlayback) { - stream_all_nsm->RecordFirstFrameTime(start_qpc_); - } - else { - stream_all_nsm->RecordFirstFrameTime(present_event->PresentStartTime); - } - } - } - - if ((process_nsm == nullptr) && (stream_all_nsm == nullptr)) { - // process is not being monitored. Skip. - return; - } - - PmNsmFrameData data = {}; - // Copy the passed in PresentEvent data into the PmNsmFrameData - // structure. - CopyFromPresentMonPresentEvent(present_event, &data.present_event); - // Now update the necessary qpcs and application name which - // reside AFTER the PresentEvent members and hence were not - // updated in the copy above. - data.present_event.last_present_qpc = last_present_qpc; - data.present_event.last_displayed_qpc = last_displayed_qpc; - auto appNameNarrow = pmon::util::str::ToNarrow(app_name); - std::size_t length = appNameNarrow.copy(data.present_event.application, appNameNarrow.size()); - data.present_event.application[length] = '\0'; - // Now copy the power telemetry data - memcpy_s(&data.power_telemetry, sizeof(PresentMonPowerTelemetryInfo), - power_telemetry_info, sizeof(PresentMonPowerTelemetryInfo)); - // Finally copy the cpu telemetry data - memcpy_s(&data.cpu_telemetry, sizeof(CpuTelemetryInfo), cpu_telemetry_info, - sizeof(CpuTelemetryInfo)); - - if (process_nsm) { - auto pHdr = process_nsm->GetHeader(); - // block here if nsm is full and backpressure is enabled (only in playback modes) - if (pHdr->isPlaybackBackpressured && process_nsm->IsFull()) { - const auto start = std::chrono::high_resolution_clock::now(); - do { - const auto now = std::chrono::high_resolution_clock::now(); - const auto time_elapsed = now - start; - LOG(INFO) << "NSM is full ..."; - using namespace std::literals; - std::this_thread::sleep_for(25ms); - - if (time_elapsed >= kTimeoutLimitMs) { - LOG(ERROR) << "\nServer data write timed out."; - write_timedout_ = true; - return; - } - } while (process_nsm->IsFull()); - } - process_nsm->WriteTelemetryCapBits(gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - process_nsm->WriteFrameData(&data); - } - - if (stream_all_nsm) { - stream_all_nsm->WriteTelemetryCapBits(gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - stream_all_nsm->WriteFrameData(&data); - } -} - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/// @brief StartStreaming with target process_id -/// @return Returns corresponding mapfile_name string. If mapfile_name is empty, it means process is not found. -/// - PM_STATUS Streamer::StartStreaming(uint32_t client_process_id, - uint32_t target_process_id, - std::string& mapfile_name, - bool isPlayback, - bool isPlaybackPaced, - bool isPlaybackRetimed, - bool isPlaybackBackpressured, - bool isPlaybackResetOldest) { - auto target_range = client_map_.equal_range(client_process_id); - auto found = std::any_of(target_range.first, target_range.second, - [target_process_id](auto client_entry) { - return target_process_id == client_entry.second; - }); - if (found) { - return PM_STATUS::PM_STATUS_ALREADY_TRACKING_PROCESS; - } - - uint64_t mem_size = kBufSize; - #define _CRT_SECURE_NO_WARNINGS - const char* name = "PM2_NSM_SIZE"; - char* pValue = nullptr; - size_t len; - errno_t err = _dupenv_s(&pValue, &len, name); - if (err || pValue == nullptr) { - LOG(INFO) << "Use default NSM size : " << kBufSize; - } else { - LOG(INFO) << "PM2_NSM_SIZE = " << pValue; - char* pEnd; - mem_size = strtoull(pValue, &pEnd,10); - } - free(pValue); - - // create new shared mem for particular process id - if (CreateNamedSharedMemory(target_process_id, mem_size, - isPlayback, - isPlaybackPaced, - isPlaybackRetimed, - isPlaybackBackpressured, - isPlaybackResetOldest) == false) { - return PM_STATUS::PM_STATUS_UNABLE_TO_CREATE_NSM; - } - - client_map_.insert(std::make_pair(client_process_id, target_process_id)); - LOG(INFO) << "Started streaming for process id:" << target_process_id; - mapfile_name = GetMapFileName(target_process_id); - - return PM_STATUS::PM_STATUS_SUCCESS; -} - -// Update the named shared memory process attachments. If the number of -// attachments drops to zero release the NSM. Function assumes the -// NSM map mutex has been called PRIOR to calling this function. -bool Streamer::UpdateNSMAttachments(uint32_t process_id, int& ref_count) { - ref_count = 0; - auto iter = process_shared_mem_map_.find(process_id); - if (iter != process_shared_mem_map_.end()) { - // Check if refcount is going to be zero - if (iter->second->GetRefCount() > 1) { - iter->second->DecrementRefcount(); - ref_count = iter->second->GetRefCount(); - } else { - iter->second->NotifyProcessKilled(); - process_shared_mem_map_.erase(std::move(iter)); - ref_count = 0; - } - return true; - } - return false; -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/// @brief Stop streaming with target process_id. -/// Decrement refcount if more than one client is attached to it. -/// Otherwise destroy corresponding mapfilename -/// -void Streamer::StopStreaming(uint32_t process_id) { - // Lock the nsm map mutex as stop streaming calls can occur at any time - // from both the client and the output thread when it detects processes - // have terminated - std::lock_guard lock(nsm_map_mutex_); - - int ref_count = 0; - // Check to see if the incoming process id is a client process id - if (client_map_.contains(process_id) == false) { - // Not a client process id, assume a target process. Need to remove - // the NSM. - bool status = UpdateNSMAttachments(process_id, ref_count); - while ((status == true) && (ref_count > 0)) { - status = UpdateNSMAttachments(process_id, ref_count); - } - if ((status == true) && (ref_count == 0)) { - // If the passed in target process id resulted in a destruction of the - // named shared memory then go through the client maps and remove - // any references to the target process. - for (auto i = client_map_.begin(); i != client_map_.end();) { - if (i->second == process_id) { - i = client_map_.erase(i); - } else { - ++i; - } - } - } - } else { - auto client_range = client_map_.equal_range(process_id); - for (auto i = client_range.first; i != client_range.second; ++i) { - UpdateNSMAttachments(i->second, ref_count); - } - client_map_.erase(client_range.first, client_range.second); - } - return; -} - -void Streamer::StopStreaming(uint32_t client_process_id, - uint32_t target_process_id) { - // Lock the nsm map mutex as stop streaming calls can occur at any time - // from both the client and the output thread when it detects processes - // have terminated - std::lock_guard lock(nsm_map_mutex_); - - int ref_count = 0; - bool status = UpdateNSMAttachments(target_process_id, ref_count); - if ((status == true) && (ref_count == 0)) { - // If the passed in target process id resulted in the destruction of the - // named shared memory then go through the client maps and remove - // any references to the target process. - for (auto i = client_map_.begin(); i != client_map_.end();) { - if (i->second == target_process_id) { - i = client_map_.erase(i); - } else { - ++i; - } - } - } else if ((status == true) && (ref_count > 0)) { - // Succesfully found the NSM of the target process id but other clients - // are still monitoring it. Only remove it from this clients mapping. - for (auto i = client_map_.begin(); i != client_map_.end();) { - if ((i->first == client_process_id) && (i->second == target_process_id)) { - i = client_map_.erase(i); - } else { - ++i; - } - } - } - return; -} - -void Streamer::StopAllStreams() { - // Lock the nsm map mutex to ensure we don't destroy the NSMs - // while writing frame data. - std::lock_guard lock(nsm_map_mutex_); - for (auto const& it : process_shared_mem_map_) { - it.second->NotifyProcessKilled(); - } - process_shared_mem_map_.clear(); - client_map_.clear(); - write_timedout_ = false; -} - -bool Streamer::CreateNamedSharedMemory(DWORD process_id, - uint64_t nsm_size_in_bytes, - bool isPlayback, - bool isPlaybackPaced, - bool isPlaybackRetimed, - bool isPlaybackBackpressured, - bool isPlaybackResetOldest) { - std::string mapfile_name = mapfileNamePrefix_ + std::to_string(process_id); - - std::lock_guard lock(nsm_map_mutex_); - auto iter = process_shared_mem_map_.find(process_id); - if (iter == process_shared_mem_map_.end()) { - auto nsm = - std::make_unique(std::move(mapfile_name), nsm_size_in_bytes, - isPlayback, - isPlaybackPaced, - isPlaybackRetimed, - isPlaybackBackpressured, - isPlaybackResetOldest); - if (nsm->IsNSMCreated()) { - process_shared_mem_map_.emplace(process_id, std::move(nsm)); - return true; - } else { - LOG(INFO) << "Unabled to create NSM for process id:" << process_id; - return false; - } - - } else { - LOG(INFO) << "Shared mem for process(" << process_id - << ") already exists. Increment recount."; - iter->second->IncrementRefcount(); - return true; - } -} - -std::string Streamer::GetMapFileName(DWORD process_id) { - std::lock_guard lock(nsm_map_mutex_); - auto iter = process_shared_mem_map_.find(process_id); - std::string mapfile_name; - - if (iter != process_shared_mem_map_.end()) { - mapfile_name = iter->second->GetMapFileName(); - } - - return mapfile_name; -} - -std::set Streamer::GetActiveStreamPids() const -{ - std::lock_guard lk{ nsm_map_mutex_ }; - return { std::from_range, process_shared_mem_map_ | vi::keys }; -} \ No newline at end of file diff --git a/IntelPresentMon/Streamer/Streamer.h b/IntelPresentMon/Streamer/Streamer.h deleted file mode 100644 index 9332d27a..00000000 --- a/IntelPresentMon/Streamer/Streamer.h +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (C) 2022-2023 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once - -#include -#include -#include -#include -#include - -#include "../PresentMonUtils/StreamFormat.h" -#include "gtest/gtest.h" -#include "NamedSharedMemory.h" - -class Streamer { - public: - Streamer(); - ~Streamer() = default; - - // Client API, start streaming data for process by name - PM_STATUS StartStreaming(uint32_t client_process_id, - uint32_t target_process_id, - std::string& mapfile_name, - bool isPlayback, - bool isPlaybackPaced, - bool isPlaybackRetimed, - bool isPlaybackBackpressured, - bool isPlaybackResetOldest); - - // Set streaming mode. Default value is real time streaming for single process. - void StopStreaming(uint32_t client_process_id, uint32_t target_process_id); - void StopStreaming(uint32_t process_id); - void StopAllStreams(); - // Last producer and last consumer are internal fields - // Remove for public build - void ProcessPresentEvent( - PresentEvent* present_event, - PresentMonPowerTelemetryInfo* power_telemetry_info, - CpuTelemetryInfo* cpu_telemetry_info, uint64_t last_present_qpc, - uint64_t last_displayed_qpc, std::wstring app_name, - std::bitset(GpuTelemetryCapBits::gpu_telemetry_count)> - gpu_telemetry_cap_bits, - std::bitset(CpuTelemetryCapBits::cpu_telemetry_count)> - cpu_telemetry_cap_bits); - - void WriteFrameData( - uint32_t process_id, PmNsmFrameData* data, - std::bitset(GpuTelemetryCapBits::gpu_telemetry_count)> - gpu_telemetry_cap_bits, - std::bitset(CpuTelemetryCapBits::cpu_telemetry_count)> - cpu_telemetry_cap_bits); - std::string GetMapFileName(DWORD process_id); - std::set GetActiveStreamPids() const; - void SetStartQpc(uint64_t start_qpc) { start_qpc_ = start_qpc; }; - bool IsTimedOut() { return write_timedout_; }; - int NumActiveStreams() { return (int)process_shared_mem_map_.size(); } - - private: - FRIEND_TEST(NamedSharedMemoryTest, CreateNamedSharedMemory); - FRIEND_TEST(NamedSharedMemoryTestCustomSize, CreateNamedSharedMemory); - bool CreateNamedSharedMemory(DWORD process_id, uint64_t nsm_size_in_bytes, - bool isPlayback, - bool isPlaybackPaced, - bool isPlaybackRetimed, - bool isPlaybackBackpressured, - bool isPlaybackResetOldest); - void CopyFromPresentMonPresentEvent(PresentEvent* present_event, - PmNsmPresentEvent* nsm_present_event); - bool UpdateNSMAttachments(uint32_t process_id, int& ref_count); - std::string mapfileNamePrefix_; - // Shared mem buffer map of process id and share mem handle - std::map> process_shared_mem_map_; - std::multimap client_map_; - uint64_t shared_mem_size_; - uint64_t start_qpc_; - // This flag is currently used during etl processing. If the client - // misbehaved or died such that streamer is blocked to write frame data, - // write_timedout_ would be set to true and etl_session_ of PresentMon would - // stop the trace session. - bool write_timedout_; - mutable std::mutex nsm_map_mutex_; -}; diff --git a/IntelPresentMon/Streamer/Streamer.vcxproj b/IntelPresentMon/Streamer/Streamer.vcxproj deleted file mode 100644 index c7cf635a..00000000 --- a/IntelPresentMon/Streamer/Streamer.vcxproj +++ /dev/null @@ -1,133 +0,0 @@ - - - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {bf43064b-01f0-4c69-91fb-c2122baf621d} - Streamer - 10.0 - - - - StaticLibrary - true - v143 - MultiByte - x64 - - - StaticLibrary - false - v143 - true - MultiByte - x64 - - - - - - - - - - - - - - - - - - - true - --checks=clang-analyzer-*,google-* - false - false - true - - - false - --checks=clang-analyzer-*,google-* - false - false - true - - - - - - - - Level3 - true - true - NotUsing - MultiThreadedDebug - stdcpplatest - - - - - true - - - - - Level3 - true - true - true - true - NotUsing - MultiThreaded - stdcpplatest - NDEBUG;%(PreprocessorDefinitions) - - - - - true - true - true - - - - - - - - - - - - - - - - - - {892028e5-32f6-45fc-8ab2-90fcbcac4bf6} - - - {08a704d8-ca1c-45e9-8ede-542a1a43b53e} - - - {66e9f6c5-28db-4218-81b9-31e0e146ecc0} - - - - - - - \ No newline at end of file diff --git a/IntelPresentMon/Streamer/Streamer.vcxproj.filters b/IntelPresentMon/Streamer/Streamer.vcxproj.filters deleted file mode 100644 index cae7383e..00000000 --- a/IntelPresentMon/Streamer/Streamer.vcxproj.filters +++ /dev/null @@ -1,23 +0,0 @@ - - - - - {88626460-8f37-486c-9c5b-7a47e38ff975} - - - - - - - - - - - - - - - proto - - - \ No newline at end of file diff --git a/IntelPresentMon/ULT/MemBufferTests.cpp b/IntelPresentMon/ULT/MemBufferTests.cpp deleted file mode 100644 index 4c774b6b..00000000 --- a/IntelPresentMon/ULT/MemBufferTests.cpp +++ /dev/null @@ -1,174 +0,0 @@ -#include -#include -#include - -#include "../PresentMonUtils/MemBuffer.h" - -class MemBufferULT : public ::testing::Test { - public: - void SetUp() override { mMemBuffer = std::make_unique(); } - std::unique_ptr mMemBuffer; -}; - -TEST_F(MemBufferULT, MemBufferInitialization) { - EXPECT_TRUE((mMemBuffer->GetCurrentSize() == 0)); - EXPECT_TRUE((mMemBuffer->AccessMem() == nullptr)); -} - -TEST_F(MemBufferULT, MemBufferSingleItemAdd) { - double testData[2]; - testData[0] = 60.0f; - testData[1] = 120.34f; - size_t testDataSize = sizeof(testData); - mMemBuffer->AddItem(testData, testDataSize); - EXPECT_TRUE((mMemBuffer->GetCurrentSize() == testDataSize)); - - double* tempTestData = (double*)mMemBuffer->AccessMem(); - EXPECT_TRUE(tempTestData != nullptr); - EXPECT_TRUE(*tempTestData++ == 60.0f); - EXPECT_TRUE(*tempTestData++ == 120.34f); -} - -TEST_F(MemBufferULT, MemBufferMultiItemAddOne) { - - uint32_t testData1 = 0x1645; - size_t testData1Size = sizeof(testData1); - uint64_t testData2 = 0xba5eball; - size_t testData2Size = sizeof(testData2); - double testData3[2]; - testData3[0] = 60.0f; - testData3[1] = 120.34f; - size_t testData3Size = sizeof(testData3); - - mMemBuffer->AddItem(&testData1, testData1Size); - mMemBuffer->AddItem(&testData2, testData2Size); - mMemBuffer->AddItem(&testData3, testData3Size); - EXPECT_TRUE((mMemBuffer->GetCurrentSize() == testData1Size + testData2Size + testData3Size)); - - LPVOID testData = mMemBuffer->AccessMem(); - EXPECT_TRUE(testData != nullptr); - - uint32_t* dataItem1 = (uint32_t*)testData; - EXPECT_TRUE(*dataItem1 == testData1); - - uint64_t* dataItem2 = (uint64_t*)((BYTE*)testData + testData1Size); - EXPECT_TRUE(*dataItem2 == testData2); - - double* dataItem3 = (double*)((BYTE*)testData + (testData1Size + testData2Size)); - EXPECT_TRUE(*dataItem3++ == 60.0f); - EXPECT_TRUE(*dataItem3++ == 120.34f); -} - -TEST_F(MemBufferULT, MemBufferMultiItemAddTwo) { - - uint32_t testData1 = 0x1645; - size_t testData1Size = sizeof(testData1); - uint64_t testData2 = 0xba5eball; - size_t testData2Size = sizeof(testData2); - double testData3[2]; - testData3[0] = 60.0f; - testData3[1] = 120.34f; - size_t testData3Size = sizeof(testData3); - uint64_t testData4[10]; - size_t testData4Size = sizeof(testData4); - - BCRYPT_ALG_HANDLE crypto_algo; - auto status = BCryptOpenAlgorithmProvider( - &crypto_algo, BCRYPT_RNG_ALGORITHM, NULL, 0); - EXPECT_TRUE(status >= 0); - - for (uint32_t i = 0; i < 10; i++) { - ULONG test_data_size = sizeof(uint64_t); - auto rand_gen_success = - BCryptGenRandom(crypto_algo, (BYTE*)&testData4[i], test_data_size, 0); - EXPECT_TRUE(rand_gen_success >= 0); - } - status = BCryptCloseAlgorithmProvider(crypto_algo, 0); - EXPECT_TRUE(status >= 0); - - mMemBuffer->AddItem(&testData1, testData1Size); - mMemBuffer->AddItem(&testData2, testData2Size); - mMemBuffer->AddItem(&testData3, testData3Size); - mMemBuffer->AddItem(&testData4, testData4Size); - EXPECT_TRUE((mMemBuffer->GetCurrentSize() == testData1Size + testData2Size + testData3Size + testData4Size)); - - LPVOID testData = mMemBuffer->AccessMem(); - EXPECT_TRUE(testData != nullptr); - - uint32_t* dataItem1 = (uint32_t*)testData; - EXPECT_TRUE(*dataItem1 == testData1); - - uint64_t* dataItem2 = (uint64_t*)((BYTE*)testData + testData1Size); - EXPECT_TRUE(*dataItem2 == testData2); - - double* dataItem3 = (double*)((BYTE*)testData + (testData1Size + testData2Size)); - EXPECT_TRUE(*dataItem3++ == testData3[0]); - EXPECT_TRUE(*dataItem3++ == testData3[1]); - - uint64_t* dataItem4 = (uint64_t*)((BYTE*)testData + (testData1Size + testData2Size + testData3Size)); - for (uint32_t i = 0; i < 10; i++) { - EXPECT_TRUE(*dataItem4++ == testData4[i]); - } -} - -TEST_F(MemBufferULT, MemBufferAddAfterAccess) { - - uint32_t testData1 = 0x1645; - size_t testData1Size = sizeof(testData1); - uint64_t testData2 = 0xba5eball; - size_t testData2Size = sizeof(testData2); - double testData3[2]; - testData3[0] = 60.0f; - testData3[1] = 120.34f; - size_t testData3Size = sizeof(testData3); - - mMemBuffer->AddItem(&testData1, testData1Size); - mMemBuffer->AddItem(&testData2, testData2Size); - mMemBuffer->AddItem(&testData3, testData3Size); - EXPECT_TRUE((mMemBuffer->GetCurrentSize() == testData1Size + testData2Size + testData3Size)); - - LPVOID testData = mMemBuffer->AccessMem(); - EXPECT_TRUE(testData != nullptr); - - uint32_t* dataItem1 = (uint32_t*)testData; - EXPECT_TRUE(*dataItem1 == testData1); - - uint64_t* dataItem2 = (uint64_t*)((BYTE*)testData + testData1Size); - EXPECT_TRUE(*dataItem2 == testData2); - - double* dataItem3 = (double*)((BYTE*)testData + (testData1Size + testData2Size)); - EXPECT_TRUE(*dataItem3++ == 60.0f); - EXPECT_TRUE(*dataItem3++ == 120.34f); - - uint64_t testData4[10]; - size_t testData4Size = sizeof(testData4); - - BCRYPT_ALG_HANDLE crypto_algo; - auto status = BCryptOpenAlgorithmProvider( - &crypto_algo, BCRYPT_RNG_ALGORITHM, NULL, 0); - EXPECT_TRUE(status >= 0); - - for (uint32_t i = 0; i < 10; i++) { - ULONG test_data_size = sizeof(uint64_t); - auto rand_gen_success = - BCryptGenRandom(crypto_algo, (BYTE*)&testData4[i], test_data_size, 0); - EXPECT_TRUE(rand_gen_success >= 0); - } - status = BCryptCloseAlgorithmProvider(crypto_algo, 0); - EXPECT_TRUE(status >= 0); - - mMemBuffer->AddItem(&testData4, testData4Size); - - testData = mMemBuffer->AccessMem(); - - EXPECT_TRUE((mMemBuffer->GetCurrentSize() == testData1Size + testData2Size + testData3Size + testData4Size)); - uint64_t* dataItem4 = (uint64_t*)((BYTE*)testData + (testData1Size + testData2Size + testData3Size)); - for (uint32_t i = 0; i < 10; i++) { - EXPECT_TRUE(*dataItem4++ == testData4[i]); - } -} - -TEST_F(MemBufferULT, MemBufferNullInputBuffer) { - bool status = mMemBuffer->AddItem(nullptr, 0); - EXPECT_TRUE(status == false); -} \ No newline at end of file diff --git a/IntelPresentMon/ULT/PMApiTests.cpp b/IntelPresentMon/ULT/PMApiTests.cpp deleted file mode 100644 index 39e73d0c..00000000 --- a/IntelPresentMon/ULT/PMApiTests.cpp +++ /dev/null @@ -1,1219 +0,0 @@ -#include "../PresentMonUtils/MemBuffer.h" -#include "../PresentMonUtils/PresentMonNamedPipe.h" -#include "../Streamer/StreamClient.h" -#include "../Streamer/Streamer.h" -#include "PmFrameGenerator.h" -#include "gtest/gtest.h" -#include "utils.h" - -#include "../CommonUtilities/log/GlogShim.h" - -const uint32_t kPid = 10; -const double kRunWindowSize = 1000.0f; -const uint32_t kNumFrames = 150; - -// Error tolerance -const double kErrorTolerance = 0.2; - -static bool fltCmp(double val1, double val2, double tolerance) { - if (fabs(val1 - val2) < tolerance) { - return true; - } else { - return false; - } -} - -class PresentMonApiULT : public ::testing::Test {}; - -bool Equal(const PM_METRIC_DOUBLE_DATA &data1, const PM_METRIC_DOUBLE_DATA& data2) { - - if (!fltCmp(data1.avg, data2.avg, kErrorTolerance)) { - return false; - } - if (!fltCmp(data1.high, data2.high, kErrorTolerance)) { - return false; - } - if (!fltCmp(data1.low, data2.low, kErrorTolerance)) { - return false; - } - if (!fltCmp(data1.percentile_90, data2.percentile_90, kErrorTolerance)) { - return false; - } - if (!fltCmp(data1.percentile_95, data2.percentile_95, kErrorTolerance)) { - return false; - } - if (!fltCmp(data1.percentile_95, data2.percentile_95, kErrorTolerance)) { - return false; - } - if (!fltCmp(data1.raw, data2.raw, kErrorTolerance)) { - return false; - } - return true; -} - -template -bool CompareTelemetry(T telemetry_a, T telemetry_b) { - if constexpr (std::is_floating_point::value) { - return (fltCmp(telemetry_a, telemetry_b, kErrorTolerance)); - } else { - return (telemetry_a == telemetry_b); - } -} - -template -bool CompareOptionalTelemetry(T telemetry_a, T telemetry_b) { - - if (telemetry_a.valid == telemetry_b.valid) { - return CompareTelemetry(telemetry_a.data, telemetry_b.data); - return true; - } else { - return false; - } -} - -bool CompareFrameData(const PM_FRAME_DATA &data1, const PM_FRAME_DATA &data2) { - std::string data1_string = data1.application; - std::string data2_string = data2.application; - if (data1_string != data2_string) { - return false; - } - if (data1.swap_chain_address != data2.swap_chain_address) { - return false; - } - data1_string = data1.runtime; - data2_string = data2.runtime; - if (data1_string != data2_string) { - return false; - } - if (data1.sync_interval != data2.sync_interval) { - return false; - } - if (data1.present_flags != data2.present_flags) { - return false; - } - if (data1.dropped != data2.dropped) { - return false; - } - if (!fltCmp(data1.time_in_seconds, data2.time_in_seconds, kErrorTolerance)) { - return false; - } - if (!fltCmp(data1.ms_in_present_api, data2.ms_in_present_api, - kErrorTolerance)) { - return false; - } - if (!fltCmp(data1.ms_between_presents, data2.ms_between_presents, - kErrorTolerance)) { - return false; - } - if (data1.allows_tearing != data2.allows_tearing) { - return false; - } - if (data1.present_mode != data2.present_mode) { - return false; - } - if (!fltCmp(data1.ms_until_render_complete, data2.ms_until_render_complete, - kErrorTolerance)) { - return false; - } - if (!fltCmp(data1.ms_until_displayed, data2.ms_until_displayed, - kErrorTolerance)) { - return false; - } - if (!fltCmp(data1.ms_between_display_change, data2.ms_between_display_change, - kErrorTolerance)) { - return false; - } - if (!fltCmp(data1.ms_until_render_start, data2.ms_until_render_start, - kErrorTolerance)) { - return false; - } - if (data1.qpc_time != data2.qpc_time) { - return false; - } - - if (!fltCmp(data1.ms_since_input, data2.ms_since_input, kErrorTolerance)) { - return false; - } - if (!fltCmp(data1.ms_gpu_active, data2.ms_gpu_active, kErrorTolerance)) { - return false; - } - if (!fltCmp(data1.ms_gpu_video_active, data2.ms_gpu_video_active, kErrorTolerance)) { - return false; - } - - // Power Telemetry data - if (!CompareOptionalTelemetry(data1.gpu_power_w, data2.gpu_power_w)){ - return false; - } - if (!CompareOptionalTelemetry(data1.gpu_sustained_power_limit_w, - data2.gpu_sustained_power_limit_w)) { - return false; - } - if (!CompareOptionalTelemetry(data1.gpu_voltage_v, data2.gpu_voltage_v)) { - return false; - } - if (!CompareOptionalTelemetry(data1.gpu_frequency_mhz, data2.gpu_frequency_mhz)) { - return false; - } - if (!CompareOptionalTelemetry(data1.gpu_temperature_c, data2.gpu_temperature_c)) { - return false; - } - if (!CompareOptionalTelemetry(data1.gpu_utilization, data2.gpu_utilization)) { - return false; - } - if (!CompareOptionalTelemetry(data1.gpu_render_compute_utilization, - data2.gpu_render_compute_utilization)) { - return false; - } - if (!CompareOptionalTelemetry(data1.gpu_media_utilization, data2.gpu_media_utilization)) { - return false; - } - if (!CompareOptionalTelemetry(data1.vram_power_w, data2.vram_power_w)) { - return false; - } - if (!CompareOptionalTelemetry(data1.vram_voltage_v, data2.vram_voltage_v)) { - return false; - } - if (!CompareOptionalTelemetry(data1.vram_frequency_mhz, data2.vram_frequency_mhz)) { - return false; - } - if (!CompareOptionalTelemetry(data1.vram_effective_frequency_gbs, - data2.vram_effective_frequency_gbs)) { - return false; - } - if (!CompareOptionalTelemetry(data1.vram_temperature_c, data2.vram_temperature_c)) { - return false; - } - if (!CompareOptionalTelemetry(data1.gpu_mem_total_size_b, data2.gpu_mem_total_size_b)) { - return false; - } - if (!CompareOptionalTelemetry(data1.gpu_mem_used_b, data2.gpu_mem_used_b)) { - return false; - } - if (!CompareOptionalTelemetry(data1.gpu_mem_max_bandwidth_bps, data2.gpu_mem_max_bandwidth_bps)) { - return false; - } - if (!CompareOptionalTelemetry(data1.gpu_mem_read_bandwidth_bps, - data2.gpu_mem_read_bandwidth_bps)) { - return false; - } - if (!CompareOptionalTelemetry(data1.gpu_mem_write_bandwidth_bps, - data2.gpu_mem_write_bandwidth_bps)) { - return false; - } - if (!CompareOptionalTelemetry(data1.gpu_power_limited, data2.gpu_power_limited)) { - return false; - } - if (!CompareOptionalTelemetry(data1.gpu_temperature_limited, data2.gpu_temperature_limited)) { - return false; - } - if (!CompareOptionalTelemetry(data1.gpu_current_limited, data2.gpu_current_limited)) { - return false; - } - if (!CompareOptionalTelemetry(data1.gpu_voltage_limited, data2.gpu_voltage_limited)) { - return false; - } - if (!CompareOptionalTelemetry(data1.gpu_utilization_limited, data2.gpu_utilization_limited)) { - return false; - } - if (!CompareOptionalTelemetry(data1.vram_power_limited, data2.vram_power_limited)) { - return false; - } - if (!CompareOptionalTelemetry(data1.vram_temperature_limited, data2.vram_temperature_limited)) { - return false; - } - if (!CompareOptionalTelemetry(data1.vram_current_limited, data2.vram_current_limited)) { - return false; - } - if (!CompareOptionalTelemetry(data1.vram_voltage_limited, data2.vram_voltage_limited)) { - return false; - } - if (!CompareOptionalTelemetry(data1.vram_utilization_limited, data2.vram_utilization_limited)) { - return false; - } - if (!CompareOptionalTelemetry(data1.cpu_utilization, data2.cpu_utilization)) { - return false; - } - if (!CompareOptionalTelemetry(data1.cpu_frequency, data2.cpu_frequency)) { - return false; - } - - return true; -} - -TEST_F(PresentMonApiULT, TestFpsMetrics) { - - PmFrameGenerator frame_gen{PmFrameGenerator::FrameParams{}}; - LOG(INFO) << "\nSeed used for TestFpsMetrics test: " - << frame_gen.GetSeed(); - frame_gen.SetFps(60.); - frame_gen.GenerateFrames(kNumFrames); - DWORD client_process_id = GetCurrentProcessId(); - std::string nsm_name; - GpuTelemetryBitset gpu_telemetry_cap_bits; - CpuTelemetryBitset cpu_telemetry_cap_bits; - gpu_telemetry_cap_bits.set(); - cpu_telemetry_cap_bits.set(); - { - Streamer test_streamer; - test_streamer.StartStreaming(client_process_id, kPid, nsm_name); - for (int i = 0; i <= frame_gen.GetNumFrames(); i++) { - auto frame = frame_gen.GetFrameData(i); - test_streamer.WriteFrameData(kPid, &frame, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - } - - PresentMonClient pm_client; - std::unique_ptr client = std::make_unique(); - client->Initialize(test_streamer.GetMapFileName(kPid)); - pm_client.clients_.emplace(kPid, std::move(client)); - pm_client.SetupClientCaches(kPid); - - EXPECT_NE(pm_client.clients_[kPid]->GetNamedSharedMemView(), nullptr); - - std::vector calculated_fps_data; - frame_gen.CalculateFpsMetrics((uint32_t)frame_gen.GetNumFrames(), - kRunWindowSize, calculated_fps_data); - - PM_FPS_DATA fps_data{}; - uint32_t num_swap_chains = 1; - pm_client.GetFramesPerSecondData(kPid, &fps_data, kRunWindowSize, - &num_swap_chains); - EXPECT_TRUE(1 == num_swap_chains); - EXPECT_TRUE((num_swap_chains == (uint32_t)calculated_fps_data.size())); - - EXPECT_TRUE( - Equal(fps_data.presented_fps, calculated_fps_data[0].presented_fps)); - EXPECT_TRUE( - Equal(fps_data.displayed_fps, calculated_fps_data[0].displayed_fps)); - EXPECT_TRUE( - Equal(fps_data.frame_time_ms, calculated_fps_data[0].frame_time_ms)); - EXPECT_TRUE(Equal(fps_data.gpu_busy, calculated_fps_data[0].gpu_busy)); - EXPECT_TRUE(Equal(fps_data.percent_dropped_frames, - calculated_fps_data[0].percent_dropped_frames)); - - EXPECT_TRUE((fps_data.sync_interval == 0)); - EXPECT_TRUE(fps_data.present_mode == calculated_fps_data[0].present_mode); - - EXPECT_TRUE((fps_data.num_presents == calculated_fps_data[0].num_presents)); - } - - frame_gen.SetFps(300.); - frame_gen.GenerateFrames(kNumFrames); - { - Streamer test_streamer; - test_streamer.StartStreaming(client_process_id, kPid, nsm_name); - for (int i = 0; i <= frame_gen.GetNumFrames(); i++) { - auto frame = frame_gen.GetFrameData(i); - test_streamer.WriteFrameData(kPid, &frame, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - } - - PresentMonClient pm_client; - std::unique_ptr client = std::make_unique(); - client->Initialize(test_streamer.GetMapFileName(kPid)); - pm_client.clients_.emplace(kPid, std::move(client)); - pm_client.SetupClientCaches(kPid); - - EXPECT_NE(pm_client.clients_[kPid]->GetNamedSharedMemView(), nullptr); - - std::vector calculated_fps_data; - frame_gen.CalculateFpsMetrics((uint32_t)frame_gen.GetNumFrames(), - kRunWindowSize, calculated_fps_data); - - PM_FPS_DATA fps_data{}; - uint32_t num_swap_chains = 1; - pm_client.GetFramesPerSecondData(kPid, &fps_data, kRunWindowSize, - &num_swap_chains); - EXPECT_TRUE(1 == num_swap_chains); - EXPECT_TRUE((num_swap_chains == (uint32_t)calculated_fps_data.size())); - - EXPECT_TRUE( - Equal(fps_data.presented_fps, calculated_fps_data[0].presented_fps)); - EXPECT_TRUE( - Equal(fps_data.displayed_fps, calculated_fps_data[0].displayed_fps)); - EXPECT_TRUE( - Equal(fps_data.frame_time_ms, calculated_fps_data[0].frame_time_ms)); - - EXPECT_TRUE(Equal(fps_data.gpu_busy, calculated_fps_data[0].gpu_busy)); - EXPECT_TRUE(Equal(fps_data.percent_dropped_frames, - calculated_fps_data[0].percent_dropped_frames)); - - EXPECT_TRUE((fps_data.sync_interval == 0)); - EXPECT_TRUE(fps_data.present_mode == calculated_fps_data[0].present_mode); - - EXPECT_TRUE((fps_data.num_presents == calculated_fps_data[0].num_presents)); - } -} - -TEST_F(PresentMonApiULT, TestFpsMetricsMultiSwapChains) { - PmFrameGenerator frame_gen{PmFrameGenerator::FrameParams{}}; - LOG(INFO) << "\nSeed used for TestFpsMetricsMultiSwapChains test: " - << frame_gen.GetSeed(); - frame_gen.SetFps(180.); - frame_gen.SetNumberSwapChains(3); - frame_gen.GenerateFrames(kNumFrames); - - DWORD client_process_id = GetCurrentProcessId(); - std::string nsm_name; - GpuTelemetryBitset gpu_telemetry_cap_bits; - CpuTelemetryBitset cpu_telemetry_cap_bits; - gpu_telemetry_cap_bits.set(); - cpu_telemetry_cap_bits.set(); - Streamer test_streamer; - test_streamer.StartStreaming(client_process_id, kPid, nsm_name); - for (int i = 0; i <= frame_gen.GetNumFrames(); i++) { - auto frame = frame_gen.GetFrameData(i); - test_streamer.WriteFrameData(kPid, &frame, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - } - - PresentMonClient pm_client; - std::unique_ptr client = std::make_unique(); - client->Initialize(test_streamer.GetMapFileName(kPid)); - pm_client.clients_.emplace(kPid, std::move(client)); - pm_client.SetupClientCaches(kPid); - - EXPECT_NE(pm_client.clients_[kPid]->GetNamedSharedMemView(), nullptr); - - std::vector calculated_fps_data; - frame_gen.CalculateFpsMetrics((uint32_t)frame_gen.GetNumFrames(), - kRunWindowSize, calculated_fps_data); - - PM_FPS_DATA fps_data[3]{}; - uint32_t num_swap_chains = 3; - pm_client.GetFramesPerSecondData(kPid, fps_data, kRunWindowSize, - &num_swap_chains); - EXPECT_TRUE(3 == num_swap_chains); - EXPECT_TRUE((num_swap_chains == (uint32_t)calculated_fps_data.size())); - - for (uint32_t i = 0; i < num_swap_chains; i++) { - EXPECT_TRUE( - Equal(fps_data[i].presented_fps, calculated_fps_data[i].presented_fps)); - EXPECT_TRUE( - Equal(fps_data[i].displayed_fps, calculated_fps_data[i].displayed_fps)); - EXPECT_TRUE( - Equal(fps_data[i].frame_time_ms, calculated_fps_data[i].frame_time_ms)); - EXPECT_TRUE(Equal(fps_data[i].gpu_busy, calculated_fps_data[i].gpu_busy)); - EXPECT_TRUE(Equal(fps_data[i].percent_dropped_frames, - calculated_fps_data[i].percent_dropped_frames)); - - EXPECT_TRUE((fps_data[i].sync_interval == 0)); - EXPECT_TRUE(fps_data[i].present_mode == - calculated_fps_data[i].present_mode); - - EXPECT_TRUE( - (fps_data[i].num_presents == calculated_fps_data[i].num_presents)); - } -} - -TEST_F(PresentMonApiULT, TestGfxLatencyMultiSwapChains) { - PmFrameGenerator frame_gen{PmFrameGenerator::FrameParams{}}; - LOG(INFO) << "\nSeed used for GetFramesPerSecondData test: " - << frame_gen.GetSeed(); - frame_gen.SetFps(60.); - frame_gen.SetNumberSwapChains(4); - frame_gen.GenerateFrames(kNumFrames); - DWORD client_process_id = GetCurrentProcessId(); - std::string nsm_name; - GpuTelemetryBitset gpu_telemetry_cap_bits; - CpuTelemetryBitset cpu_telemetry_cap_bits; - gpu_telemetry_cap_bits.set(); - cpu_telemetry_cap_bits.set(); - { - Streamer test_streamer; - test_streamer.StartStreaming(client_process_id, kPid, nsm_name); - for (int i = 0; i <= frame_gen.GetNumFrames(); i++) { - auto frame = frame_gen.GetFrameData(i); - test_streamer.WriteFrameData(kPid, &frame, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - } - - PresentMonClient pm_client; - std::unique_ptr client = std::make_unique(); - client->Initialize(test_streamer.GetMapFileName(kPid)); - pm_client.clients_.emplace(kPid, std::move(client)); - pm_client.SetupClientCaches(kPid); - - EXPECT_NE(pm_client.clients_[kPid]->GetNamedSharedMemView(), nullptr); - - std::vector calculated_latency_data; - frame_gen.CalculateLatencyMetrics((uint32_t)frame_gen.GetNumFrames(), - kRunWindowSize, calculated_latency_data); - - PM_GFX_LATENCY_DATA gfx_latency_data[4]{}; - uint32_t num_swap_chains = 4; - pm_client.GetGfxLatencyData(kPid, gfx_latency_data, kRunWindowSize, - &num_swap_chains); - EXPECT_TRUE(4 == num_swap_chains); - EXPECT_TRUE((num_swap_chains == (uint32_t)calculated_latency_data.size())); - - for (uint32_t i = 0; i < num_swap_chains; i++) { - EXPECT_TRUE(Equal(gfx_latency_data[i].display_latency_ms, - calculated_latency_data[i].display_latency_ms)); - EXPECT_TRUE(Equal(gfx_latency_data[i].render_latency_ms, - calculated_latency_data[i].render_latency_ms)); - } - } -} - -TEST_F(PresentMonApiULT, TestFpsDroppedFrames) { - PmFrameGenerator frame_gen{ - PmFrameGenerator::FrameParams{.percent_dropped = 50.}}; - LOG(INFO) << "\nSeed used for GetFramesPerSecondData test: " - << frame_gen.GetSeed(); - frame_gen.SetFps(302.); - frame_gen.GenerateFrames(kNumFrames); - DWORD client_process_id = GetCurrentProcessId(); - std::string nsm_name; - GpuTelemetryBitset gpu_telemetry_cap_bits; - CpuTelemetryBitset cpu_telemetry_cap_bits; - gpu_telemetry_cap_bits.set(); - cpu_telemetry_cap_bits.set(); - { - Streamer test_streamer; - test_streamer.StartStreaming(client_process_id, kPid, nsm_name); - for (int i = 0; i <= frame_gen.GetNumFrames(); i++) { - auto frame = frame_gen.GetFrameData(i); - test_streamer.WriteFrameData(kPid, &frame, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - } - - PresentMonClient pm_client; - std::unique_ptr client = std::make_unique(); - client->Initialize(test_streamer.GetMapFileName(kPid)); - pm_client.clients_.emplace(kPid, std::move(client)); - pm_client.SetupClientCaches(kPid); - - EXPECT_NE(pm_client.clients_[kPid]->GetNamedSharedMemView(), nullptr); - - std::vector calculated_fps_data; - frame_gen.CalculateFpsMetrics((uint32_t)frame_gen.GetNumFrames(), - kRunWindowSize, calculated_fps_data); - - PM_FPS_DATA fps_data{}; - uint32_t num_swap_chains = 1; - pm_client.GetFramesPerSecondData(kPid, &fps_data, kRunWindowSize, - &num_swap_chains); - EXPECT_TRUE(1 == num_swap_chains); - EXPECT_TRUE((num_swap_chains == (uint32_t)calculated_fps_data.size())); - - EXPECT_TRUE( - Equal(fps_data.presented_fps, calculated_fps_data[0].presented_fps)); - EXPECT_TRUE( - Equal(fps_data.displayed_fps, calculated_fps_data[0].displayed_fps)); - EXPECT_TRUE( - Equal(fps_data.frame_time_ms, calculated_fps_data[0].frame_time_ms)); - EXPECT_TRUE(Equal(fps_data.gpu_busy, calculated_fps_data[0].gpu_busy)); - EXPECT_TRUE(Equal(fps_data.percent_dropped_frames, - calculated_fps_data[0].percent_dropped_frames)); - - EXPECT_TRUE((fps_data.sync_interval == 0)); - EXPECT_TRUE(fps_data.present_mode == calculated_fps_data[0].present_mode); - - EXPECT_TRUE((fps_data.num_presents == calculated_fps_data[0].num_presents)); - } -} - -TEST_F(PresentMonApiULT, TestGfxLatency) { - PmFrameGenerator frame_gen{PmFrameGenerator::FrameParams{}}; - LOG(INFO) << "\nSeed used for GetGfxLatencyData test: " - << frame_gen.GetSeed(); - frame_gen.GenerateFrames(kNumFrames); - - DWORD client_process_id = GetCurrentProcessId(); - std::string nsm_name; - GpuTelemetryBitset gpu_telemetry_cap_bits; - CpuTelemetryBitset cpu_telemetry_cap_bits; - gpu_telemetry_cap_bits.set(); - cpu_telemetry_cap_bits.set(); - Streamer test_streamer; - test_streamer.StartStreaming(client_process_id, kPid, nsm_name); - for (int i = 0; i <= frame_gen.GetNumFrames(); i++) { - auto frame = frame_gen.GetFrameData(i); - test_streamer.WriteFrameData(kPid, &frame, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - } - - PresentMonClient pm_client; - std::unique_ptr client = std::make_unique(); - client->Initialize(test_streamer.GetMapFileName(kPid)); - pm_client.clients_.emplace(kPid, std::move(client)); - pm_client.SetupClientCaches(kPid); - - EXPECT_NE(pm_client.clients_[kPid]->GetNamedSharedMemView(), nullptr); - - std::vector calculated_latency_data; - frame_gen.CalculateLatencyMetrics((uint32_t)frame_gen.GetNumFrames(), - kRunWindowSize, - calculated_latency_data); - - PM_GFX_LATENCY_DATA gfx_latency_data{}; - uint32_t num_swap_chains = 1; - pm_client.GetGfxLatencyData(kPid, &gfx_latency_data, - kRunWindowSize, &num_swap_chains); - EXPECT_TRUE(1 == num_swap_chains); - EXPECT_TRUE(num_swap_chains == (uint32_t)calculated_latency_data.size()); - EXPECT_TRUE(Equal(gfx_latency_data.display_latency_ms, - calculated_latency_data[0].display_latency_ms)); - EXPECT_TRUE(Equal(gfx_latency_data.render_latency_ms, - calculated_latency_data[0].render_latency_ms)); -} - -TEST_F(PresentMonApiULT, TestGfxLatencyDroppedFrames) { - PmFrameGenerator frame_gen{PmFrameGenerator::FrameParams{.percent_dropped = 50.}}; - LOG(INFO) << "\nSeed used for GetFramesPerSecondData test: " - << frame_gen.GetSeed(); - frame_gen.SetFps(302.); - frame_gen.GenerateFrames(kNumFrames); - - DWORD client_process_id = GetCurrentProcessId(); - std::string nsm_name; - GpuTelemetryBitset gpu_telemetry_cap_bits; - CpuTelemetryBitset cpu_telemetry_cap_bits; - gpu_telemetry_cap_bits.set(); - cpu_telemetry_cap_bits.set(); - Streamer test_streamer; - test_streamer.StartStreaming(client_process_id, kPid, nsm_name); - for (int i = 0; i <= frame_gen.GetNumFrames(); i++) { - auto frame = frame_gen.GetFrameData(i); - test_streamer.WriteFrameData(kPid, &frame, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - } - - PresentMonClient pm_client; - std::unique_ptr client = std::make_unique(); - client->Initialize(test_streamer.GetMapFileName(kPid)); - pm_client.clients_.emplace(kPid, std::move(client)); - pm_client.SetupClientCaches(kPid); - - EXPECT_NE(pm_client.clients_[kPid]->GetNamedSharedMemView(), nullptr); - std::vector calculated_latency_data; - frame_gen.CalculateLatencyMetrics((uint32_t)frame_gen.GetNumFrames(), - kRunWindowSize, calculated_latency_data); - - PM_GFX_LATENCY_DATA gfx_latency_data{}; - uint32_t num_swap_chains = 1; - pm_client.GetGfxLatencyData(kPid, &gfx_latency_data, kRunWindowSize, - &num_swap_chains); - EXPECT_TRUE(1 == num_swap_chains); - EXPECT_TRUE(num_swap_chains == (uint32_t)calculated_latency_data.size()); - EXPECT_TRUE(Equal(gfx_latency_data.display_latency_ms, - calculated_latency_data[0].display_latency_ms)); - EXPECT_TRUE(Equal(gfx_latency_data.render_latency_ms, - calculated_latency_data[0].render_latency_ms)); -} - - -TEST_F(PresentMonApiULT, TestSingleEntryFpsPercentiles) { - PmFrameGenerator frame_gen{PmFrameGenerator::FrameParams{}}; - LOG(INFO) << "\nSeed used for TestSingleEntryFpsPercentiles test: " - << frame_gen.GetSeed(); - frame_gen.GenerateFrames(kNumFrames); - - DWORD client_process_id = GetCurrentProcessId(); - std::string nsm_name; - GpuTelemetryBitset gpu_telemetry_cap_bits; - CpuTelemetryBitset cpu_telemetry_cap_bits; - gpu_telemetry_cap_bits.set(); - cpu_telemetry_cap_bits.set(); - Streamer test_streamer; - test_streamer.StartStreaming(client_process_id, kPid, nsm_name); - for (int i = 0; i <= frame_gen.GetNumFrames(); i++) { - auto frame = frame_gen.GetFrameData(i); - test_streamer.WriteFrameData(kPid, &frame, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - } - - PresentMonClient pm_client; - std::unique_ptr client = std::make_unique(); - client->Initialize(test_streamer.GetMapFileName(kPid)); - pm_client.clients_.emplace(kPid, std::move(client)); - pm_client.SetupClientCaches(kPid); - - EXPECT_NE(pm_client.clients_[kPid]->GetNamedSharedMemView(), nullptr); - - std::vector calculated_fps_data; - frame_gen.CalculateFpsMetrics((uint32_t)frame_gen.GetNumFrames(), 8, - calculated_fps_data); - - PM_FPS_DATA fps_data; - uint32_t num_swap_chains = 1; - pm_client.GetFramesPerSecondData(10, &fps_data, 8, &num_swap_chains); - EXPECT_TRUE(1 == num_swap_chains); - EXPECT_TRUE((num_swap_chains == (uint32_t)calculated_fps_data.size())); - - EXPECT_TRUE(fltCmp(fps_data.presented_fps.percentile_99, - calculated_fps_data[0].presented_fps.percentile_99, - kErrorTolerance)); - EXPECT_TRUE(fltCmp(fps_data.presented_fps.percentile_95, - calculated_fps_data[0].presented_fps.percentile_95, - kErrorTolerance)); - EXPECT_TRUE(fltCmp(fps_data.presented_fps.percentile_90, - calculated_fps_data[0].presented_fps.percentile_90, - kErrorTolerance)); - - EXPECT_TRUE(fltCmp(fps_data.displayed_fps.percentile_99, - calculated_fps_data[0].displayed_fps.percentile_99, - kErrorTolerance)); - EXPECT_TRUE(fltCmp(fps_data.displayed_fps.percentile_95, - calculated_fps_data[0].displayed_fps.percentile_95, - kErrorTolerance)); - EXPECT_TRUE(fltCmp(fps_data.displayed_fps.percentile_90, - calculated_fps_data[0].displayed_fps.percentile_90, - kErrorTolerance)); -} - -TEST_F(PresentMonApiULT, TestSingleEntryGfxLatencyPercentiles) { - PmFrameGenerator frame_gen{PmFrameGenerator::FrameParams{}}; - LOG(INFO) << "\nSeed used for TestSingleEntryLatencyPercentiles test: " - << frame_gen.GetSeed(); - frame_gen.GenerateFrames(20); - - DWORD client_process_id = GetCurrentProcessId(); - std::string nsm_name; - GpuTelemetryBitset gpu_telemetry_cap_bits; - CpuTelemetryBitset cpu_telemetry_cap_bits; - gpu_telemetry_cap_bits.set(); - cpu_telemetry_cap_bits.set(); - Streamer test_streamer; - test_streamer.StartStreaming(client_process_id, kPid, nsm_name); - for (int i = 0; i <= frame_gen.GetNumFrames(); i++) { - auto frame = frame_gen.GetFrameData(i); - test_streamer.WriteFrameData(kPid, &frame, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - } - - PresentMonClient pm_client; - std::unique_ptr client = std::make_unique(); - client->Initialize(test_streamer.GetMapFileName(kPid)); - pm_client.clients_.emplace(kPid, std::move(client)); - pm_client.SetupClientCaches(kPid); - - EXPECT_NE(pm_client.clients_[kPid]->GetNamedSharedMemView(), nullptr); - - std::vector calculated_latency_data; - frame_gen.CalculateLatencyMetrics((uint32_t)frame_gen.GetNumFrames(), - 8, - calculated_latency_data); - - PM_GFX_LATENCY_DATA gfx_latency_data; - uint32_t num_swap_chains = 1; - pm_client.GetGfxLatencyData(kPid, &gfx_latency_data, 8, - &num_swap_chains); - EXPECT_EQ(1, num_swap_chains); - EXPECT_EQ(num_swap_chains, (uint32_t)calculated_latency_data.size()); - EXPECT_TRUE(fltCmp(gfx_latency_data.display_latency_ms.avg, - calculated_latency_data[0].display_latency_ms.avg, - kErrorTolerance)); - EXPECT_TRUE(fltCmp(gfx_latency_data.display_latency_ms.percentile_99, - calculated_latency_data[0].display_latency_ms.percentile_99, - kErrorTolerance)); - EXPECT_TRUE(fltCmp(gfx_latency_data.display_latency_ms.percentile_95, - calculated_latency_data[0].display_latency_ms.percentile_95, - kErrorTolerance)); - EXPECT_TRUE(fltCmp(gfx_latency_data.display_latency_ms.percentile_90, - calculated_latency_data[0].display_latency_ms.percentile_90, - kErrorTolerance)); - EXPECT_TRUE(fltCmp(gfx_latency_data.display_latency_ms.high, - calculated_latency_data[0].display_latency_ms.high, - kErrorTolerance)); - EXPECT_TRUE(fltCmp(gfx_latency_data.display_latency_ms.low, - calculated_latency_data[0].display_latency_ms.low, - kErrorTolerance)); - - EXPECT_TRUE(fltCmp(gfx_latency_data.render_latency_ms.avg, - calculated_latency_data[0].render_latency_ms.avg, - kErrorTolerance)); - EXPECT_TRUE(fltCmp(gfx_latency_data.render_latency_ms.percentile_99, - calculated_latency_data[0].render_latency_ms.percentile_99, - kErrorTolerance)); - EXPECT_TRUE(fltCmp(gfx_latency_data.render_latency_ms.percentile_95, - calculated_latency_data[0].render_latency_ms.percentile_95, - kErrorTolerance)); - EXPECT_TRUE(fltCmp(gfx_latency_data.render_latency_ms.percentile_90, - calculated_latency_data[0].render_latency_ms.percentile_90, - kErrorTolerance)); - EXPECT_TRUE(fltCmp(gfx_latency_data.render_latency_ms.high, - calculated_latency_data[0].render_latency_ms.high, - kErrorTolerance)); - EXPECT_TRUE(fltCmp(gfx_latency_data.render_latency_ms.low, - calculated_latency_data[0].render_latency_ms.low, - kErrorTolerance)); -} - -TEST_F(PresentMonApiULT, TestGpuMetrics) { - PmFrameGenerator frame_gen{PmFrameGenerator::FrameParams{.gpu_power_limited_percent = 50.}}; - LOG(INFO) << "\nSeed used for TestGpuMetrics test: " << frame_gen.GetSeed(); - frame_gen.GenerateFrames(kNumFrames); - - DWORD client_process_id = GetCurrentProcessId(); - std::string nsm_name; - Streamer test_streamer; - GpuTelemetryBitset gpu_telemetry_cap_bits; - CpuTelemetryBitset cpu_telemetry_cap_bits; - gpu_telemetry_cap_bits.set(); - cpu_telemetry_cap_bits.set(); - test_streamer.StartStreaming(client_process_id, kPid, nsm_name); - for (int i = 0; i <= frame_gen.GetNumFrames(); i++) { - auto frame = frame_gen.GetFrameData(i); - test_streamer.WriteFrameData(kPid, &frame, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - } - - PresentMonClient pm_client; - std::unique_ptr client = std::make_unique(); - client->Initialize(test_streamer.GetMapFileName(kPid)); - pm_client.clients_.emplace(kPid, std::move(client)); - pm_client.SetupClientCaches(kPid); - - EXPECT_NE(pm_client.clients_[kPid]->GetNamedSharedMemView(), nullptr); - - PM_GPU_DATA calculated_gpu_data{}; - frame_gen.CalculateGpuMetrics((uint32_t)frame_gen.GetNumFrames(), - kRunWindowSize, gpu_telemetry_cap_bits, - calculated_gpu_data); - - PM_GPU_DATA gfx_gpu_data{}; - pm_client.GetGpuData(kPid, &gfx_gpu_data, kRunWindowSize); - - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_power_w, gfx_gpu_data.gpu_power_w)); - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_sustained_power_limit_w, - gfx_gpu_data.gpu_sustained_power_limit_w)); - EXPECT_TRUE( - Equal(calculated_gpu_data.gpu_voltage_v, gfx_gpu_data.gpu_voltage_v)); - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_frequency_mhz, - gfx_gpu_data.gpu_frequency_mhz)); - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_temperature_c, - gfx_gpu_data.gpu_temperature_c)); - EXPECT_TRUE( - Equal(calculated_gpu_data.gpu_utilization, gfx_gpu_data.gpu_utilization)); - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_render_compute_utilization, - gfx_gpu_data.gpu_render_compute_utilization)); - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_media_utilization, - gfx_gpu_data.gpu_media_utilization)); - EXPECT_TRUE( - Equal(calculated_gpu_data.vram_power_w, gfx_gpu_data.vram_power_w)); - EXPECT_TRUE( - Equal(calculated_gpu_data.vram_voltage_v, gfx_gpu_data.vram_voltage_v)); - EXPECT_TRUE(Equal(calculated_gpu_data.vram_frequency_mhz, - gfx_gpu_data.vram_frequency_mhz)); - EXPECT_TRUE(Equal(calculated_gpu_data.vram_effective_frequency_gbps, - gfx_gpu_data.vram_effective_frequency_gbps)); - EXPECT_TRUE(Equal(calculated_gpu_data.vram_read_bandwidth_bps, - gfx_gpu_data.vram_read_bandwidth_bps)); - EXPECT_TRUE(Equal(calculated_gpu_data.vram_write_bandwidth_bps, - gfx_gpu_data.vram_write_bandwidth_bps)); - EXPECT_TRUE(Equal(calculated_gpu_data.vram_temperature_c, - gfx_gpu_data.vram_temperature_c)); - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_mem_total_size_b, - gfx_gpu_data.gpu_mem_total_size_b)); - EXPECT_TRUE( - Equal(calculated_gpu_data.gpu_mem_used_b, gfx_gpu_data.gpu_mem_used_b)); - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_mem_utilization, - gfx_gpu_data.gpu_mem_utilization)); - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_mem_max_bandwidth_bps, - gfx_gpu_data.gpu_mem_max_bandwidth_bps)); - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_mem_read_bandwidth_bps, - gfx_gpu_data.gpu_mem_read_bandwidth_bps)); - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_mem_write_bandwidth_bps, - gfx_gpu_data.gpu_mem_write_bandwidth_bps)); - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_power_limited, - gfx_gpu_data.gpu_power_limited)); - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_temperature_limited, - gfx_gpu_data.gpu_temperature_limited)); - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_current_limited, - gfx_gpu_data.gpu_current_limited)); - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_voltage_limited, - gfx_gpu_data.gpu_voltage_limited)); - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_utilization_limited, - gfx_gpu_data.gpu_utilization_limited)); - EXPECT_TRUE(Equal(calculated_gpu_data.vram_power_limited, - gfx_gpu_data.vram_power_limited)); - EXPECT_TRUE(Equal(calculated_gpu_data.vram_temperature_limited, - gfx_gpu_data.vram_temperature_limited)); - EXPECT_TRUE(Equal(calculated_gpu_data.vram_current_limited, - gfx_gpu_data.vram_current_limited)); - EXPECT_TRUE(Equal(calculated_gpu_data.vram_voltage_limited, - gfx_gpu_data.vram_voltage_limited)); - EXPECT_TRUE(Equal(calculated_gpu_data.vram_utilization_limited, - gfx_gpu_data.vram_utilization_limited)); - for (int i = 0; i < MAX_PM_FAN_COUNT; i++) { - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_fan_speed_rpm[i], - gfx_gpu_data.gpu_fan_speed_rpm[i])); - } -} - -TEST_F(PresentMonApiULT, TestTelemetryCapBits) { - PmFrameGenerator frame_gen{ - PmFrameGenerator::FrameParams{}}; - LOG(INFO) << "\nSeed used for TestTelemetryCapBits test: " << frame_gen.GetSeed(); - frame_gen.GenerateFrames(kNumFrames); - - DWORD client_process_id = GetCurrentProcessId(); - std::string nsm_name; - Streamer test_streamer; - GpuTelemetryBitset gpu_telemetry_cap_bits; - CpuTelemetryBitset cpu_telemetry_cap_bits; - { - // Both the GPU and CPU metrics tests enable all of the telemetry bits. - // In this test only enable a single GPU and CPU telemetry item - gpu_telemetry_cap_bits.set( - static_cast(GpuTelemetryCapBits::gpu_utilization), true); - cpu_telemetry_cap_bits.set( - static_cast(CpuTelemetryCapBits::cpu_utilization), true); - test_streamer.StartStreaming(client_process_id, kPid, nsm_name); - for (int i = 0; i <= frame_gen.GetNumFrames(); i++) { - auto frame = frame_gen.GetFrameData(i); - test_streamer.WriteFrameData(kPid, &frame, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - } - - PresentMonClient pm_client; - std::unique_ptr client = std::make_unique(); - client->Initialize(test_streamer.GetMapFileName(kPid)); - pm_client.clients_.emplace(kPid, std::move(client)); - pm_client.SetupClientCaches(kPid); - - EXPECT_NE(pm_client.clients_[kPid]->GetNamedSharedMemView(), nullptr); - - PM_GPU_DATA calculated_gpu_data{}; - frame_gen.CalculateGpuMetrics((uint32_t)frame_gen.GetNumFrames(), - kRunWindowSize, gpu_telemetry_cap_bits, - calculated_gpu_data); - PM_GPU_DATA gfx_gpu_data{}; - pm_client.GetGpuData(kPid, &gfx_gpu_data, kRunWindowSize); - - // Check to make sure only GPU Utilization is valid, has the correct values - // and that all other telemetry items are not valid - EXPECT_EQ(gfx_gpu_data.gpu_utilization.valid, true); - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_utilization, - gfx_gpu_data.gpu_utilization)); - EXPECT_EQ(gfx_gpu_data.gpu_power_w.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_sustained_power_limit_w.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_voltage_v.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_frequency_mhz.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_temperature_c.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_render_compute_utilization.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_media_utilization.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_power_w.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_voltage_v.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_frequency_mhz.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_effective_frequency_gbps.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_read_bandwidth_bps.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_write_bandwidth_bps.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_temperature_c.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_mem_total_size_b.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_mem_used_b.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_mem_utilization.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_mem_max_bandwidth_bps.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_mem_read_bandwidth_bps.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_mem_write_bandwidth_bps.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_power_limited.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_temperature_limited.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_current_limited.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_voltage_limited.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_utilization_limited.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_power_limited.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_temperature_limited.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_current_limited.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_voltage_limited.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_utilization_limited.valid, false); - for (int i = 0; i < MAX_PM_FAN_COUNT; i++) { - EXPECT_EQ(gfx_gpu_data.gpu_fan_speed_rpm[i].valid, false); - } - // Now inspect CPU telemetry. Again a single telemetry should - // be valid (CPU Utilization). - PM_CPU_DATA calculated_cpu_data{}; - frame_gen.CalculateCpuMetrics((uint32_t)frame_gen.GetNumFrames(), - kRunWindowSize, cpu_telemetry_cap_bits, - calculated_cpu_data); - PM_CPU_DATA cpu_data{}; - pm_client.GetCpuData(kPid, &cpu_data, kRunWindowSize); - EXPECT_EQ(cpu_data.cpu_utilization.valid, true); - EXPECT_TRUE( - Equal(calculated_cpu_data.cpu_utilization, cpu_data.cpu_utilization)); - EXPECT_EQ(cpu_data.cpu_power_w.valid, false); - EXPECT_EQ(cpu_data.cpu_power_limit_w.valid, false); - EXPECT_EQ(cpu_data.cpu_temperature_c.valid, false); - EXPECT_EQ(cpu_data.cpu_frequency.valid, false); - } - - { - gpu_telemetry_cap_bits.reset(); - cpu_telemetry_cap_bits.reset(); - - gpu_telemetry_cap_bits.set( - static_cast(GpuTelemetryCapBits::gpu_utilization), true); - gpu_telemetry_cap_bits.set( - static_cast( - GpuTelemetryCapBits::gpu_render_compute_utilization), - true); - gpu_telemetry_cap_bits.set( - static_cast(GpuTelemetryCapBits::gpu_media_utilization), - true); - gpu_telemetry_cap_bits.set( - static_cast(GpuTelemetryCapBits::fan_speed_2), true); - gpu_telemetry_cap_bits.set( - static_cast(GpuTelemetryCapBits::vram_power_limited), true); - cpu_telemetry_cap_bits.set( - static_cast(CpuTelemetryCapBits::cpu_frequency), true); - - test_streamer.StartStreaming(client_process_id, kPid, nsm_name); - for (int i = 0; i <= frame_gen.GetNumFrames(); i++) { - auto frame = frame_gen.GetFrameData(i); - test_streamer.WriteFrameData(kPid, &frame, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - } - - PresentMonClient pm_client; - std::unique_ptr client = std::make_unique(); - client->Initialize(test_streamer.GetMapFileName(kPid)); - pm_client.clients_.emplace(kPid, std::move(client)); - pm_client.SetupClientCaches(kPid); - - EXPECT_NE(pm_client.clients_[kPid]->GetNamedSharedMemView(), nullptr); - - PM_GPU_DATA calculated_gpu_data{}; - frame_gen.CalculateGpuMetrics((uint32_t)frame_gen.GetNumFrames(), - kRunWindowSize, gpu_telemetry_cap_bits, - calculated_gpu_data); - PM_GPU_DATA gfx_gpu_data{}; - pm_client.GetGpuData(kPid, &gfx_gpu_data, kRunWindowSize); - - EXPECT_EQ(gfx_gpu_data.gpu_utilization.valid, true); - EXPECT_EQ(gfx_gpu_data.gpu_render_compute_utilization.valid, true); - EXPECT_EQ(gfx_gpu_data.gpu_media_utilization.valid, true); - EXPECT_EQ(gfx_gpu_data.gpu_fan_speed_rpm[2].valid, true); - EXPECT_EQ(gfx_gpu_data.vram_power_limited.valid, true); - - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_utilization, - gfx_gpu_data.gpu_utilization)); - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_render_compute_utilization, - gfx_gpu_data.gpu_render_compute_utilization)); - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_media_utilization, - gfx_gpu_data.gpu_media_utilization)); - EXPECT_TRUE(Equal(calculated_gpu_data.gpu_fan_speed_rpm[2], - gfx_gpu_data.gpu_fan_speed_rpm[2])); - EXPECT_TRUE(Equal(calculated_gpu_data.vram_power_limited, - gfx_gpu_data.vram_power_limited)); - - EXPECT_EQ(gfx_gpu_data.gpu_power_w.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_sustained_power_limit_w.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_voltage_v.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_frequency_mhz.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_temperature_c.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_power_w.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_voltage_v.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_frequency_mhz.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_effective_frequency_gbps.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_read_bandwidth_bps.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_write_bandwidth_bps.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_temperature_c.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_mem_total_size_b.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_mem_used_b.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_mem_utilization.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_mem_max_bandwidth_bps.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_mem_read_bandwidth_bps.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_mem_write_bandwidth_bps.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_power_limited.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_temperature_limited.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_current_limited.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_voltage_limited.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_utilization_limited.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_temperature_limited.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_current_limited.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_voltage_limited.valid, false); - EXPECT_EQ(gfx_gpu_data.vram_utilization_limited.valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_fan_speed_rpm[0].valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_fan_speed_rpm[1].valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_fan_speed_rpm[3].valid, false); - EXPECT_EQ(gfx_gpu_data.gpu_fan_speed_rpm[4].valid, false); - - // Now inspect CPU telemetry. Again a single telemetry should - // be valid (CPU Utilization). - PM_CPU_DATA calculated_cpu_data{}; - frame_gen.CalculateCpuMetrics((uint32_t)frame_gen.GetNumFrames(), - kRunWindowSize, cpu_telemetry_cap_bits, - calculated_cpu_data); - PM_CPU_DATA cpu_data{}; - pm_client.GetCpuData(kPid, &cpu_data, kRunWindowSize); - EXPECT_EQ(cpu_data.cpu_frequency.valid, true); - EXPECT_TRUE( - Equal(calculated_cpu_data.cpu_frequency, cpu_data.cpu_frequency)); - EXPECT_EQ(cpu_data.cpu_power_w.valid, false); - EXPECT_EQ(cpu_data.cpu_power_limit_w.valid, false); - EXPECT_EQ(cpu_data.cpu_temperature_c.valid, false); - EXPECT_EQ(cpu_data.cpu_utilization.valid, false); - } -} - - -TEST_F(PresentMonApiULT, TestCpuMetrics) { - PmFrameGenerator frame_gen{PmFrameGenerator::FrameParams{}}; - LOG(INFO) << "\nSeed used for TestCpuMetrics test: " << frame_gen.GetSeed(); - frame_gen.GenerateFrames(kNumFrames); - - DWORD client_process_id = GetCurrentProcessId(); - std::string nsm_name; - GpuTelemetryBitset gpu_telemetry_cap_bits; - CpuTelemetryBitset cpu_telemetry_cap_bits; - gpu_telemetry_cap_bits.set(); - cpu_telemetry_cap_bits.set(); - Streamer test_streamer; - test_streamer.StartStreaming(client_process_id, kPid, nsm_name); - for (int i = 0; i <= frame_gen.GetNumFrames(); i++) { - auto frame = frame_gen.GetFrameData(i); - test_streamer.WriteFrameData(kPid, &frame, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - } - - PresentMonClient pm_client; - std::unique_ptr client = std::make_unique(); - client->Initialize(test_streamer.GetMapFileName(kPid)); - pm_client.clients_.emplace(kPid, std::move(client)); - pm_client.SetupClientCaches(kPid); - - EXPECT_NE(pm_client.clients_[kPid]->GetNamedSharedMemView(), nullptr); - - PM_CPU_DATA calculated_cpu_data{}; - frame_gen.CalculateCpuMetrics((uint32_t)frame_gen.GetNumFrames(), - kRunWindowSize, cpu_telemetry_cap_bits, - calculated_cpu_data); - - PM_CPU_DATA cpu_data{}; - pm_client.GetCpuData(kPid, &cpu_data, kRunWindowSize); - - EXPECT_TRUE(Equal(calculated_cpu_data.cpu_utilization, cpu_data.cpu_utilization)); - EXPECT_TRUE(Equal(calculated_cpu_data.cpu_frequency, cpu_data.cpu_frequency)); -} - -TEST_F(PresentMonApiULT, TestFrameCapture) { - PmFrameGenerator frame_gen{PmFrameGenerator::FrameParams{}}; - LOG(INFO) << "\nSeed used for TestFrameCapture test: " << frame_gen.GetSeed(); - frame_gen.GenerateFrames(kNumFrames); - - DWORD client_process_id = GetCurrentProcessId(); - std::string nsm_name; - - Streamer test_streamer; - test_streamer.StartStreaming(client_process_id, kPid, nsm_name); - - PresentMonClient pm_client; - std::unique_ptr client = std::make_unique(); - client->Initialize(test_streamer.GetMapFileName(kPid)); - pm_client.clients_.emplace(kPid, std::move(client)); - EXPECT_NE(pm_client.clients_[kPid]->GetNamedSharedMemView(), nullptr); - - auto frame_data = std::make_unique(kNumFrames); - uint32_t num_frames = kNumFrames; - auto pm_status = pm_client.GetFrameData(kPid, false, &num_frames, frame_data.get()); - EXPECT_TRUE(num_frames == 0); - EXPECT_TRUE(pm_status == PM_STATUS::PM_STATUS_NO_DATA); - GpuTelemetryBitset gpu_telemetry_cap_bits; - CpuTelemetryBitset cpu_telemetry_cap_bits; - gpu_telemetry_cap_bits.set(); - cpu_telemetry_cap_bits.set(); - - // Stream a single frame. - auto frame = frame_gen.GetFrameData(0); - test_streamer.WriteFrameData(kPid, &frame, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - num_frames = kNumFrames; - // This first call initializes the frame data capture system on the pm - // client side. No frames will be captured on the first call - pm_status = pm_client.GetFrameData(kPid, false, &num_frames, frame_data.get()); - EXPECT_TRUE(num_frames == 0); - EXPECT_TRUE(pm_status == PM_STATUS::PM_STATUS_NO_DATA); - - // Stream the rest of the generated frames - for (int i = 1; i <= frame_gen.GetNumFrames(); i++) { - frame = frame_gen.GetFrameData(i); - test_streamer.WriteFrameData(kPid, &frame, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - } - num_frames = kNumFrames; - pm_status = pm_client.GetFrameData(kPid, false, &num_frames, frame_data.get()); - EXPECT_TRUE(num_frames == kNumFrames - 1); - EXPECT_TRUE(num_frames == (uint32_t)frame_gen.GetNumFrames()); - EXPECT_TRUE(pm_status == PM_STATUS::PM_STATUS_SUCCESS); - // Compare all recorded frames - for (uint32_t i = 0; i <= ((uint32_t)frame_gen.GetNumFrames() - 1); i++) { - EXPECT_TRUE(CompareFrameData( - frame_data[i], frame_gen.GetPmFrameData(i, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits))); - } - -} - -TEST_F(PresentMonApiULT, TestFrameCaptureCapBits) { - PmFrameGenerator frame_gen{PmFrameGenerator::FrameParams{}}; - LOG(INFO) << "\nSeed used for TestFrameCapture test: " << frame_gen.GetSeed(); - frame_gen.GenerateFrames(kNumFrames); - - DWORD client_process_id = GetCurrentProcessId(); - std::string nsm_name; - - Streamer test_streamer; - test_streamer.StartStreaming(client_process_id, kPid, nsm_name); - - PresentMonClient pm_client; - std::unique_ptr client = std::make_unique(); - client->Initialize(test_streamer.GetMapFileName(kPid)); - pm_client.clients_.emplace(kPid, std::move(client)); - EXPECT_NE(pm_client.clients_[kPid]->GetNamedSharedMemView(), nullptr); - - auto frame_data = std::make_unique(kNumFrames); - uint32_t num_frames = kNumFrames; - auto pm_status = - pm_client.GetFrameData(kPid, false, &num_frames, frame_data.get()); - EXPECT_TRUE(num_frames == 0); - EXPECT_TRUE(pm_status == PM_STATUS::PM_STATUS_NO_DATA); - GpuTelemetryBitset gpu_telemetry_cap_bits; - CpuTelemetryBitset cpu_telemetry_cap_bits; - // Both the GPU and CPU metrics tests enable all of the telemetry bits. - // In this test only enable a few metrics - gpu_telemetry_cap_bits.set( - static_cast(GpuTelemetryCapBits::gpu_power), true); - gpu_telemetry_cap_bits.set( - static_cast(GpuTelemetryCapBits::gpu_frequency), true); - gpu_telemetry_cap_bits.set( - static_cast(GpuTelemetryCapBits::gpu_utilization), true); - gpu_telemetry_cap_bits.set( - static_cast(GpuTelemetryCapBits::gpu_mem_size), true); - gpu_telemetry_cap_bits.set( - static_cast(GpuTelemetryCapBits::gpu_mem_used), true); - cpu_telemetry_cap_bits.set( - static_cast(CpuTelemetryCapBits::cpu_utilization), true); - - // Stream a single frame. - auto frame = frame_gen.GetFrameData(0); - test_streamer.WriteFrameData(kPid, &frame, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - num_frames = kNumFrames; - // This first call initializes the frame data capture system on the pm - // client side. No frames will be captured on the first call - pm_status = - pm_client.GetFrameData(kPid, false, &num_frames, frame_data.get()); - EXPECT_TRUE(num_frames == 0); - EXPECT_TRUE(pm_status == PM_STATUS::PM_STATUS_NO_DATA); - - // Stream the rest of the generated frames - for (int i = 1; i <= frame_gen.GetNumFrames(); i++) { - frame = frame_gen.GetFrameData(i); - test_streamer.WriteFrameData(kPid, &frame, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - } - num_frames = kNumFrames; - pm_status = - pm_client.GetFrameData(kPid, false, &num_frames, frame_data.get()); - EXPECT_TRUE(num_frames == kNumFrames - 1); - EXPECT_TRUE(num_frames == (uint32_t)frame_gen.GetNumFrames()); - EXPECT_TRUE(pm_status == PM_STATUS::PM_STATUS_SUCCESS); - // Compare all recorded frames - for (uint32_t i = 0; i <= ((uint32_t)frame_gen.GetNumFrames() - 1); i++) { - EXPECT_TRUE(CompareFrameData( - frame_data[i], frame_gen.GetPmFrameData(i, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits))); - } -} \ No newline at end of file diff --git a/IntelPresentMon/ULT/PmFrameGenerator.cpp b/IntelPresentMon/ULT/PmFrameGenerator.cpp deleted file mode 100644 index 8ba91c17..00000000 --- a/IntelPresentMon/ULT/PmFrameGenerator.cpp +++ /dev/null @@ -1,1243 +0,0 @@ -#include "PmFrameGenerator.h" - -PmFrameGenerator::PmFrameGenerator(const FrameParams& frame_params) - : app_name_{frame_params.app_name.value_or("test_app")}, - process_id_{frame_params.process_id.value_or(10)}, - runtime_{frame_params.runtime.value_or(Runtime::D3D9)}, - sync_interval_{frame_params.sync_interval.value_or(0)}, - present_flags_{frame_params.present_flags.value_or(512)}, - percent_dropped_{frame_params.percent_dropped.value_or(0.)}, - in_present_api_ms_{frame_params.in_present_api_ms.value_or(1.3707)}, - in_present_api_variation_ms_{ - frame_params.in_present_api_variation_ms.value_or(1.3)}, - between_presents_ms_{frame_params.between_presents_ms.value_or(7.15)}, - between_presents_variation_ms_{ - frame_params.between_presents_variation_ms.value_or(0.15)}, - percent_tearing_{frame_params.percent_tearing.value_or(0.)}, - present_mode_{frame_params.present_mode.value_or( - PresentMode::Hardware_Independent_Flip)}, - until_render_complete_ms_{ - frame_params.until_render_complete_ms.value_or(6.2663)}, - until_render_complete_variation_ms_{ - frame_params.until_render_complete_variation_ms.value_or(0.)}, - until_displayed_ms_{frame_params.until_displayed_ms.value_or(11.35)}, - until_displayed_variation_ms_{ - frame_params.until_displayed_ms.value_or(0.2)}, - until_display_change_ms_{ - frame_params.until_display_change_ms.value_or(7.15)}, - until_display_change_variation_ms_{ - frame_params.until_display_change_variation_ms.value_or(0.15)}, - until_render_start_ms_{ - frame_params.until_render_start_ms.value_or(-10.96)}, - until_render_start_variation_ms_{ - frame_params.until_render_start_variation_ms.value_or(0.)}, - qpc_time_{frame_params.qpc_time.value_or(0)}, - since_input_ms_{frame_params.since_input_ms.value_or(15.2)}, - since_input_variation_ms_{frame_params.since_input_ms.value_or(3.)}, - gpu_active_ms_{frame_params.gpu_active_ms.value_or(7.05)}, - gpu_active_variation_ms_{ - frame_params.gpu_active_variation_ms.value_or(0.0)}, - gpu_video_active_ms_{frame_params.gpu_video_active_ms.value_or(7.05)}, - gpu_video_active_variation_ms_{ - frame_params.gpu_video_active_variation_ms.value_or(0.0)}, - gpu_power_w_{frame_params.gpu_power_w.value_or(135.34)}, - gpu_power_variation_w_{frame_params.gpu_power_variation_w.value_or(37.)}, - gpu_sustained_power_limit_w_{ - frame_params.gpu_sustained_power_limit_w.value_or(190.)}, - gpu_sustained_power_limit_variation_w_{ - frame_params.gpu_sustained_power_limit_variation_w.value_or(0.)}, - gpu_voltage_v_{frame_params.gpu_voltage_v.value_or(1.032)}, - gpu_voltage_variation_v_{ - frame_params.gpu_voltage_variation_v.value_or(0.)}, - gpu_frequency_mhz_{frame_params.gpu_frequency_mhz.value_or(2400.)}, - gpu_frequency_variation_mhz_{ - frame_params.gpu_frequency_variation_mhz.value_or(0.)}, - gpu_temp_c_{frame_params.gpu_temp_c.value_or(62.7)}, - gpu_temp_variation_c_{ - frame_params.gpu_frequency_variation_mhz.value_or(1.01)}, - gpu_util_percent_{frame_params.gpu_util_percent.value_or(98.3)}, - gpu_util_variation_percent_{ - frame_params.gpu_util_variation_percent.value_or(5.9)}, - gpu_render_compute_util_percent_{ - frame_params.gpu_render_compute_util_percent.value_or(96.4)}, - gpu_render_compute_util_variation_percent_{ - frame_params.gpu_render_compute_util_variation_percent.value_or(7.)}, - gpu_media_util_percent_{frame_params.gpu_media_util_percent.value_or(0.)}, - gpu_media_util_variation_percent_{ - frame_params.gpu_media_util_variation_percent.value_or(0.)}, - vram_power_w_{frame_params.vram_power_w.value_or(0.)}, - vram_power_variation_w_{frame_params.vram_power_variation_w.value_or(0.)}, - vram_voltage_v_{frame_params.vram_voltage_v.value_or(0.)}, - vram_voltage_variation_v_{ - frame_params.vram_voltage_variation_v.value_or(0.)}, - vram_frequency_mhz_{frame_params.vram_frequency_mhz.value_or(2000.)}, - vram_frequency_variation_mhz_{ - frame_params.vram_frequency_variation_mhz.value_or(0.)}, - vram_effective_frequency_gbps_{ - frame_params.vram_effective_frequency_gbps.value_or(16000.)}, - vram_effective_frequency_variation_gbps_{ - frame_params.vram_effective_frequency_variation_gbps.value_or(0.)}, - vram_temp_c_{frame_params.vram_temp_c.value_or(71.)}, - vram_temp_variation_c_{frame_params.vram_temp_variation_c.value_or(1.2)}, - gpu_mem_total_size_b_{ - frame_params.gpu_mem_total_size_b.value_or(8589934592)}, - gpu_mem_total_size_variation_b_{ - frame_params.gpu_mem_total_size_variation_b.value_or(0)}, - gpu_mem_used_b_{frame_params.gpu_mem_total_size_b.value_or(2192377540)}, - gpu_mem_used_variation_b_{ - frame_params.gpu_mem_total_size_variation_b.value_or(3016908)}, - gpu_mem_max_bw_gbps_{ - frame_params.gpu_mem_max_bw_gbps.value_or(512000000000)}, - gpu_mem_max_bw_variation_gbps_{ - frame_params.gpu_mem_max_bw_variation_gbps.value_or(0)}, - gpu_mem_read_bw_bps_{ - frame_params.gpu_mem_read_bw_bps.value_or(55754930711)}, - gpu_mem_read_bw_variation_bps_{ - frame_params.gpu_mem_read_bw_variation_bps.value_or(4938578793)}, - gpu_mem_write_bw_bps_{ - frame_params.gpu_mem_max_bw_gbps.value_or(35272691238)}, - gpu_mem_write_bw_variation_bps_{ - frame_params.gpu_mem_write_bw_variation_bps.value_or(3051695995)}, - gpu_fan_speed_rpm_{ - frame_params.gpu_fan_speed_rpm.value_or(1070.2)}, - gpu_fan_speed_rpm_variation_rpm_{ - frame_params.gpu_fan_speed_rpm.value_or(100.2)}, - gpu_power_limited_percent_{ - frame_params.gpu_power_limited_percent.value_or(0.)}, - gpu_temp_limited_percent_{ - frame_params.gpu_temp_limited_percent.value_or(0.)}, - gpu_current_limited_percent_{ - frame_params.gpu_current_limited_percent.value_or(0.)}, - gpu_voltage_limited_percent_{ - frame_params.gpu_voltage_limited_percent.value_or(0.)}, - gpu_util_limited_percent_{ - frame_params.gpu_util_limited_percent.value_or(0.)}, - vram_power_limited_percent_{ - frame_params.vram_power_limited_percent.value_or(0.)}, - vram_temp_limited_percent_{ - frame_params.vram_temp_limited_percent.value_or(0.)}, - vram_current_limited_percent_{ - frame_params.vram_current_limited_percent.value_or(0.)}, - vram_voltage_limited_percent_{ - frame_params.vram_voltage_limited_percent.value_or(0.)}, - vram_util_limited_percent_{ - frame_params.vram_util_limited_percent.value_or(0.)}, - cpu_util_percent_{frame_params.cpu_util_percent.value_or(19.4)}, - cpu_util_variation_percent_{ - frame_params.cpu_util_variation_percent.value_or(5.86)}, - cpu_frequency_mhz_{frame_params.cpu_frequency_mhz.value_or(4212.9)}, - cpu_frequency_variation_mhz_{ - frame_params.cpu_frequency_variation_mhz.value_or(1070.2)} { - - QueryPerformanceFrequency(&qpc_frequency_); - QueryPerformanceCounter(&start_qpc_); - - swap_chains_.resize(1); - swap_chains_[0] = (uint64_t)1; -} - -void PmFrameGenerator::GenerateFrames(int num_frames) { - frames_.clear(); - frames_.resize(num_frames); - pmft_frames_.clear(); - pmft_frames_.resize(num_frames); - GeneratePresentData(); - GenerateGPUData(); - GenerateCPUData(); - return; -} - -size_t PmFrameGenerator::GetNumFrames() { - if (frames_.size() > 0) { - return frames_.size() - 1; - } else { - return frames_.size(); - } -} - -PmNsmFrameData PmFrameGenerator::GetFrameData(int frame_num) { - PmNsmFrameData temp_frame{}; - if (frame_num >= 0 && frame_num < frames_.size()) { - temp_frame = frames_[frame_num]; - } - return temp_frame; -} - -PM_FRAME_DATA PmFrameGenerator::GetPmFrameData( - int frame_num, GpuTelemetryBitset gpu_telemetry_cap_bits, - CpuTelemetryBitset cpu_telemetry_cap_bits) { - PM_FRAME_DATA temp_frame{}; - if (frame_num >= 0 && - (frame_num < pmft_frames_.size() && (frame_num < frames_.size()))) { - std::string temp_string = frames_[frame_num].present_event.application; - if (temp_string.size() < sizeof(temp_frame.application)) { - temp_string.copy(temp_frame.application, sizeof(temp_frame.application)); - } - temp_frame.process_id = frames_[frame_num].present_event.ProcessId; - temp_frame.swap_chain_address = - frames_[frame_num].present_event.SwapChainAddress; - temp_string = RuntimeToString(frames_[frame_num].present_event.Runtime); - if (temp_string.size() < sizeof(temp_frame.runtime)) { - temp_string.copy(temp_frame.runtime, sizeof(temp_frame.runtime)); - } - temp_frame.sync_interval = frames_[frame_num].present_event.SyncInterval; - temp_frame.present_flags = frames_[frame_num].present_event.PresentFlags; - temp_frame.dropped = pmft_frames_[frame_num].dropped; - temp_frame.time_in_seconds = pmft_frames_[frame_num].time_in_seconds; - temp_frame.ms_in_present_api = pmft_frames_[frame_num].ms_in_present_api; - temp_frame.ms_between_presents = - pmft_frames_[frame_num].ms_between_presents; - temp_frame.allows_tearing = - frames_[frame_num].present_event.SupportsTearing; - temp_frame.present_mode = pmft_frames_[frame_num].present_mode; - temp_frame.ms_until_render_complete = - pmft_frames_[frame_num].ms_until_render_complete; - temp_frame.ms_until_displayed = pmft_frames_[frame_num].ms_until_displayed; - temp_frame.ms_between_display_change = - pmft_frames_[frame_num].ms_between_display_change; - temp_frame.ms_until_render_start = - pmft_frames_[frame_num].ms_until_render_start; - temp_frame.qpc_time = pmft_frames_[frame_num].qpc_time; - temp_frame.ms_since_input = pmft_frames_[frame_num].ms_until_input; - temp_frame.ms_gpu_active = pmft_frames_[frame_num].ms_gpu_active; - temp_frame.ms_gpu_video_active = pmft_frames_[frame_num].ms_gpu_video_active; - - // Copy power telemetry - temp_frame.gpu_power_w.data = frames_[frame_num].power_telemetry.gpu_power_w; - temp_frame.gpu_power_w.valid = gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_power)]; - - temp_frame.gpu_sustained_power_limit_w.data = - frames_[frame_num].power_telemetry.gpu_sustained_power_limit_w; - temp_frame.gpu_sustained_power_limit_w.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_sustained_power_limit)]; - - temp_frame.gpu_voltage_v.data = frames_[frame_num].power_telemetry.gpu_voltage_v; - temp_frame.gpu_voltage_v.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_voltage)]; - - temp_frame.gpu_frequency_mhz.data = - frames_[frame_num].power_telemetry.gpu_frequency_mhz; - temp_frame.gpu_frequency_mhz.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_frequency)]; - - temp_frame.gpu_temperature_c.data = - frames_[frame_num].power_telemetry.gpu_temperature_c; - temp_frame.gpu_temperature_c.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_temperature)]; - - temp_frame.gpu_utilization.data = - frames_[frame_num].power_telemetry.gpu_utilization; - temp_frame.gpu_utilization.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_utilization)]; - - temp_frame.gpu_render_compute_utilization.data = - frames_[frame_num].power_telemetry.gpu_render_compute_utilization; - temp_frame.gpu_render_compute_utilization.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_render_compute_utilization)]; - - temp_frame.gpu_media_utilization.data = - frames_[frame_num].power_telemetry.gpu_media_utilization; - temp_frame.gpu_media_utilization.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_media_utilization)]; - - temp_frame.vram_power_w.data = - frames_[frame_num].power_telemetry.vram_power_w; - temp_frame.vram_power_w.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_power)]; - - temp_frame.vram_voltage_v.data = - frames_[frame_num].power_telemetry.vram_voltage_v; - temp_frame.vram_voltage_v.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_voltage)]; - - temp_frame.vram_frequency_mhz.data = - frames_[frame_num].power_telemetry.vram_frequency_mhz; - temp_frame.vram_frequency_mhz.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_frequency)]; - - temp_frame.vram_effective_frequency_gbs.data = - frames_[frame_num].power_telemetry.vram_effective_frequency_gbps; - temp_frame.vram_effective_frequency_gbs.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_effective_frequency)]; - - temp_frame.vram_temperature_c.data = - frames_[frame_num].power_telemetry.vram_temperature_c; - temp_frame.vram_temperature_c.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_temperature)]; - - for (size_t i = 0; i < MAX_PM_FAN_COUNT; i++) { - temp_frame.fan_speed_rpm[i].data = - frames_[frame_num].power_telemetry.fan_speed_rpm[i]; - temp_frame.fan_speed_rpm[i].valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::fan_speed_0) + i]; - } - - for (uint32_t i = 0; i < MAX_PM_PSU_COUNT; i++) { - temp_frame.psu_type[i].data = - TranslatePsuType(frames_[frame_num].power_telemetry.psu[i].psu_type); - temp_frame.psu_type[i].valid = gpu_telemetry_cap_bits - [static_cast(GpuTelemetryCapBits::psu_info_0) + i]; - - temp_frame.psu_power[i].data = - frames_[frame_num].power_telemetry.psu[i].psu_power; - temp_frame.psu_power[i].valid = gpu_telemetry_cap_bits - [static_cast(GpuTelemetryCapBits::psu_info_0) + i]; - - temp_frame.psu_voltage[i].data = - frames_[frame_num].power_telemetry.psu[i].psu_voltage; - temp_frame.psu_voltage[i].valid = gpu_telemetry_cap_bits - [static_cast(GpuTelemetryCapBits::psu_info_0) + i]; - } - - temp_frame.gpu_mem_total_size_b.data = - frames_[frame_num].power_telemetry.gpu_mem_total_size_b; - temp_frame.gpu_mem_total_size_b.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_size)]; - - temp_frame.gpu_mem_used_b.data = - frames_[frame_num].power_telemetry.gpu_mem_used_b; - temp_frame.gpu_mem_used_b.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_used)]; - - temp_frame.gpu_mem_max_bandwidth_bps.data = - frames_[frame_num].power_telemetry.gpu_mem_max_bandwidth_bps; - temp_frame.gpu_mem_max_bandwidth_bps.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_max_bandwidth)]; - - temp_frame.gpu_mem_read_bandwidth_bps.data = - frames_[frame_num].power_telemetry.gpu_mem_read_bandwidth_bps; - temp_frame.gpu_mem_read_bandwidth_bps.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_read_bandwidth)]; - - temp_frame.gpu_mem_write_bandwidth_bps.data = - frames_[frame_num].power_telemetry.gpu_mem_write_bandwidth_bps; - temp_frame.gpu_mem_write_bandwidth_bps.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_write_bandwidth)]; - - temp_frame.gpu_power_limited.data = - frames_[frame_num].power_telemetry.gpu_power_limited; - temp_frame.gpu_power_limited.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_power_limited)]; - - temp_frame.gpu_temperature_limited.data = - frames_[frame_num].power_telemetry.gpu_temperature_limited; - temp_frame.gpu_temperature_limited.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_temperature_limited)]; - - temp_frame.gpu_current_limited.data = - frames_[frame_num].power_telemetry.gpu_current_limited; - temp_frame.gpu_current_limited.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_current_limited)]; - - temp_frame.gpu_voltage_limited.data = - frames_[frame_num].power_telemetry.gpu_voltage_limited; - temp_frame.gpu_voltage_limited.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_voltage_limited)]; - - temp_frame.gpu_utilization_limited.data = - frames_[frame_num].power_telemetry.gpu_utilization_limited; - temp_frame.gpu_utilization_limited.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_utilization_limited)]; - - temp_frame.vram_power_limited.data = - frames_[frame_num].power_telemetry.vram_power_limited; - temp_frame.vram_power_limited.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_power_limited)]; - - temp_frame.vram_temperature_limited.data = - frames_[frame_num].power_telemetry.vram_temperature_limited; - temp_frame.vram_temperature_limited.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_temperature_limited)]; - - temp_frame.vram_current_limited.data = - frames_[frame_num].power_telemetry.vram_current_limited; - temp_frame.vram_current_limited.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_current_limited)]; - - temp_frame.vram_voltage_limited.data = - frames_[frame_num].power_telemetry.vram_voltage_limited; - temp_frame.vram_voltage_limited.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_voltage_limited)]; - - temp_frame.vram_utilization_limited.data = - frames_[frame_num].power_telemetry.vram_utilization_limited; - temp_frame.vram_utilization_limited.valid = - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_utilization_limited)]; - - // Cpu telemetry - temp_frame.cpu_utilization.data = - frames_[frame_num].cpu_telemetry.cpu_utilization; - temp_frame.cpu_utilization.valid = - cpu_telemetry_cap_bits[static_cast( - CpuTelemetryCapBits::cpu_utilization)]; - - temp_frame.cpu_frequency.data = - frames_[frame_num].cpu_telemetry.cpu_frequency; - temp_frame.cpu_frequency.valid = - cpu_telemetry_cap_bits[static_cast( - CpuTelemetryCapBits::cpu_frequency)]; - } - return temp_frame; -} - -void PmFrameGenerator::GeneratePresentData() { - if (frames_.size() != pmft_frames_.size()) { - return; - } - - int swap_chain_idx = 0; - uint64_t last_displayed_screen_time = 0; - for (int i = 0; i < (int)frames_.size(); i++) { - if (app_name_.size() < sizeof(frames_[i].present_event.application)) { - app_name_.copy(frames_[i].present_event.application, - sizeof(frames_[i].present_event.application)); - } - frames_[i].present_event.ProcessId = process_id_; - frames_[i].present_event.SwapChainAddress = swap_chains_[swap_chain_idx]; - pmft_frames_[i].swap_chain = swap_chains_[swap_chain_idx]; - // Set the next swap chain index - swap_chain_idx += 1; - swap_chain_idx = swap_chain_idx % swap_chains_.size(); - frames_[i].present_event.Runtime = runtime_; - frames_[i].present_event.SyncInterval = sync_interval_; - frames_[i].present_event.PresentFlags = present_flags_; - // Using the specified percent dropped set the final state of the - // present - if (uniform_random_gen_.Generate(0., 100.) < percent_dropped_) { - frames_[i].present_event.FinalState = PresentResult::Discarded; - } else { - frames_[i].present_event.FinalState = PresentResult::Presented; - } - - frames_[i].present_event.PresentMode = present_mode_; - pmft_frames_[i].present_mode = TranslatePresentMode(present_mode_); - // Calculate the ms between presents - pmft_frames_[i].ms_between_presents = GetAlteredTimingValue( - between_presents_ms_, between_presents_variation_ms_); - // Convert ms between presents to qpc ticks - auto qpc_ticks_between_presents = SecondsDeltaToQpc( - pmft_frames_[i].ms_between_presents / 1000., qpc_frequency_); - // Using the ms between presents calculate the frame qpc times. - if (i == 0) { - // Set the first frame to the QPC of when the frame generator was - // initialized. - frames_[i].present_event.PresentStartTime = start_qpc_.QuadPart; - // Also on the first frame calculate a qpc for the last presented qpc - // using the created ms_between_presents and the start qpc. - frames_[i].present_event.last_present_qpc = - frames_[i].present_event.PresentStartTime - - qpc_ticks_between_presents; - - } else { - frames_[i].present_event.PresentStartTime = - frames_[i - 1].present_event.PresentStartTime + - qpc_ticks_between_presents; - frames_[i].present_event.last_present_qpc = - frames_[i - 1].present_event.PresentStartTime; - } - pmft_frames_[i].qpc_time = frames_[i].present_event.PresentStartTime; - pmft_frames_[i].time_in_seconds = QpcDeltaToSeconds( - pmft_frames_[i].qpc_time - pmft_frames_[0].qpc_time, qpc_frequency_); - - pmft_frames_[i].ms_in_present_api = - GetAlteredTimingValue( - in_present_api_ms_, in_present_api_variation_ms_); - frames_[i].present_event.TimeInPresent = - SecondsDeltaToQpc(pmft_frames_[i].ms_in_present_api / 1000., - qpc_frequency_); - pmft_frames_[i].ms_until_render_start = GetAlteredTimingValue( - until_render_start_ms_, until_render_start_variation_ms_); - if (pmft_frames_[i].ms_until_render_start > 0.) { - // If positive this means GPU start time occurred after present - // start. Convert the render start time from ms to qpc ticks - frames_[i].present_event.GPUStartTime = SecondsDeltaToQpc( - pmft_frames_[i].ms_until_render_start / 1000., qpc_frequency_); - // Add in the start time of the present to get the correct gpu - // start time - frames_[i].present_event.GPUStartTime = - frames_[i].present_event.PresentStartTime + - frames_[i].present_event.GPUStartTime; - } else { - // GPUStartTime is an unsigned int 64 so we need to convert to - // positive. - frames_[i].present_event.GPUStartTime = SecondsDeltaToQpc( - pmft_frames_[i].ms_until_render_start / -1000., qpc_frequency_); - // SUBTRACT the start time of the present to get the correct gpu - // start time - frames_[i].present_event.GPUStartTime = - frames_[i].present_event.PresentStartTime - - frames_[i].present_event.GPUStartTime; - } - - pmft_frames_[i].ms_until_render_complete = GetAlteredTimingValue( - until_render_complete_ms_, until_render_complete_variation_ms_); - if (pmft_frames_[i].ms_until_render_complete > 0.0) { - frames_[i].present_event.ReadyTime = SecondsDeltaToQpc( - pmft_frames_[i].ms_until_render_complete / 1000., qpc_frequency_); - frames_[i].present_event.ReadyTime = - frames_[i].present_event.PresentStartTime + - frames_[i].present_event.ReadyTime; - } else { - // ReadyTime is also an unsigned int 64, same as above. - frames_[i].present_event.ReadyTime = SecondsDeltaToQpc( - pmft_frames_[i].ms_until_render_complete / -1000., qpc_frequency_); - frames_[i].present_event.ReadyTime = - frames_[i].present_event.PresentStartTime - - frames_[i].present_event.ReadyTime; - } - - if (frames_[i].present_event.FinalState == PresentResult::Presented) { - pmft_frames_[i].dropped = false; - pmft_frames_[i].ms_until_displayed = GetAlteredTimingValue( - until_displayed_ms_, until_displayed_variation_ms_); - frames_[i].present_event.ScreenTime = SecondsDeltaToQpc( - pmft_frames_[i].ms_until_displayed / 1000., qpc_frequency_); - frames_[i].present_event.ScreenTime += - frames_[i].present_event.PresentStartTime; - if (last_displayed_screen_time != 0) { - pmft_frames_[i].ms_between_display_change = QpcDeltaToMs( - frames_[i].present_event.ScreenTime - last_displayed_screen_time, - qpc_frequency_); - frames_[i].present_event.last_displayed_qpc = - last_displayed_screen_time; - - } else { - pmft_frames_[i].ms_between_display_change = 0.; - frames_[i].present_event.last_displayed_qpc = 0; - } - last_displayed_screen_time = frames_[i].present_event.ScreenTime; - } else { - pmft_frames_[i].ms_until_displayed = 0; - pmft_frames_[i].ms_between_display_change = 0.; - pmft_frames_[i].dropped = true; - frames_[i].present_event.ScreenTime = 0; - } - - // Internal frame data fields; Remove for public build - pmft_frames_[i].ms_until_input = GetAlteredTimingValue(since_input_ms_, - since_input_variation_ms_); - if (pmft_frames_[i].ms_until_input != 0.) { - frames_[i].present_event.InputTime = SecondsDeltaToQpc( - pmft_frames_[i].ms_until_input / 1000., qpc_frequency_); - frames_[i].present_event.InputTime = - frames_[i].present_event.PresentStartTime - - frames_[i].present_event.InputTime; - } else { - frames_[i].present_event.InputTime = 0; - } - pmft_frames_[i].ms_gpu_active = - GetAlteredTimingValue(gpu_active_ms_, gpu_active_variation_ms_); - frames_[i].present_event.GPUDuration = SecondsDeltaToQpc( - pmft_frames_[i].ms_gpu_active / 1000., qpc_frequency_); - pmft_frames_[i].ms_gpu_video_active = - GetAlteredTimingValue(gpu_video_active_ms_, gpu_video_active_variation_ms_); - frames_[i].present_event.GPUVideoDuration = SecondsDeltaToQpc( - pmft_frames_[i].ms_gpu_video_active / 1000., qpc_frequency_); - } -} - -void PmFrameGenerator::GenerateGPUData() { - for (int i = 0; i < (int)frames_.size(); i++) { - frames_[i].power_telemetry.gpu_power_w = - GetAlteredTimingValue(gpu_power_w_, gpu_power_variation_w_); - frames_[i].power_telemetry.gpu_sustained_power_limit_w = - GetAlteredTimingValue(gpu_sustained_power_limit_w_, - gpu_sustained_power_limit_variation_w_); - frames_[i].power_telemetry.gpu_voltage_v = - GetAlteredTimingValue(gpu_voltage_v_, gpu_voltage_variation_v_); - frames_[i].power_telemetry.gpu_frequency_mhz = - GetAlteredTimingValue( - gpu_frequency_mhz_, gpu_frequency_variation_mhz_); - frames_[i].power_telemetry.gpu_temperature_c = - GetAlteredTimingValue(gpu_temp_c_, gpu_temp_variation_c_); - frames_[i].power_telemetry.gpu_utilization = GetAlteredTimingValue( - gpu_util_percent_, gpu_util_variation_percent_); - frames_[i].power_telemetry.gpu_render_compute_utilization = - GetAlteredTimingValue(gpu_render_compute_util_percent_, - gpu_render_compute_util_variation_percent_); - frames_[i].power_telemetry.gpu_media_utilization = - GetAlteredTimingValue(gpu_media_util_percent_, - gpu_media_util_variation_percent_); - frames_[i].power_telemetry.vram_power_w = - GetAlteredTimingValue(vram_power_w_, vram_power_variation_w_); - frames_[i].power_telemetry.vram_voltage_v = - GetAlteredTimingValue(vram_voltage_v_, vram_voltage_variation_v_); - frames_[i].power_telemetry.vram_frequency_mhz = GetAlteredTimingValue( - vram_frequency_mhz_, vram_frequency_variation_mhz_); - frames_[i].power_telemetry.vram_effective_frequency_gbps = - GetAlteredTimingValue(vram_effective_frequency_gbps_, - vram_effective_frequency_variation_gbps_); - frames_[i].power_telemetry.vram_temperature_c = - GetAlteredTimingValue(vram_temp_c_, vram_temp_variation_c_); - frames_[i].power_telemetry.gpu_mem_total_size_b = GetAlteredTimingValue( - gpu_mem_total_size_b_, gpu_mem_total_size_variation_b_); - frames_[i].power_telemetry.gpu_mem_used_b = - GetAlteredTimingValue(gpu_mem_used_b_, gpu_mem_used_variation_b_); - frames_[i].power_telemetry.gpu_mem_max_bandwidth_bps = - GetAlteredTimingValue(gpu_mem_max_bw_gbps_, - gpu_mem_max_bw_variation_gbps_); - frames_[i].power_telemetry.gpu_mem_read_bandwidth_bps = - (double)GetAlteredTimingValue(gpu_mem_read_bw_bps_, - gpu_mem_read_bw_variation_bps_); - frames_[i].power_telemetry.gpu_mem_write_bandwidth_bps = - (double)GetAlteredTimingValue(gpu_mem_write_bw_bps_, - gpu_mem_write_bw_variation_bps_); - frames_[i].power_telemetry.fan_speed_rpm[0] = GetAlteredTimingValue( - gpu_fan_speed_rpm_, gpu_fan_speed_rpm_variation_rpm_); - frames_[i].power_telemetry.gpu_power_limited = - IsLimited(gpu_power_limited_percent_); - frames_[i].power_telemetry.gpu_temperature_limited = - IsLimited(gpu_util_limited_percent_); - frames_[i].power_telemetry.gpu_current_limited = - IsLimited(gpu_current_limited_percent_); - frames_[i].power_telemetry.gpu_voltage_limited = - IsLimited(gpu_voltage_limited_percent_); - frames_[i].power_telemetry.gpu_utilization_limited = - IsLimited(gpu_util_limited_percent_); - frames_[i].power_telemetry.vram_power_limited = - IsLimited(vram_power_limited_percent_); - frames_[i].power_telemetry.vram_temperature_limited = - IsLimited(vram_util_limited_percent_); - frames_[i].power_telemetry.vram_current_limited = - IsLimited(vram_current_limited_percent_); - frames_[i].power_telemetry.vram_voltage_limited = - IsLimited(vram_voltage_limited_percent_); - frames_[i].power_telemetry.vram_utilization_limited = - IsLimited(vram_util_limited_percent_); - } -} - -void PmFrameGenerator::GenerateCPUData() { - for (int i = 0; i < (int)frames_.size(); i++) { - frames_[i].cpu_telemetry.cpu_utilization = GetAlteredTimingValue( - cpu_util_percent_, cpu_util_variation_percent_); - frames_[i].cpu_telemetry.cpu_frequency = GetAlteredTimingValue( - cpu_frequency_mhz_, cpu_frequency_variation_mhz_); - } -} - -void PmFrameGenerator::CalcMetricStats(std::vector& data, - PM_METRIC_DOUBLE_DATA& metric, - bool valid) { - if (data.size() > 1) { - // Before sorting, grab the raw data. - size_t middle_index = data.size() / 2; - metric.raw = data[middle_index]; - std::sort(data.begin(), data.end()); - metric.low = data[0]; - metric.high = data[data.size() - 1]; - - int window_size = (int)data.size(); - auto sum = std::accumulate(data.begin(), data.end(), 0.); - if (sum != 0) { - metric.avg = sum / window_size; - } - - metric.percentile_90 = GetPercentile(data, 0.1); - metric.percentile_95 = GetPercentile(data, 0.05); - metric.percentile_99 = GetPercentile(data, 0.01); - } else if (data.size() == 1) { - metric.low = data[0]; - metric.high = data[0]; - metric.avg = data[0]; - metric.percentile_90 = data[0]; - metric.percentile_95 = data[0]; - metric.percentile_99 = data[0]; - } else { - metric.low = 0.; - metric.high = 0.; - metric.avg = 0.; - metric.percentile_90 = 0.; - metric.percentile_95 = 0.; - metric.percentile_99 = 0.; - } - metric.valid = valid; - return; -} - -// Calculate percentile using linear interpolation between the closet ranks -// method -double PmFrameGenerator::GetPercentile(std::vector& data, - double percentile) { - double integral_part_as_double; - double fractpart = - modf(((percentile * (static_cast(data.size() - 1))) + 1), - &integral_part_as_double); - - // Subtract off one from the integral_part as we are zero based and the - // calculation above is based one based - uint32_t integral_part = static_cast(integral_part_as_double) - 1; - uint32_t next_idx = integral_part + 1; - // Before we access the vector data ensure that our calculated index values - // are not out of range - if (integral_part < data.size() || next_idx < data.size()) { - return data[integral_part] + - (fractpart * (data[next_idx] - data[integral_part])); - } else { - return 0.0f; - } -} - -bool PmFrameGenerator::CalculateFpsMetrics( - uint32_t start_frame, double window_size_in_ms, - std::vector& fps_metrics) { - std::unordered_map swap_chain_data; - if (start_frame > pmft_frames_.size()) { - return false; - } - - // Get the qpc for the start frame - uint64_t start_frame_qpc = pmft_frames_[start_frame].qpc_time; - - // Calculate number of ticks based on the passed in window size - uint64_t window_size_in_ticks = - SecondsDeltaToQpc(window_size_in_ms / 1000., qpc_frequency_); - uint64_t calculated_end_frame_qpc = start_frame_qpc - window_size_in_ticks; - - for (uint32_t current_frame_number = start_frame; current_frame_number > 0; - current_frame_number--) { - auto result = swap_chain_data.emplace( - pmft_frames_[current_frame_number].swap_chain, fps_swap_chain_data()); - auto swap_chain = &result.first->second; - if (result.second) { - swap_chain->num_presents = 1; - // TODO Need to define sync interval in pmft structure - // swap_chain->sync_interval - swap_chain->present_mode = - pmft_frames_[current_frame_number].present_mode; - swap_chain->gpu_sum_ms.push_back( - pmft_frames_[current_frame_number].ms_gpu_active); - swap_chain->time_in_s = 0.; - swap_chain->cpu_n_time = - pmft_frames_[current_frame_number].time_in_seconds; - swap_chain->cpu_0_time = swap_chain->cpu_n_time; - if (pmft_frames_[current_frame_number].dropped == false) { - swap_chain->mLastDisplayedScreenTime = - pmft_frames_[current_frame_number].time_in_seconds + - (pmft_frames_[current_frame_number].ms_until_displayed / 1000.); - swap_chain->display_0_screen_time = swap_chain->mLastDisplayedScreenTime; - swap_chain->dropped.push_back(0); - } else { - swap_chain->dropped.push_back(1); - } - } else { - if (pmft_frames_[current_frame_number].qpc_time > - calculated_end_frame_qpc) { - swap_chain->num_presents++; - swap_chain->present_mode = - pmft_frames_[current_frame_number].present_mode; - swap_chain->gpu_sum_ms.push_back( - pmft_frames_[current_frame_number].ms_gpu_active); - swap_chain->time_in_s = - swap_chain->cpu_0_time - - pmft_frames_[current_frame_number].time_in_seconds; - swap_chain->cpu_0_time = - pmft_frames_[current_frame_number].time_in_seconds; - if (swap_chain->time_in_s != 0.) { - // Convert to ms and store frametime - swap_chain->frame_times_ms.push_back(swap_chain->time_in_s * 1000.); - swap_chain->presented_fps.push_back(1. / swap_chain->time_in_s); - } - if (pmft_frames_[current_frame_number].dropped == false) { - auto current_display_screen_time_s = - pmft_frames_[current_frame_number].time_in_seconds + - (pmft_frames_[current_frame_number].ms_until_displayed / 1000.); - if (swap_chain->display_0_screen_time != 0.) { - swap_chain->display_fps.push_back( - 1. / (swap_chain->display_0_screen_time - - current_display_screen_time_s)); - } else { - swap_chain->mLastDisplayedScreenTime = current_display_screen_time_s; - } - swap_chain->display_0_screen_time = current_display_screen_time_s; - swap_chain->dropped.push_back(0); - } else { - swap_chain->dropped.push_back(1); - } - } else { - break; - } - } - } - - fps_metrics.clear(); - PM_FPS_DATA temp_fps_data{}; - for (auto pair : swap_chain_data) { - temp_fps_data.swap_chain = pair.first; - auto swap_chain = pair.second; - CalcMetricStats(swap_chain.display_fps, temp_fps_data.displayed_fps); - CalcMetricStats(swap_chain.presented_fps, temp_fps_data.presented_fps); - CalcMetricStats(swap_chain.frame_times_ms, temp_fps_data.frame_time_ms); - CalcMetricStats(swap_chain.gpu_sum_ms, temp_fps_data.gpu_busy); - // Overwrite the average both the display and cpu average fps. - auto avg_fps = - swap_chain.mLastDisplayedScreenTime - swap_chain.display_0_screen_time; - avg_fps /= swap_chain.display_fps.size(); - avg_fps = 1. / avg_fps; - temp_fps_data.displayed_fps.avg = avg_fps; - avg_fps = swap_chain.cpu_n_time - swap_chain.cpu_0_time; - avg_fps /= swap_chain.presented_fps.size(); - avg_fps = 1. / avg_fps; - temp_fps_data.presented_fps.avg = avg_fps; - temp_fps_data.present_mode = swap_chain.present_mode; - temp_fps_data.num_presents = swap_chain.num_presents; - temp_fps_data.sync_interval = swap_chain.sync_interval; - CalcMetricStats(swap_chain.dropped, temp_fps_data.percent_dropped_frames); - temp_fps_data.percent_dropped_frames.avg *= 100.; - - fps_metrics.emplace_back(temp_fps_data); - temp_fps_data = {}; - } - return true; -} - -void PmFrameGenerator::CalculateLatencyMetrics( - uint32_t start_frame, double window_size_in_ms, - std::vector& latency_metrics) { - - std::unordered_map swap_chain_data; - - if (start_frame > pmft_frames_.size()) { - return; - } - - // Get the qpc for the start frame - uint64_t start_frame_qpc = pmft_frames_[start_frame].qpc_time; - - // Calculate number of ticks based on the passed in window size - uint64_t window_size_in_ticks = - SecondsDeltaToQpc(window_size_in_ms / 1000., qpc_frequency_); - uint64_t calculated_end_frame_qpc = start_frame_qpc - window_size_in_ticks; - - for (uint32_t current_frame_number = start_frame; current_frame_number > 0; - current_frame_number--) { - auto result = - swap_chain_data.emplace(pmft_frames_[current_frame_number].swap_chain, - latency_swap_chain_data()); - auto swap_chain = &result.first->second; - if (result.second) { - swap_chain->render_latency_ms.clear(); - swap_chain->display_latency_ms.clear(); - } else { - if (pmft_frames_[current_frame_number].qpc_time > - calculated_end_frame_qpc) { - if (pmft_frames_[current_frame_number].ms_between_display_change != - 0.) { - swap_chain->render_latency_ms.push_back( - pmft_frames_[current_frame_number].ms_until_displayed); - swap_chain->display_latency_ms.push_back( - pmft_frames_[current_frame_number].ms_until_displayed - - pmft_frames_[current_frame_number].ms_until_render_complete); - } - } else { - break; - } - } - } - - latency_metrics.clear(); - PM_GFX_LATENCY_DATA temp_latency_data{}; - for (auto pair : swap_chain_data) { - temp_latency_data.swap_chain = pair.first; - auto swap_chain = pair.second; - CalcMetricStats(swap_chain.render_latency_ms, - temp_latency_data.render_latency_ms); - CalcMetricStats(swap_chain.display_latency_ms, - temp_latency_data.display_latency_ms); - latency_metrics.emplace_back(temp_latency_data); - temp_latency_data = {}; - } - return; -} - -void PmFrameGenerator::CalculateGpuMetrics( - uint32_t start_frame, double window_size_in_ms, - GpuTelemetryBitset gpu_telemetry_cap_bits, PM_GPU_DATA& gpu_metrics) { - if (start_frame > pmft_frames_.size()) { - return; - } - - // Get the qpc for the start frame - uint64_t start_frame_qpc = pmft_frames_[start_frame].qpc_time; - - bool gpu_mem_util_enabled = (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_size)] && - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_used)]); - - // Calculate number of ticks based on the passed in window size - uint64_t window_size_in_ticks = - SecondsDeltaToQpc(window_size_in_ms / 1000., qpc_frequency_); - uint64_t calculated_end_frame_qpc = start_frame_qpc - window_size_in_ticks; - gpu_data calculated_gpu_metrics{}; - for (uint32_t current_frame_number = start_frame; current_frame_number > 0; - current_frame_number--) { - if (pmft_frames_[current_frame_number].qpc_time > - calculated_end_frame_qpc) { - if (gpu_telemetry_cap_bits[static_cast(GpuTelemetryCapBits::gpu_power)]) { - calculated_gpu_metrics.gpu_power_w.push_back( - frames_[current_frame_number].power_telemetry.gpu_power_w); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_sustained_power_limit)]) { - calculated_gpu_metrics.gpu_sustained_power_limit_w.push_back( - frames_[current_frame_number] - .power_telemetry.gpu_sustained_power_limit_w); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_voltage)]) { - calculated_gpu_metrics.gpu_voltage_v.push_back( - frames_[current_frame_number].power_telemetry.gpu_voltage_v); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_frequency)]) { - calculated_gpu_metrics.gpu_frequency_mhz.push_back( - frames_[current_frame_number].power_telemetry.gpu_frequency_mhz); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_temperature)]) { - calculated_gpu_metrics.gpu_temp_c.push_back( - frames_[current_frame_number].power_telemetry.gpu_temperature_c); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_utilization)]) { - calculated_gpu_metrics.gpu_util_percent.push_back( - frames_[current_frame_number].power_telemetry.gpu_utilization); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_render_compute_utilization)]) { - calculated_gpu_metrics.gpu_render_compute_util_percent.push_back( - frames_[current_frame_number] - .power_telemetry.gpu_render_compute_utilization); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_media_utilization)]) { - calculated_gpu_metrics.gpu_media_util_percent.push_back( - frames_[current_frame_number] - .power_telemetry.gpu_media_utilization); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_power)]) { - calculated_gpu_metrics.vram_power_w.push_back( - frames_[current_frame_number].power_telemetry.vram_power_w); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_voltage)]) { - calculated_gpu_metrics.vram_voltage_v.push_back( - frames_[current_frame_number].power_telemetry.vram_voltage_v); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_frequency)]) { - calculated_gpu_metrics.vram_frequency_mhz.push_back( - frames_[current_frame_number].power_telemetry.vram_frequency_mhz); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_effective_frequency)]) { - calculated_gpu_metrics.vram_effective_frequency_gbps.push_back( - frames_[current_frame_number] - .power_telemetry.vram_effective_frequency_gbps); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_temperature)]) { - calculated_gpu_metrics.vram_temp_c.push_back( - frames_[current_frame_number].power_telemetry.vram_temperature_c); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_size)]) { - calculated_gpu_metrics.gpu_mem_total_size_b.push_back( - (double)frames_[current_frame_number] - .power_telemetry.gpu_mem_total_size_b); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_used)]) { - calculated_gpu_metrics.gpu_mem_used_b.push_back( - (double)frames_[current_frame_number] - .power_telemetry.gpu_mem_used_b); - } - // gpu mem utilization is calculated from the total gpu memory - // and the used gpu memory - if (gpu_mem_util_enabled) { - if (frames_[current_frame_number] - .power_telemetry.gpu_mem_total_size_b != 0.) { - calculated_gpu_metrics.gpu_mem_util_percent.push_back( - 100. * - double(frames_[current_frame_number] - .power_telemetry.gpu_mem_used_b) / - frames_[current_frame_number] - .power_telemetry.gpu_mem_total_size_b); - } else { - calculated_gpu_metrics.gpu_mem_util_percent.push_back(0.); - } - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_max_bandwidth)]) { - calculated_gpu_metrics.gpu_mem_max_bw_gbps.push_back( - (double)frames_[current_frame_number] - .power_telemetry.gpu_mem_max_bandwidth_bps); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_read_bandwidth)]) { - calculated_gpu_metrics.gpu_mem_read_bw_bps.push_back( - frames_[current_frame_number] - .power_telemetry.gpu_mem_read_bandwidth_bps); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_write_bandwidth)]) { - calculated_gpu_metrics.gpu_mem_write_bw_bps.push_back( - frames_[current_frame_number] - .power_telemetry.gpu_mem_write_bandwidth_bps); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::fan_speed_0)]) { - calculated_gpu_metrics.gpu_fan_speed_rpm.push_back( - frames_[current_frame_number].power_telemetry.fan_speed_rpm[0]); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_power_limited)]) { - calculated_gpu_metrics.gpu_power_limited_percent.push_back( - frames_[current_frame_number].power_telemetry.gpu_power_limited); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_temperature_limited)]) { - calculated_gpu_metrics.gpu_temp_limited_percent.push_back( - frames_[current_frame_number] - .power_telemetry.gpu_temperature_limited); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_current_limited)]) { - calculated_gpu_metrics.gpu_current_limited_percent.push_back( - frames_[current_frame_number].power_telemetry.gpu_current_limited); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_voltage_limited)]) { - calculated_gpu_metrics.gpu_voltage_limited_percent.push_back( - frames_[current_frame_number].power_telemetry.gpu_voltage_limited); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_utilization_limited)]) { - calculated_gpu_metrics.gpu_util_limited_percent.push_back( - frames_[current_frame_number] - .power_telemetry.gpu_utilization_limited); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_power_limited)]) { - calculated_gpu_metrics.vram_power_limited_percent.push_back( - frames_[current_frame_number].power_telemetry.vram_power_limited); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_temperature_limited)]) { - calculated_gpu_metrics.vram_temp_limited_percent.push_back( - frames_[current_frame_number] - .power_telemetry.vram_temperature_limited); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_current_limited)]) { - calculated_gpu_metrics.vram_current_limited_percent.push_back( - frames_[current_frame_number].power_telemetry.vram_current_limited); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_voltage_limited)]) { - calculated_gpu_metrics.vram_voltage_limited_percent.push_back( - frames_[current_frame_number].power_telemetry.vram_voltage_limited); - } - if (gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_utilization_limited)]) { - calculated_gpu_metrics.vram_util_limited_percent.push_back( - frames_[current_frame_number] - .power_telemetry.vram_utilization_limited); - } - - } else { - break; - } - } - - CalcMetricStats(calculated_gpu_metrics.gpu_power_w, gpu_metrics.gpu_power_w, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_power)]); - CalcMetricStats(calculated_gpu_metrics.gpu_sustained_power_limit_w, - gpu_metrics.gpu_sustained_power_limit_w, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_sustained_power_limit)]); - CalcMetricStats(calculated_gpu_metrics.gpu_voltage_v, - gpu_metrics.gpu_voltage_v, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_voltage)]); - CalcMetricStats(calculated_gpu_metrics.gpu_frequency_mhz, - gpu_metrics.gpu_frequency_mhz, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_frequency)]); - CalcMetricStats(calculated_gpu_metrics.gpu_temp_c, - gpu_metrics.gpu_temperature_c, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_temperature)]); - CalcMetricStats(calculated_gpu_metrics.gpu_fan_speed_rpm, - gpu_metrics.gpu_fan_speed_rpm[0], - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::fan_speed_0)]); - CalcMetricStats(calculated_gpu_metrics.gpu_util_percent, - gpu_metrics.gpu_utilization, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_utilization)]); - CalcMetricStats(calculated_gpu_metrics.gpu_render_compute_util_percent, - gpu_metrics.gpu_render_compute_utilization, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_render_compute_utilization)]); - CalcMetricStats(calculated_gpu_metrics.gpu_media_util_percent, - gpu_metrics.gpu_media_utilization, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_media_utilization)]); - CalcMetricStats(calculated_gpu_metrics.vram_power_w, gpu_metrics.vram_power_w, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_power)]); - CalcMetricStats(calculated_gpu_metrics.vram_voltage_v, - gpu_metrics.vram_voltage_v, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_voltage)]); - CalcMetricStats(calculated_gpu_metrics.vram_frequency_mhz, - gpu_metrics.vram_frequency_mhz, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_frequency)]); - CalcMetricStats(calculated_gpu_metrics.vram_effective_frequency_gbps, - gpu_metrics.vram_effective_frequency_gbps, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_effective_frequency)]); - CalcMetricStats(calculated_gpu_metrics.vram_temp_c, - gpu_metrics.vram_temperature_c, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_temperature)]); - CalcMetricStats(calculated_gpu_metrics.gpu_mem_total_size_b, - gpu_metrics.gpu_mem_total_size_b, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_size)]); - CalcMetricStats(calculated_gpu_metrics.gpu_mem_used_b, - gpu_metrics.gpu_mem_used_b, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_used)]); - CalcMetricStats(calculated_gpu_metrics.gpu_mem_util_percent, - gpu_metrics.gpu_mem_utilization, gpu_mem_util_enabled); - CalcMetricStats(calculated_gpu_metrics.gpu_mem_max_bw_gbps, - gpu_metrics.gpu_mem_max_bandwidth_bps, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_max_bandwidth)]); - CalcMetricStats(calculated_gpu_metrics.gpu_mem_read_bw_bps, - gpu_metrics.gpu_mem_read_bandwidth_bps, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_max_bandwidth)]); - CalcMetricStats(calculated_gpu_metrics.gpu_mem_write_bw_bps, - gpu_metrics.gpu_mem_write_bandwidth_bps, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_mem_write_bandwidth)]); - CalcMetricStats(calculated_gpu_metrics.gpu_power_limited_percent, - gpu_metrics.gpu_power_limited, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_power_limited)]); - CalcMetricStats(calculated_gpu_metrics.gpu_temp_limited_percent, - gpu_metrics.gpu_temperature_limited, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_temperature_limited)]); - CalcMetricStats(calculated_gpu_metrics.gpu_current_limited_percent, - gpu_metrics.gpu_current_limited, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_current_limited)]); - CalcMetricStats(calculated_gpu_metrics.gpu_voltage_limited_percent, - gpu_metrics.gpu_voltage_limited, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_voltage_limited)]); - CalcMetricStats(calculated_gpu_metrics.gpu_util_limited_percent, - gpu_metrics.gpu_utilization_limited, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::gpu_utilization_limited)]); - CalcMetricStats(calculated_gpu_metrics.vram_power_limited_percent, - gpu_metrics.vram_power_limited, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_power_limited)]); - CalcMetricStats(calculated_gpu_metrics.vram_temp_limited_percent, - gpu_metrics.vram_temperature_limited, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_temperature_limited)]); - CalcMetricStats(calculated_gpu_metrics.vram_current_limited_percent, - gpu_metrics.vram_current_limited, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_current_limited)]); - CalcMetricStats(calculated_gpu_metrics.vram_voltage_limited_percent, - gpu_metrics.vram_voltage_limited, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_voltage_limited)]); - CalcMetricStats(calculated_gpu_metrics.vram_util_limited_percent, - gpu_metrics.vram_utilization_limited, - gpu_telemetry_cap_bits[static_cast( - GpuTelemetryCapBits::vram_utilization_limited)]); -} - -void PmFrameGenerator::CalculateCpuMetrics( - uint32_t start_frame, double window_size_in_ms, - CpuTelemetryBitset cpu_telemetry_cap_bits, - PM_CPU_DATA& cpu_metrics) { - if (start_frame > pmft_frames_.size()) { - return; - } - - // Get the qpc for the start frame - uint64_t start_frame_qpc = pmft_frames_[start_frame].qpc_time; - - // Calculate number of ticks based on the passed in window size - uint64_t window_size_in_ticks = - SecondsDeltaToQpc(window_size_in_ms / 1000., qpc_frequency_); - uint64_t calculated_end_frame_qpc = start_frame_qpc - window_size_in_ticks; - cpu_data calculated_cpu_metrics{}; - for (uint32_t current_frame_number = start_frame; current_frame_number > 0; - current_frame_number--) { - if (pmft_frames_[current_frame_number].qpc_time > - calculated_end_frame_qpc) { - if (cpu_telemetry_cap_bits[static_cast( - CpuTelemetryCapBits::cpu_utilization)]) { - calculated_cpu_metrics.cpu_util_percent.push_back( - frames_[current_frame_number].cpu_telemetry.cpu_utilization); - } - if (cpu_telemetry_cap_bits[static_cast( - CpuTelemetryCapBits::cpu_frequency)]) { - calculated_cpu_metrics.cpu_frequency_mhz.push_back( - frames_[current_frame_number].cpu_telemetry.cpu_frequency); - } - } - } - - CalcMetricStats(calculated_cpu_metrics.cpu_util_percent, - cpu_metrics.cpu_utilization, - cpu_telemetry_cap_bits[static_cast( - CpuTelemetryCapBits::cpu_utilization)]); - CalcMetricStats(calculated_cpu_metrics.cpu_frequency_mhz, - cpu_metrics.cpu_frequency, - cpu_telemetry_cap_bits[static_cast( - CpuTelemetryCapBits::cpu_frequency)]); -} \ No newline at end of file diff --git a/IntelPresentMon/ULT/PmFrameGenerator.h b/IntelPresentMon/ULT/PmFrameGenerator.h deleted file mode 100644 index 7bb5925f..00000000 --- a/IntelPresentMon/ULT/PmFrameGenerator.h +++ /dev/null @@ -1,371 +0,0 @@ -#pragma once - -#include -#include - -#include "../PresentMonUtils/MemBuffer.h" -#include "../PresentMonUtils/PresentDataUtils.h" -#include "../PresentMonUtils/PresentMonNamedPipe.h" -#include "../PresentMonUtils/QPCUtils.h" - -struct PMFrameTimingInformation { - uint64_t swap_chain = 0; - bool dropped = false; - double time_in_seconds = 0.; - double ms_between_presents = 0.; - double ms_in_present_api = 0.; - double ms_until_render_start = 0.; - double ms_until_render_complete = 0.; - double ms_until_displayed = 0.; - double ms_between_display_change = 0.; - uint64_t qpc_time = 0; - PM_PRESENT_MODE present_mode = - PM_PRESENT_MODE::PM_PRESENT_MODE_HARDWARE_INDEPENDENT_FLIP; - double ms_until_input = 0.; - double ms_gpu_active = 0.; - double ms_gpu_video_active = 0.; -}; - -template -concept Numeric = (std::is_integral_v || - std::is_floating_point_v)&&!std::same_as; - -class PmFrameGenerator { - public: - struct FrameParams { - std::optional app_name; - std::optional process_id; - std::optional runtime; - std::optional sync_interval; - std::optional present_flags; - std::optional percent_dropped; - std::optional in_present_api_ms; - std::optional in_present_api_variation_ms; - std::optional between_presents_ms; - std::optional between_presents_variation_ms; - std::optional percent_tearing; - std::optional present_mode; - std::optional until_render_complete_ms; - std::optional until_render_complete_variation_ms; - std::optional until_displayed_ms; - std::optional until_displayed_variation_ms; - std::optional until_display_change_ms; - std::optional until_display_change_variation_ms; - std::optional until_render_start_ms; - std::optional until_render_start_variation_ms; - std::optional qpc_time; - - // Internal frame data fields; Remove for public build - std::optional since_input_ms; - std::optional since_input_variation_ms; - std::optional gpu_active_ms; - std::optional gpu_active_variation_ms; - std::optional gpu_video_active_ms; - std::optional gpu_video_active_variation_ms; - - std::optional gpu_power_w; - std::optional gpu_power_variation_w; - std::optional gpu_sustained_power_limit_w; - std::optional gpu_sustained_power_limit_variation_w; - std::optional gpu_voltage_v; - std::optional gpu_voltage_variation_v; - std::optional gpu_frequency_mhz; - std::optional gpu_frequency_variation_mhz; - std::optional gpu_temp_c; - std::optional gpu_temp_variation_c; - std::optional gpu_util_percent; - std::optional gpu_util_variation_percent; - std::optional gpu_render_compute_util_percent; - std::optional gpu_render_compute_util_variation_percent; - std::optional gpu_media_util_percent; - std::optional gpu_media_util_variation_percent; - std::optional vram_power_w; - std::optional vram_power_variation_w; - std::optional vram_voltage_v; - std::optional vram_voltage_variation_v; - std::optional vram_frequency_mhz; - std::optional vram_frequency_variation_mhz; - std::optional vram_effective_frequency_gbps; - std::optional vram_effective_frequency_variation_gbps; - std::optional vram_read_bw_gbps; - std::optional vram_read_bw_variation_gbps; - std::optional vram_write_bw_gbps; - std::optional vram_write_bw_variation_gbps; - std::optional vram_temp_c; - std::optional vram_temp_variation_c; - std::optional gpu_mem_total_size_b; - std::optional gpu_mem_total_size_variation_b; - std::optional gpu_mem_used_b; - std::optional gpu_mem_used_variation_b; - std::optional gpu_mem_max_bw_gbps; - std::optional gpu_mem_max_bw_variation_gbps; - std::optional gpu_mem_read_bw_bps; - std::optional gpu_mem_read_bw_variation_bps; - std::optional gpu_mem_write_bw_bps; - std::optional gpu_mem_write_bw_variation_bps; - std::optional gpu_fan_speed_rpm; - std::optional gpu_fan_speed_rpm_variation_rpm; - std::optional gpu_power_limited_percent; - std::optional gpu_temp_limited_percent; - std::optional gpu_current_limited_percent; - std::optional gpu_voltage_limited_percent; - std::optional gpu_util_limited_percent; - std::optional vram_power_limited_percent; - std::optional vram_temp_limited_percent; - std::optional vram_current_limited_percent; - std::optional vram_voltage_limited_percent; - std::optional vram_util_limited_percent; - std::optional cpu_util_percent; - std::optional cpu_util_variation_percent; - std::optional cpu_frequency_mhz; - std::optional cpu_frequency_variation_mhz; - }; - - PmFrameGenerator(const FrameParams& frame_params); - - void GenerateFrames(int num_frames); - size_t GetNumFrames(); - PmNsmFrameData GetFrameData(int frame_num); - PM_FRAME_DATA GetPmFrameData(int frame_num, - GpuTelemetryBitset gpu_telemetry_cap_bits, - CpuTelemetryBitset cpu_telemetry_cap_bits); - - // Calculate the Fps metrics using the PresentMon frame data and not - // the raw qpc data. - bool CalculateFpsMetrics(uint32_t start_frame, double window_size_in_ms, - std::vector& fps_metrics); - void CalculateLatencyMetrics( - uint32_t start_frame, double window_size_in_ms, - std::vector& latency_metrics); - void CalculateGpuMetrics(uint32_t start_frame, double window_size_in_ms, - GpuTelemetryBitset gpu_telemetry_cap_bits, - PM_GPU_DATA& gpu_metrics); - void CalculateCpuMetrics(uint32_t start_frame, double window_size_in_ms, - CpuTelemetryBitset cpu_telemetry_cap_bits, - PM_CPU_DATA& cpu_metrics); - - - // Simple function to set the number of swap chains to be created - // when generating // frame data. If set AFTER frame data generation it - // will have no affect on current data. Frames must be re-generated after - // setting the number of swapchains. When more than one swap chain is - // specified the swap chain will be divided as equal as possible among - // the frames. - void SetNumberSwapChains(uint32_t num_swap_chains) { - swap_chains_.clear(); - swap_chains_.resize(num_swap_chains); - for (uint32_t i = 0; i < swap_chains_.size(); i++) { - swap_chains_[i] = (uint64_t)i; - } - } - void SetFps(double fps) { between_presents_ms_ = 1000. / fps; }; - size_t GetNumberOfSwapChains() { return swap_chains_.size(); } - double GetWindowSize() { - return QpcDeltaToMs( - (frames_[frames_.size() - 1].present_event.PresentStartTime - - frames_[0].present_event.PresentStartTime), - qpc_frequency_); - } - uint32_t GetSeed() { return uniform_random_gen_.GetSeed(); } - - private: - class UniformRandomGenerator { - public: - UniformRandomGenerator(uint32_t seed = std::random_device{}()) - : seed_{seed}, engine_{seed} {} - template - T Generate(T low, T high) { - if constexpr (std::is_integral_v) { - return std::uniform_int_distribution{low, high}(engine_); - } else { - return std::uniform_real_distribution{low, high}(engine_); - } - } - uint32_t GetSeed() const { return seed_; } - - private: - std::mt19937 engine_; - uint32_t seed_; - }; // note: no need to provide virtual dtor; this is not a polymorphic type - // (no virtual functions) - - template - T GetAlteredTimingValue(T metric_in, T metric_variation_value) { - if constexpr (std::is_unsigned::value) { - return metric_in + - uniform_random_gen_.Generate((T)0, metric_variation_value); - } else { - return metric_in + - uniform_random_gen_.Generate(-1. * metric_variation_value, - metric_variation_value); - } - } - - struct fps_swap_chain_data { - std::vector presented_fps; - std::vector display_fps; - std::vector frame_times_ms; - std::vector gpu_sum_ms; - std::vector dropped; - double mLastDisplayedScreenTime = 0.; - double display_0_screen_time = 0.; - double cpu_n_time = 0.; - double cpu_0_time = 0.; - double time_in_s = 0.; - uint32_t num_presents = 0; - int32_t sync_interval = 0; - PM_PRESENT_MODE present_mode = PM_PRESENT_MODE_UNKNOWN; - }; - - struct latency_swap_chain_data { - std::vector render_latency_ms; - std::vector display_latency_ms; - }; - - struct gpu_data { - std::vector gpu_power_w; - std::vector gpu_sustained_power_limit_w; - std::vector gpu_voltage_v; - std::vector gpu_frequency_mhz; - std::vector gpu_temp_c; - std::vector gpu_util_percent; - std::vector gpu_render_compute_util_percent; - std::vector gpu_media_util_percent; - std::vector vram_power_w; - std::vector vram_voltage_v; - std::vector vram_frequency_mhz; - std::vector vram_effective_frequency_gbps; - std::vector vram_read_bw_gbps; - std::vector vram_write_bw_gbps; - std::vector vram_temp_c; - std::vector gpu_mem_total_size_b; - std::vector gpu_mem_used_b; - std::vector gpu_mem_util_percent; - std::vector gpu_mem_max_bw_gbps; - std::vector gpu_mem_read_bw_bps; - std::vector gpu_mem_write_bw_bps; - std::vector gpu_fan_speed_rpm; - std::vector gpu_power_limited_percent; - std::vector gpu_temp_limited_percent; - std::vector gpu_current_limited_percent; - std::vector gpu_voltage_limited_percent; - std::vector gpu_util_limited_percent; - std::vector vram_power_limited_percent; - std::vector vram_temp_limited_percent; - std::vector vram_current_limited_percent; - std::vector vram_voltage_limited_percent; - std::vector vram_util_limited_percent; - }; - - struct cpu_data { - std::vector cpu_util_percent; - std::vector cpu_frequency_mhz; - }; - - void GeneratePresentData(); - void GenerateGPUData(); - void GenerateCPUData(); - - void CalcMetricStats(std::vector& data, - PM_METRIC_DOUBLE_DATA& metric, - bool valid=true); - - // Calculate percentile using linear interpolation between the closet ranks - // method - double GetPercentile(std::vector& data, double percentile); - bool IsLimited(const double& limited_percentage) { - if (uniform_random_gen_.Generate(0., 100.) < limited_percentage) { - return true; - } else { - return false; - } - } - - std::string app_name_; - std::vector swap_chains_; - uint32_t process_id_; - Runtime runtime_; - int32_t sync_interval_; - uint32_t present_flags_; - double percent_dropped_; - double in_present_api_ms_; - double in_present_api_variation_ms_; - double between_presents_ms_; - double between_presents_variation_ms_; - double percent_tearing_; - PresentMode present_mode_; - double until_render_complete_ms_; - double until_render_complete_variation_ms_; - double until_displayed_ms_; - double until_displayed_variation_ms_; - double until_display_change_ms_; - double until_display_change_variation_ms_; - double until_render_start_ms_; - double until_render_start_variation_ms_; - double qpc_time_; - double since_input_ms_; - double since_input_variation_ms_; - double gpu_active_ms_; - double gpu_active_variation_ms_; - double gpu_video_active_ms_; - double gpu_video_active_variation_ms_; - - double gpu_power_w_; - double gpu_power_variation_w_; - double gpu_sustained_power_limit_w_; - double gpu_sustained_power_limit_variation_w_; - double gpu_voltage_v_; - double gpu_voltage_variation_v_; - double gpu_frequency_mhz_; - double gpu_frequency_variation_mhz_; - double gpu_temp_c_; - double gpu_temp_variation_c_; - double gpu_util_percent_; - double gpu_util_variation_percent_; - double gpu_render_compute_util_percent_; - double gpu_render_compute_util_variation_percent_; - double gpu_media_util_percent_; - double gpu_media_util_variation_percent_; - double vram_power_w_; - double vram_power_variation_w_; - double vram_voltage_v_; - double vram_voltage_variation_v_; - double vram_frequency_mhz_; - double vram_frequency_variation_mhz_; - double vram_effective_frequency_gbps_; - double vram_effective_frequency_variation_gbps_; - double vram_temp_c_; - double vram_temp_variation_c_; - uint64_t gpu_mem_total_size_b_; - uint64_t gpu_mem_total_size_variation_b_; - uint64_t gpu_mem_used_b_; - uint64_t gpu_mem_used_variation_b_; - uint64_t gpu_mem_max_bw_gbps_; - uint64_t gpu_mem_max_bw_variation_gbps_; - uint64_t gpu_mem_read_bw_bps_; - uint64_t gpu_mem_read_bw_variation_bps_; - uint64_t gpu_mem_write_bw_bps_; - uint64_t gpu_mem_write_bw_variation_bps_; - double gpu_fan_speed_rpm_; - double gpu_fan_speed_rpm_variation_rpm_; - double gpu_power_limited_percent_; - double gpu_temp_limited_percent_; - double gpu_current_limited_percent_; - double gpu_voltage_limited_percent_; - double gpu_util_limited_percent_; - double vram_power_limited_percent_; - double vram_temp_limited_percent_; - double vram_current_limited_percent_; - double vram_voltage_limited_percent_; - double vram_util_limited_percent_; - double cpu_util_percent_; - double cpu_util_variation_percent_; - double cpu_frequency_mhz_; - double cpu_frequency_variation_mhz_; - LARGE_INTEGER qpc_frequency_; - LARGE_INTEGER start_qpc_; - - std::vector frames_; - std::vector pmft_frames_; - UniformRandomGenerator uniform_random_gen_; -}; \ No newline at end of file diff --git a/IntelPresentMon/ULT/StreamerTests.cpp b/IntelPresentMon/ULT/StreamerTests.cpp deleted file mode 100644 index 30979acc..00000000 --- a/IntelPresentMon/ULT/StreamerTests.cpp +++ /dev/null @@ -1,306 +0,0 @@ -#include "gtest/gtest.h" -#include "..\Streamer\Streamer.h" -#include "..\Streamer\StreamClient.h" -#include "utils.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../CommonUtilities/log/GlogShim.h" - -static const string kClientEXE = "SampleStreamerClient.exe"; -static const string kMapFileName = "Global\\MyFileMappingObject"; -static const string kSampleTestFile = "C:\\temp\\test.txt"; -static const uint32_t kServerUpdateIntervalInMs = 10; -static const uint32_t kClientReadIntervalsInMs = 20; -static const string kSamplePresentMonFile = "testdata\\hitman3_presentmon_2.log"; -static const string kSamplePresentMonFileSmall = "testdata\\hitman3_presentmon_1.log"; -static const uint32_t kLoopCount = 20; -static const uint32_t kClientLoopCount = 20; -static const string sample_test_data = "HITMAN3.exe,1166288,0x000000004474CBE0,DXGI,1,0,1,0.04376850000000,0.39320000000000,16.52640000000000,0,Composed: Flip,32.33330000000000,0.00000000000000,0.00000000000000,785981.74005040002521"; -static const uint32_t kNumFramesInBuf = 20; -static const uint64_t kNsmBufSize = 65535; -static const uint64_t kNsmBufSizeLarge = -1; - - -void ParsePresentMonCsvData(string line, PmNsmFrameData& data) { - const std::regex delimiter(","); // whitespace - std::sregex_token_iterator iter(line.begin(), line.end(), delimiter, -1); - std::sregex_token_iterator end; - - // Skipping Application - ++iter; - data.present_event.ProcessId = atoi(static_cast(*iter).c_str()); - ++iter; - data.present_event.SwapChainAddress = std::stoull(static_cast(*iter).c_str(), NULL, 16); - // Skipping Runtime - ++iter; - ++iter; - data.present_event.SyncInterval = atoi(static_cast(*iter).c_str()); - ++iter; - //PresentFlags - data.present_event.PresentFlags = atoi(static_cast(*iter).c_str()); -} - - -class StreamerULT : public ::testing::Test { -public: - void SetUp() override { - } - - void TearDown() override { - LOG(INFO) << "Tear down ..."; - } - - Streamer streamer_; - StreamClient client_; - - ~StreamerULT() { - } - - void ServerRead(string file_name); - bool reading_from_file_; -}; - - -void StreamerULT::ServerRead(string file_name) { - DWORD proc_id = GetCurrentProcessId(); - static const int kPathSize = 1024; - - string mapfile_name; - GpuTelemetryBitset gpu_telemetry_cap_bits; - CpuTelemetryBitset cpu_telemetry_cap_bits; - gpu_telemetry_cap_bits.set(); - cpu_telemetry_cap_bits.set(); - - streamer_.StartStreaming(proc_id, proc_id, mapfile_name); - EXPECT_FALSE(mapfile_name.empty()); - - std::ifstream test_read_file; - test_read_file.open(file_name, std::fstream::in); - - - if (test_read_file.is_open()) { - LOG(INFO) << "\nFile successfully opened."; - - // skip the first line - string line; - getline(test_read_file, line); - - while (getline(test_read_file, line) && reading_from_file_) { - LOG(INFO) << "\nWriting data...\n"<< line << std::endl; - PmNsmFrameData data = { 0 }; - - ParsePresentMonCsvData(line, data); - - streamer_.WriteFrameData(proc_id, &data, - gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - LOG(INFO) << "\nspin for " << kServerUpdateIntervalInMs << " ms"; - std::this_thread::sleep_for(std::chrono::milliseconds(kServerUpdateIntervalInMs)); - } - } - else { - LOG(ERROR) << "\n Failed to open " << kSamplePresentMonFile; - } - - test_read_file.close(); -} - -TEST_F(StreamerULT, StartStreaming) { - DWORD proc_id = GetCurrentProcessId(); - auto status = pmStartStream(proc_id); - EXPECT_EQ(status, PM_STATUS::PM_STATUS_SERVICE_NOT_INITIALIZED); -} - -TEST_F(StreamerULT, StopStreaming) { - DWORD proc_id = GetCurrentProcessId(); - - string mapfile_name; - streamer_.StartStreaming(proc_id, proc_id, mapfile_name); - EXPECT_FALSE(mapfile_name.empty()); - - // Stop with the same proc_id again - streamer_.StopStreaming(proc_id); - mapfile_name = streamer_.GetMapFileName(proc_id); - // NSM should no longer exist - EXPECT_TRUE(mapfile_name.empty()); -} - -TEST_F(StreamerULT, ClientOpenMappedFile) { - std::thread serverthread(&StreamerULT::ServerRead, this, kSamplePresentMonFile); - std::this_thread::sleep_for(std::chrono::milliseconds(kClientReadIntervalsInMs)); - - STARTUPINFO si; - PROCESS_INFORMATION pi; - - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - ZeroMemory(&pi, sizeof(pi)); - - DWORD proc_id = GetCurrentProcessId(); - - LOG(INFO) << "\nStreamer map file name for pid(" << proc_id << "):" << streamer_.GetMapFileName(proc_id); - - string cmdline_str = kClientEXE + " "+ streamer_.GetMapFileName(proc_id); - - LOG(INFO) << "\nCmdLine to start client process: " << cmdline_str; - - LPSTR cmdLine = const_cast(cmdline_str.c_str()); - - // Start the child process. - if (!CreateProcess(NULL, // No module name (use command line) - cmdLine, // Command line - NULL, // Process handle not inheritable - NULL, // Thread handle not inheritable - FALSE, // Set handle inheritance to FALSE - 0, // No creation flags - NULL, // Use parent's environment block - NULL, // Use parent's starting directory - &si, // Pointer to STARTUPINFO structure - &pi) // Pointer to PROCESS_INFORMATION structure - ) - { - printf("CreateProcess failed (%lu).\n", GetLastError()); - return; - } - - - // Wait until child process exits. - WaitForSingleObject(pi.hProcess, INFINITE); - - // Close process and thread handles. - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - - reading_from_file_ = false; - serverthread.join(); - - SUCCEED(); -} - - -TEST_F(StreamerULT, ServerWriteData) { - DWORD proc_id = GetCurrentProcessId(); - - string mapfile_name; - GpuTelemetryBitset gpu_telemetry_cap_bits; - CpuTelemetryBitset cpu_telemetry_cap_bits; - gpu_telemetry_cap_bits.set(); - cpu_telemetry_cap_bits.set(); - - streamer_.StartStreaming(proc_id, proc_id, mapfile_name); - EXPECT_FALSE(mapfile_name.empty()); - PmNsmFrameData data = {}; - - ParsePresentMonCsvData(sample_test_data, data); - - streamer_.WriteFrameData(proc_id, &data, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - - std::this_thread::sleep_for(std::chrono::milliseconds(kClientReadIntervalsInMs)); - - StreamClient client(std::move(mapfile_name), false); - - PmNsmFrameData* client_read_data = nullptr; - client_read_data = client.ReadLatestFrame(); - EXPECT_NE(client_read_data, nullptr); - - EXPECT_EQ(client_read_data->present_event.ProcessId, data.present_event.ProcessId); - EXPECT_EQ(client_read_data->present_event.SwapChainAddress, data.present_event.SwapChainAddress); - EXPECT_EQ(client_read_data->present_event.SyncInterval, data.present_event.SyncInterval); - - SUCCEED(); -} - -TEST(NamedSharedMemoryTest, CreateNamedSharedMemory) { - DWORD proc_id = GetCurrentProcessId(); - EXPECT_NE(proc_id, 0); - - std::unique_ptr streamer = std::make_unique(); - streamer->CreateNamedSharedMemory(proc_id); - EXPECT_NE(streamer->process_shared_mem_map_.find(proc_id), - streamer->process_shared_mem_map_.end()); - string mapfilename = streamer->GetMapFileName(proc_id); - EXPECT_EQ(mapfilename, kGlobalPrefix + std::to_string(proc_id)); - - // Test standard etl case - streamer->SetStreamMode(StreamMode::kOfflineEtl); - streamer->CreateNamedSharedMemory(static_cast(StreamPidOverride::kEtlPid)); - mapfilename = streamer->GetMapFileName( - static_cast(StreamPidOverride::kEtlPid)); - - EXPECT_EQ(mapfilename, kGlobalPrefix + std::to_string(static_cast( - StreamPidOverride::kEtlPid))); -} - -TEST(NamedSharedMemoryTestCustomSize, CreateNamedSharedMemory) { - DWORD proc_id = GetCurrentProcessId(); - EXPECT_NE(proc_id, 0); - - std::unique_ptr streamer = std::make_unique(); - // Buf size 0 - streamer->CreateNamedSharedMemory(proc_id, 0); - EXPECT_EQ(streamer->process_shared_mem_map_.find(proc_id), - streamer->process_shared_mem_map_.end()); - - // Normal buf size - streamer->CreateNamedSharedMemory(proc_id, kNsmBufSize); - EXPECT_NE(streamer->process_shared_mem_map_.find(proc_id), - streamer->process_shared_mem_map_.end()); - string mapfilename = streamer->GetMapFileName(proc_id); - EXPECT_EQ(mapfilename, kGlobalPrefix + std::to_string(proc_id)); - streamer->StopAllStreams(); - - // Oversized buf size - streamer->CreateNamedSharedMemory(proc_id, kNsmBufSizeLarge); - EXPECT_EQ(streamer->process_shared_mem_map_.find(proc_id), - streamer->process_shared_mem_map_.end()); - mapfilename = streamer->GetMapFileName(proc_id); -} - -TEST_F(StreamerULT, ServerWriteDataOverflow) { - // There are enough data to ensure write overflow - ServerRead(kSamplePresentMonFileSmall); - DWORD proc_id = GetCurrentProcessId(); - - string mapfile_name = streamer_.GetMapFileName(proc_id); - EXPECT_FALSE(mapfile_name.empty()); - PmNsmFrameData data = {}; - GpuTelemetryBitset gpu_telemetry_cap_bits; - CpuTelemetryBitset cpu_telemetry_cap_bits; - gpu_telemetry_cap_bits.set(); - cpu_telemetry_cap_bits.set(); - - ParsePresentMonCsvData(sample_test_data, data); - - streamer_.WriteFrameData(proc_id, &data, gpu_telemetry_cap_bits, - cpu_telemetry_cap_bits); - - std::this_thread::sleep_for(std::chrono::milliseconds(kClientReadIntervalsInMs)); - - StreamClient client(std::move(mapfile_name), false); - - PmNsmFrameData* client_read_data = nullptr; - client_read_data = client.ReadLatestFrame(); - EXPECT_NE(client_read_data, nullptr); - - EXPECT_EQ(client_read_data->present_event.ProcessId, data.present_event.ProcessId); - EXPECT_EQ(client_read_data->present_event.SwapChainAddress, data.present_event.SwapChainAddress); - EXPECT_EQ(client_read_data->present_event.SyncInterval, data.present_event.SyncInterval); -} - -TEST_F(StreamerULT, ReadBeforeWrite) { - StreamClient client; - - PmNsmFrameData* client_read_data = nullptr; - client_read_data = client.ReadLatestFrame(); - EXPECT_EQ(client_read_data, nullptr); -} \ No newline at end of file diff --git a/IntelPresentMon/ULT/TelemetryHistory.cpp b/IntelPresentMon/ULT/TelemetryHistory.cpp deleted file mode 100644 index 136d618b..00000000 --- a/IntelPresentMon/ULT/TelemetryHistory.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "gtest/gtest.h" -#include "../ControlLib/TelemetryHistory.h" -#include -#include -#include -#include - -TEST(TelemetryHistory, iterationEmpty) -{ - pwr::TelemetryHistory hist(5); - EXPECT_EQ(0, hist.end() - hist.begin()); -} - -TEST(TelemetryHistory, iterationPartial) -{ - pwr::TelemetryHistory hist(5); - hist.Push({ .qpc = 10 }); - hist.Push({ .qpc = 20 }); - hist.Push({ .qpc = 30 }); - EXPECT_EQ(3, hist.end() - hist.begin()); - EXPECT_EQ(10, (*(hist.begin() + 0)).qpc); - EXPECT_EQ(20, (hist.begin() += 1)->qpc); - EXPECT_EQ(30, hist.begin()[2].qpc); -} - -TEST(TelemetryHistory, iterationFull) -{ - pwr::TelemetryHistory hist(5); - hist.Push({ .qpc = 10 }); - hist.Push({ .qpc = 20 }); - hist.Push({ .qpc = 30 }); - hist.Push({ .qpc = 40 }); - hist.Push({ .qpc = 50 }); - EXPECT_EQ(5, hist.end() - hist.begin()); - - std::vector plucked; - std::transform(hist.begin(), hist.end(), std::back_inserter(plucked), - std::mem_fn(&PresentMonPowerTelemetryInfo::qpc)); - - std::vector expected{ 10, 20, 30, 40, 50 }; - EXPECT_TRUE(plucked == expected); -} - -TEST(TelemetryHistory, iterationExcursioned) -{ - pwr::TelemetryHistory hist(5); - hist.Push({ .qpc = 10 }); - hist.Push({ .qpc = 20 }); - hist.Push({ .qpc = 30 }); - hist.Push({ .qpc = 40 }); - hist.Push({ .qpc = 50 }); - hist.Push({ .qpc = 60 }); - hist.Push({ .qpc = 70 }); - EXPECT_EQ(5, hist.end() - hist.begin()); - - std::vector plucked; - std::transform(hist.begin(), hist.end(), std::back_inserter(plucked), - std::mem_fn(&PresentMonPowerTelemetryInfo::qpc)); - - std::vector expected{ 30, 40, 50, 60, 70 }; - EXPECT_TRUE(plucked == expected); -} - -TEST(TelemetryHistory, nearestInside) -{ - pwr::TelemetryHistory hist(5); - hist.Push({ .qpc = 10 }); - hist.Push({ .qpc = 20 }); - hist.Push({ .qpc = 30 }); - hist.Push({ .qpc = 40 }); - hist.Push({ .qpc = 50 }); - hist.Push({ .qpc = 60 }); - hist.Push({ .qpc = 70 }); - - const auto nearest = hist.GetNearest(50); - EXPECT_TRUE(bool(nearest)); - EXPECT_EQ(50, nearest->qpc); -} - -TEST(TelemetryHistory, nearestCloseLower) -{ - pwr::TelemetryHistory hist(5); - hist.Push({ .qpc = 10 }); - hist.Push({ .qpc = 20 }); - hist.Push({ .qpc = 30 }); - hist.Push({ .qpc = 40 }); - hist.Push({ .qpc = 50 }); - hist.Push({ .qpc = 60 }); - hist.Push({ .qpc = 70 }); - - const auto nearest = hist.GetNearest(33); - EXPECT_TRUE(bool(nearest)); - EXPECT_EQ(30, nearest->qpc); -} - -TEST(TelemetryHistory, nearestCloseHigher) -{ - pwr::TelemetryHistory hist(5); - hist.Push({ .qpc = 10 }); - hist.Push({ .qpc = 20 }); - hist.Push({ .qpc = 30 }); - hist.Push({ .qpc = 40 }); - hist.Push({ .qpc = 50 }); - hist.Push({ .qpc = 60 }); - hist.Push({ .qpc = 70 }); - - const auto nearest = hist.GetNearest(58); - EXPECT_TRUE(bool(nearest)); - EXPECT_EQ(60, nearest->qpc); -} \ No newline at end of file diff --git a/IntelPresentMon/ULT/ULT.vcxproj b/IntelPresentMon/ULT/ULT.vcxproj deleted file mode 100644 index 52113403..00000000 --- a/IntelPresentMon/ULT/ULT.vcxproj +++ /dev/null @@ -1,115 +0,0 @@ - - - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {f7d9e1cd-298d-465a-82d2-8778c860bc46} - ULT - PresentMonULT - 10.0 - - - - Application - true - v143 - MultiByte - - - Application - false - v143 - true - MultiByte - - - - - - - - - - - - - - - - - - - - - - - - - - Level3 - true - true - MultiThreadedDebug - stdcpplatest - - - Console - true - manual-link\gtest_main.lib;bcrypt.lib;shlwapi.lib;%(AdditionalDependencies) - - - - - Level3 - true - true - true - true - MultiThreaded - stdcpplatest - - - Console - true - true - true - manual-link\gtest_main.lib;bcrypt.lib;shlwapi.lib;%(AdditionalDependencies) - - - - - {3c39c9bc-0e85-42c0-894c-3561bb93e87f} - - - {66e9f6c5-28db-4218-81b9-31e0e146ecc0} - - - {bf43064b-01f0-4c69-91fb-c2122baf621d} - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/IntelPresentMon/ULT/ULT.vcxproj.filters b/IntelPresentMon/ULT/ULT.vcxproj.filters deleted file mode 100644 index 2f9d5dad..00000000 --- a/IntelPresentMon/ULT/ULT.vcxproj.filters +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/IntelPresentMon/ULT/utils.cpp b/IntelPresentMon/ULT/utils.cpp deleted file mode 100644 index 1ba2bdb0..00000000 --- a/IntelPresentMon/ULT/utils.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "utils.h" - -#include - -string GetCurrentProcessName(){ - static const int kPathSize = 1024; - char currpath[kPathSize]; - string exename; - string exepath; - if (GetModuleFileNameA(NULL, currpath, kPathSize) > 0) { - exename = exepath = currpath; - size_t sep = exename.find_last_of("\\"); - if (sep != std::string::npos) { - exename = exename.substr(sep + 1, exename.size() - sep - 1); - exepath = exepath.substr(0, sep); - } - } - - return exename; -} diff --git a/IntelPresentMon/ULT/utils.h b/IntelPresentMon/ULT/utils.h deleted file mode 100644 index 542e65b2..00000000 --- a/IntelPresentMon/ULT/utils.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once -#include - -using std::string; - -string GetCurrentProcessName(); \ No newline at end of file diff --git a/IntelPresentMon/UnitTests/MetricsCore.cpp b/IntelPresentMon/UnitTests/MetricsCore.cpp index b0a6d76c..a1927c0b 100644 --- a/IntelPresentMon/UnitTests/MetricsCore.cpp +++ b/IntelPresentMon/UnitTests/MetricsCore.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include @@ -20,199 +19,6 @@ namespace MetricsCoreTests // SECTION 1: Core Types & Foundation // ============================================================================ - TEST_CLASS(QpcConverterTests) - { - public: - TEST_METHOD(TimestampDeltaToMilliSeconds_BasicConversion) - { - // 10MHz QPC frequency (10,000,000 ticks per second) - QpcConverter qpc(10'000'000, 0); - - // 10,000 ticks = 1 millisecond at 10MHz - double result = qpc.DurationMilliSeconds(10'000); - Assert::AreEqual(1.0, result, 0.0001); - } - - TEST_METHOD(TimestampDeltaToMilliSeconds_ZeroDuration) - { - QpcConverter qpc(10'000'000, 0); - double result = qpc.DurationMilliSeconds(0); - Assert::AreEqual(0.0, result); - } - - TEST_METHOD(TimestampDeltaToMilliSeconds_LargeDuration) - { - QpcConverter qpc(10'000'000, 0); - - // 100,000,000 ticks = 10,000 milliseconds at 10MHz - double result = qpc.DurationMilliSeconds(100'000'000); - Assert::AreEqual(10'000.0, result, 0.01); - } - - TEST_METHOD(TimestampDeltaToUnsignedMilliSeconds_ForwardTime) - { - QpcConverter qpc(10'000'000, 0); - - // Start at 1000, end at 11000 (10,000 ticks = 1ms) - double result = qpc.DeltaUnsignedMilliSeconds(1000, 11'000); - Assert::AreEqual(1.0, result, 0.0001); - } - - TEST_METHOD(TimestampDeltaToUnsignedMilliSeconds_ZeroDelta) - { - QpcConverter qpc(10'000'000, 0); - double result = qpc.DeltaUnsignedMilliSeconds(5000, 5000); - Assert::AreEqual(0.0, result); - } - - TEST_METHOD(TimestampDeltaToUnsignedMilliSeconds_TypicalFrameTime) - { - // Typical QPC frequency: ~10MHz - QpcConverter qpc(10'000'000, 0); - - // 16.666ms frame time at 60fps - uint64_t frameTimeTicks = 166'660; - double result = qpc.DurationMilliSeconds(frameTimeTicks); - Assert::AreEqual(16.666, result, 0.001); - } - - TEST_METHOD(GetStartTimestamp_ReturnsCorrectValue) - { - uint64_t startTime = 123'456'789; - QpcConverter qpc(10'000'000, startTime); - - Assert::AreEqual(startTime, qpc.GetSessionStartTimestamp()); - } - }; - - TEST_CLASS(PresentSnapshotTests) - { - public: - TEST_METHOD(FromCircularBuffer_CopiesBasicTimingFields) - { - // Create a mock NSM present event - PmNsmPresentEvent nsmEvent{}; - nsmEvent.PresentStartTime = 1000; - nsmEvent.ReadyTime = 2000; - nsmEvent.TimeInPresent = 500; - nsmEvent.GPUStartTime = 1200; - nsmEvent.GPUDuration = 800; - nsmEvent.GPUVideoDuration = 300; - - auto frame = FrameData::CopyFrameData(nsmEvent); - - Assert::AreEqual(1000ull, frame.presentStartTime); - Assert::AreEqual(2000ull, frame.readyTime); - Assert::AreEqual(500ull, frame.timeInPresent); - Assert::AreEqual(1200ull, frame.gpuStartTime); - Assert::AreEqual(800ull, frame.gpuDuration); - Assert::AreEqual(300ull, frame.gpuVideoDuration); - } - - TEST_METHOD(FromCircularBuffer_CopiesAppPropagatedData) - { - PmNsmPresentEvent nsmEvent{}; - nsmEvent.AppPropagatedPresentStartTime =5000; - nsmEvent.AppPropagatedTimeInPresent =600; - nsmEvent.AppPropagatedGPUStartTime =5200; - nsmEvent.AppPropagatedReadyTime =6000; - nsmEvent.AppPropagatedGPUDuration =800; - nsmEvent.AppPropagatedGPUVideoDuration =200; - - auto frame = FrameData::CopyFrameData(nsmEvent); - - Assert::AreEqual(5000ull, frame.appPropagatedPresentStartTime); - Assert::AreEqual(600ull, frame.appPropagatedTimeInPresent); - Assert::AreEqual(5200ull, frame.appPropagatedGPUStartTime); - Assert::AreEqual(6000ull, frame.appPropagatedReadyTime); - Assert::AreEqual(800ull, frame.appPropagatedGPUDuration); - Assert::AreEqual(200ull, frame.appPropagatedGPUVideoDuration); - } - - TEST_METHOD(FromCircularBuffer_CopiesInstrumentedTimestamps) - { - PmNsmPresentEvent nsmEvent{}; - nsmEvent.AppSimStartTime =100; - nsmEvent.AppSleepStartTime =200; - nsmEvent.AppSleepEndTime =250; - nsmEvent.AppRenderSubmitStartTime =300; - - auto frame = FrameData::CopyFrameData(nsmEvent); - - Assert::AreEqual(100ull, frame.appSimStartTime); - Assert::AreEqual(200ull, frame.appSleepStartTime); - Assert::AreEqual(250ull, frame.appSleepEndTime); - Assert::AreEqual(300ull, frame.appRenderSubmitStartTime); - } - - TEST_METHOD(FromCircularBuffer_CopiesPcLatencyData) - { - PmNsmPresentEvent nsmEvent{}; - nsmEvent.PclSimStartTime =7000; - nsmEvent.PclInputPingTime =6500; - - auto frame = FrameData::CopyFrameData(nsmEvent); - - Assert::AreEqual(7000ull, frame.pclSimStartTime); - Assert::AreEqual(6500ull, frame.pclInputPingTime); - } - - TEST_METHOD(FromCircularBuffer_CopiesInputTimes) - { - PmNsmPresentEvent nsmEvent{}; - nsmEvent.InputTime =8000; - nsmEvent.MouseClickTime =8050; - - auto frame = FrameData::CopyFrameData(nsmEvent); - - Assert::AreEqual(8000ull, frame.inputTime); - Assert::AreEqual(8050ull, frame.mouseClickTime); - } - - TEST_METHOD(FromCircularBuffer_NormalizesDisplayArrays) - { - PmNsmPresentEvent nsmEvent{}; - nsmEvent.DisplayedCount =2; - nsmEvent.Displayed_FrameType[0] = FrameType::Application; - nsmEvent.Displayed_ScreenTime[0] =9000; - nsmEvent.Displayed_FrameType[1] = FrameType::Repeated; - nsmEvent.Displayed_ScreenTime[1] =9500; - - auto frame = FrameData::CopyFrameData(nsmEvent); - - Assert::AreEqual(size_t(2), frame.displayed.Size()); - Assert::IsTrue(frame.displayed[0].first == FrameType::Application); - Assert::AreEqual(9000ull, frame.displayed[0].second); - Assert::IsTrue(frame.displayed[1].first == FrameType::Repeated); - Assert::AreEqual(9500ull, frame.displayed[1].second); - } - - TEST_METHOD(FromCircularBuffer_HandlesEmptyDisplayArray) - { - PmNsmPresentEvent nsmEvent{}; - nsmEvent.DisplayedCount = 0; - - auto frame = FrameData::CopyFrameData(nsmEvent); - - Assert::AreEqual(size_t(0), frame.displayed.Size()); - } - - TEST_METHOD(FromCircularBuffer_CopiesMetadata) - { - PmNsmPresentEvent nsmEvent{}; - nsmEvent.ProcessId =1234; - nsmEvent.ThreadId =5678; - nsmEvent.SwapChainAddress =0xDEADBEEF; - nsmEvent.FrameId =42; - - auto frame = FrameData::CopyFrameData(nsmEvent); - - Assert::AreEqual(uint32_t(1234), frame.processId); - Assert::AreEqual(uint32_t(5678), frame.threadId); - Assert::AreEqual(uint64_t(0xDEADBEEF), frame.swapChainAddress); - Assert::AreEqual(uint32_t(42), frame.frameId); - } - }; // ConsoleAdapter tests are skipped in unit tests because they require PresentData // which has ETW dependencies. These will be tested during Console integration. @@ -3855,7 +3661,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) // msGPUWait = 600'000 - 500'000 = 100'000 ticks = 10 ms double expectedTotal = qpc.DeltaUnsignedMilliSeconds(1'000'000, 1'600'000); double expectedBusy = qpc.DurationMilliSeconds(500'000); - double expectedWait = max(0.0, expectedTotal - expectedBusy); + double expectedWait = std::max(0.0, expectedTotal - expectedBusy); Assert::AreEqual(expectedWait, m.msGPUWait, 0.0001); } @@ -3999,7 +3805,7 @@ TEST_CLASS(ComputeMetricsForPresentTests) // msGPUWait = 550'000 - 450'000 = 100'000 ticks = 10 ms double expectedTotal = qpc.DeltaUnsignedMilliSeconds(1'000'000, 1'550'000); double expectedBusy = qpc.DurationMilliSeconds(450'000); - double expectedWait = max(0.0, expectedTotal - expectedBusy); + double expectedWait = std::max(0.0, expectedTotal - expectedBusy); Assert::AreEqual(expectedWait, m.msGPUWait, 0.0001); } }; diff --git a/PresentData/PresentData.vcxproj b/PresentData/PresentData.vcxproj index 3557af13..96e707fc 100644 --- a/PresentData/PresentData.vcxproj +++ b/PresentData/PresentData.vcxproj @@ -359,6 +359,7 @@ + diff --git a/PresentData/PresentData.vcxproj.filters b/PresentData/PresentData.vcxproj.filters index 19e57419..df266457 100644 --- a/PresentData/PresentData.vcxproj.filters +++ b/PresentData/PresentData.vcxproj.filters @@ -49,6 +49,7 @@ + diff --git a/PresentData/PresentEventEnums.hpp b/PresentData/PresentEventEnums.hpp new file mode 100644 index 00000000..ff808f59 --- /dev/null +++ b/PresentData/PresentEventEnums.hpp @@ -0,0 +1,43 @@ +// Copyright (C) 2017-2024 Intel Corporation +// Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved +// SPDX-License-Identifier: MIT +#pragma once + +enum class PresentMode { + Unknown = 0, + Hardware_Legacy_Flip = 1, + Hardware_Legacy_Copy_To_Front_Buffer = 2, + Hardware_Independent_Flip = 3, + Composed_Flip = 4, + Composed_Copy_GPU_GDI = 5, + Composed_Copy_CPU_GDI = 6, + Hardware_Composed_Independent_Flip = 8, +}; + +enum class PresentResult { + Unknown = 0, + Presented = 1, + Discarded = 2, +}; + +enum class Runtime { + Other = 0, + DXGI = 1, + D3D9 = 2, +}; + +enum class InputDeviceType { + None = 0, + Unknown = 1, + Mouse = 2, + Keyboard = 3, +}; + +enum class FrameType { + NotSet = 0, + Unspecified = 1, + Application = 2, + Repeated = 3, + Intel_XEFG = 50, + AMD_AFMF = 100, +}; \ No newline at end of file diff --git a/PresentData/PresentMonTraceConsumer.hpp b/PresentData/PresentMonTraceConsumer.hpp index 812486e0..6d8aec97 100644 --- a/PresentData/PresentMonTraceConsumer.hpp +++ b/PresentData/PresentMonTraceConsumer.hpp @@ -27,6 +27,7 @@ #include "GpuTrace.hpp" #include "TraceConsumer.hpp" #include "NvidiaTraceConsumer.hpp" +#include "PresentEventEnums.hpp" #include "../IntelPresentMon/CommonUtilities/Hash.h" // PresentMode represents the different paths a present can take on windows. @@ -92,45 +93,6 @@ // -> DxgKrnl_PresentHistory_Info (by token ptr) // -> Assume DWM will compose this buffer on next present (missing InFrame event), follow windowed blit paths to screen time -enum class PresentMode { - Unknown = 0, - Hardware_Legacy_Flip = 1, - Hardware_Legacy_Copy_To_Front_Buffer = 2, - Hardware_Independent_Flip = 3, - Composed_Flip = 4, - Composed_Copy_GPU_GDI = 5, - Composed_Copy_CPU_GDI = 6, - Hardware_Composed_Independent_Flip = 8, -}; - -enum class PresentResult { - Unknown = 0, - Presented = 1, - Discarded = 2, -}; - -enum class Runtime { - Other = 0, - DXGI = 1, - D3D9 = 2, -}; - -enum class InputDeviceType { - None = 0, - Unknown = 1, - Mouse = 2, - Keyboard = 3, -}; - -enum class FrameType { - NotSet = 0, - Unspecified = 1, - Application = 2, - Repeated = 3, - Intel_XEFG = 50, - AMD_AFMF = 100, -}; - struct InputData { uint64_t Time; uint64_t MouseClickTime; diff --git a/PresentMon.sln b/PresentMon.sln index 6278dcce..8ae90fc6 100644 --- a/PresentMon.sln +++ b/PresentMon.sln @@ -7,16 +7,12 @@ Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "PMInstaller", "IntelPresent {0F60DFD9-208E-443E-8D01-43C902B458A6} = {0F60DFD9-208E-443E-8D01-43C902B458A6} {3C39C9BC-0E85-42C0-894C-3561BB93E87F} = {3C39C9BC-0E85-42C0-894C-3561BB93E87F} {4EB9794B-1F12-48CE-ADC1-917E9810F29E} = {4EB9794B-1F12-48CE-ADC1-917E9810F29E} - {57ABAF22-837A-4DA3-9C92-1BA37CCFA550} = {57ABAF22-837A-4DA3-9C92-1BA37CCFA550} - {66E9F6C5-28DB-4218-81B9-31E0E146ECC0} = {66E9F6C5-28DB-4218-81B9-31E0E146ECC0} {79FE382A-C29C-4C32-A028-B8D1ABDF51B0} = {79FE382A-C29C-4C32-A028-B8D1ABDF51B0} {7A1C7F0B-ECB3-4C98-B74E-E5BBA63BA4A7} = {7A1C7F0B-ECB3-4C98-B74E-E5BBA63BA4A7} {807370FB-ADE0-403A-A278-DF50893E5F94} = {807370FB-ADE0-403A-A278-DF50893E5F94} {808F5EA9-EA09-4D72-87B4-5397D43CBA54} = {808F5EA9-EA09-4D72-87B4-5397D43CBA54} {892028E5-32F6-45FC-8AB2-90FCBCAC4BF6} = {892028E5-32F6-45FC-8AB2-90FCBCAC4BF6} {9E3C74BB-12BB-40AC-86BF-818D20140CBA} = {9E3C74BB-12BB-40AC-86BF-818D20140CBA} - {BF43064B-01F0-4C69-91FB-C2122BAF621D} = {BF43064B-01F0-4C69-91FB-C2122BAF621D} - {F7D9E1CD-298D-465A-82D2-8778C860BC46} = {F7D9E1CD-298D-465A-82D2-8778C860BC46} EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PMInstallerExtension", "IntelPresentMon\PMInstallerExtension\PMInstallerExtension.csproj", "{9E3C74BB-12BB-40AC-86BF-818D20140CBA}" @@ -27,12 +23,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SDK", "SDK", "{6DCB803B-9FC EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PresentMonService", "IntelPresentMon\PresentMonService\PresentMonService.vcxproj", "{3A848E5B-A376-4A22-BBAC-E9B3C01BD385}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PresentMonUtils", "IntelPresentMon\PresentMonUtils\PresentMonUtils.vcxproj", "{66E9F6C5-28DB-4218-81B9-31E0E146ECC0}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ControlLib", "IntelPresentMon\ControlLib\ControlLib.vcxproj", "{3C39C9BC-0E85-42C0-894C-3561BB93E87F}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Streamer", "IntelPresentMon\Streamer\Streamer.vcxproj", "{BF43064B-01F0-4C69-91FB-C2122BAF621D}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PresentData", "PresentData\PresentData.vcxproj", "{892028E5-32F6-45FC-8AB2-90FCBCAC4BF6}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Core", "IntelPresentMon\Core\Core.vcxproj", "{808F5EA9-EA09-4D72-87B4-5397D43CBA54}" @@ -43,8 +35,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{B60F EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SampleClient", "IntelPresentMon\SampleClient\SampleClient.vcxproj", "{79FE382A-C29C-4C32-A028-B8D1ABDF51B0}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SampleStreamerClient", "IntelPresentMon\SampleStreamerClient\SampleStreamerClient.vcxproj", "{57ABAF22-837A-4DA3-9C92-1BA37CCFA550}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PresentMon", "PresentMon", "{9FFA4649-52C4-4492-83DF-2F417C8E3819}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "p2c", "p2c", "{0015EC44-0BF0-4F05-80CF-72000771F6EB}" @@ -62,8 +52,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Shaders", "IntelPresentMon\ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Service", "Service", "{86FF3CD6-7065-40A5-B9D3-FAC961D2AAE0}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PresentMonULT", "IntelPresentMon\ULT\ULT.vcxproj", "{F7D9E1CD-298D-465A-82D2-8778C860BC46}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{B4CC5828-9638-42EC-A692-E81E8227DD84}" ProjectSection(SolutionItems) = preProject .gitignore = .gitignore @@ -165,17 +153,6 @@ Global {3A848E5B-A376-4A22-BBAC-E9B3C01BD385}.Release-EDSS|x86.ActiveCfg = Release|x64 {3A848E5B-A376-4A22-BBAC-E9B3C01BD385}.Release-EDSS-MSI|x64.ActiveCfg = Release|x64 {3A848E5B-A376-4A22-BBAC-E9B3C01BD385}.Release-EDSS-MSI|x86.ActiveCfg = Release|x64 - {66E9F6C5-28DB-4218-81B9-31E0E146ECC0}.Debug|x64.ActiveCfg = Debug|x64 - {66E9F6C5-28DB-4218-81B9-31E0E146ECC0}.Debug|x64.Build.0 = Debug|x64 - {66E9F6C5-28DB-4218-81B9-31E0E146ECC0}.Debug|x86.ActiveCfg = Debug|x64 - {66E9F6C5-28DB-4218-81B9-31E0E146ECC0}.Release|x64.ActiveCfg = Release|x64 - {66E9F6C5-28DB-4218-81B9-31E0E146ECC0}.Release|x64.Build.0 = Release|x64 - {66E9F6C5-28DB-4218-81B9-31E0E146ECC0}.Release|x86.ActiveCfg = Release|x64 - {66E9F6C5-28DB-4218-81B9-31E0E146ECC0}.Release-EDSS|x64.ActiveCfg = Release|x64 - {66E9F6C5-28DB-4218-81B9-31E0E146ECC0}.Release-EDSS|x64.Build.0 = Release|x64 - {66E9F6C5-28DB-4218-81B9-31E0E146ECC0}.Release-EDSS|x86.ActiveCfg = Release|x64 - {66E9F6C5-28DB-4218-81B9-31E0E146ECC0}.Release-EDSS-MSI|x64.ActiveCfg = Release|x64 - {66E9F6C5-28DB-4218-81B9-31E0E146ECC0}.Release-EDSS-MSI|x86.ActiveCfg = Release|x64 {3C39C9BC-0E85-42C0-894C-3561BB93E87F}.Debug|x64.ActiveCfg = Debug|x64 {3C39C9BC-0E85-42C0-894C-3561BB93E87F}.Debug|x64.Build.0 = Debug|x64 {3C39C9BC-0E85-42C0-894C-3561BB93E87F}.Debug|x86.ActiveCfg = Debug|x64 @@ -187,17 +164,6 @@ Global {3C39C9BC-0E85-42C0-894C-3561BB93E87F}.Release-EDSS|x86.ActiveCfg = Release|x64 {3C39C9BC-0E85-42C0-894C-3561BB93E87F}.Release-EDSS-MSI|x64.ActiveCfg = Release|x64 {3C39C9BC-0E85-42C0-894C-3561BB93E87F}.Release-EDSS-MSI|x86.ActiveCfg = Release|x64 - {BF43064B-01F0-4C69-91FB-C2122BAF621D}.Debug|x64.ActiveCfg = Debug|x64 - {BF43064B-01F0-4C69-91FB-C2122BAF621D}.Debug|x64.Build.0 = Debug|x64 - {BF43064B-01F0-4C69-91FB-C2122BAF621D}.Debug|x86.ActiveCfg = Debug|x64 - {BF43064B-01F0-4C69-91FB-C2122BAF621D}.Release|x64.ActiveCfg = Release|x64 - {BF43064B-01F0-4C69-91FB-C2122BAF621D}.Release|x64.Build.0 = Release|x64 - {BF43064B-01F0-4C69-91FB-C2122BAF621D}.Release|x86.ActiveCfg = Release|x64 - {BF43064B-01F0-4C69-91FB-C2122BAF621D}.Release-EDSS|x64.ActiveCfg = Release|x64 - {BF43064B-01F0-4C69-91FB-C2122BAF621D}.Release-EDSS|x64.Build.0 = Release|x64 - {BF43064B-01F0-4C69-91FB-C2122BAF621D}.Release-EDSS|x86.ActiveCfg = Release|x64 - {BF43064B-01F0-4C69-91FB-C2122BAF621D}.Release-EDSS-MSI|x64.ActiveCfg = Release|x64 - {BF43064B-01F0-4C69-91FB-C2122BAF621D}.Release-EDSS-MSI|x86.ActiveCfg = Release|x64 {892028E5-32F6-45FC-8AB2-90FCBCAC4BF6}.Debug|x64.ActiveCfg = Debug|x64 {892028E5-32F6-45FC-8AB2-90FCBCAC4BF6}.Debug|x64.Build.0 = Debug|x64 {892028E5-32F6-45FC-8AB2-90FCBCAC4BF6}.Debug|x86.ActiveCfg = Debug|Win32 @@ -233,16 +199,6 @@ Global {79FE382A-C29C-4C32-A028-B8D1ABDF51B0}.Release-EDSS|x86.ActiveCfg = Release|x64 {79FE382A-C29C-4C32-A028-B8D1ABDF51B0}.Release-EDSS-MSI|x64.ActiveCfg = Release|x64 {79FE382A-C29C-4C32-A028-B8D1ABDF51B0}.Release-EDSS-MSI|x86.ActiveCfg = Release|x64 - {57ABAF22-837A-4DA3-9C92-1BA37CCFA550}.Debug|x64.ActiveCfg = Debug|x64 - {57ABAF22-837A-4DA3-9C92-1BA37CCFA550}.Debug|x64.Build.0 = Debug|x64 - {57ABAF22-837A-4DA3-9C92-1BA37CCFA550}.Debug|x86.ActiveCfg = Debug|x64 - {57ABAF22-837A-4DA3-9C92-1BA37CCFA550}.Release|x64.ActiveCfg = Release|x64 - {57ABAF22-837A-4DA3-9C92-1BA37CCFA550}.Release|x64.Build.0 = Release|x64 - {57ABAF22-837A-4DA3-9C92-1BA37CCFA550}.Release|x86.ActiveCfg = Release|x64 - {57ABAF22-837A-4DA3-9C92-1BA37CCFA550}.Release-EDSS|x64.ActiveCfg = Release|x64 - {57ABAF22-837A-4DA3-9C92-1BA37CCFA550}.Release-EDSS|x86.ActiveCfg = Release|x64 - {57ABAF22-837A-4DA3-9C92-1BA37CCFA550}.Release-EDSS-MSI|x64.ActiveCfg = Release|x64 - {57ABAF22-837A-4DA3-9C92-1BA37CCFA550}.Release-EDSS-MSI|x86.ActiveCfg = Release|x64 {4EB9794B-1F12-48CE-ADC1-917E9810F29E}.Debug|x64.ActiveCfg = Debug|x64 {4EB9794B-1F12-48CE-ADC1-917E9810F29E}.Debug|x64.Build.0 = Debug|x64 {4EB9794B-1F12-48CE-ADC1-917E9810F29E}.Debug|x86.ActiveCfg = Debug|Win32 @@ -279,14 +235,6 @@ Global {0F60DFD9-208E-443E-8D01-43C902B458A6}.Release-EDSS|x86.ActiveCfg = Release|Win32 {0F60DFD9-208E-443E-8D01-43C902B458A6}.Release-EDSS-MSI|x64.ActiveCfg = Release|x64 {0F60DFD9-208E-443E-8D01-43C902B458A6}.Release-EDSS-MSI|x86.ActiveCfg = Release|Win32 - {F7D9E1CD-298D-465A-82D2-8778C860BC46}.Debug|x64.ActiveCfg = Debug|x64 - {F7D9E1CD-298D-465A-82D2-8778C860BC46}.Debug|x86.ActiveCfg = Debug|x64 - {F7D9E1CD-298D-465A-82D2-8778C860BC46}.Release|x64.ActiveCfg = Release|x64 - {F7D9E1CD-298D-465A-82D2-8778C860BC46}.Release|x86.ActiveCfg = Release|x64 - {F7D9E1CD-298D-465A-82D2-8778C860BC46}.Release-EDSS|x64.ActiveCfg = Release|x64 - {F7D9E1CD-298D-465A-82D2-8778C860BC46}.Release-EDSS|x86.ActiveCfg = Release|x64 - {F7D9E1CD-298D-465A-82D2-8778C860BC46}.Release-EDSS-MSI|x64.ActiveCfg = Release|x64 - {F7D9E1CD-298D-465A-82D2-8778C860BC46}.Release-EDSS-MSI|x86.ActiveCfg = Release|x64 {5DDBA061-53A0-4835-8AAF-943F403F924F}.Debug|x64.ActiveCfg = Debug|x64 {5DDBA061-53A0-4835-8AAF-943F403F924F}.Debug|x64.Build.0 = Debug|x64 {5DDBA061-53A0-4835-8AAF-943F403F924F}.Debug|x86.ActiveCfg = Debug|x64 @@ -490,19 +438,15 @@ Global {9E3C74BB-12BB-40AC-86BF-818D20140CBA} = {61877103-313D-4140-8449-834D5D73C72E} {3C80B001-E165-4E0E-A92D-5767CF4FB1F8} = {0015EC44-0BF0-4F05-80CF-72000771F6EB} {3A848E5B-A376-4A22-BBAC-E9B3C01BD385} = {86FF3CD6-7065-40A5-B9D3-FAC961D2AAE0} - {66E9F6C5-28DB-4218-81B9-31E0E146ECC0} = {B4CC5828-9638-42EC-A692-E81E8227DD84} {3C39C9BC-0E85-42C0-894C-3561BB93E87F} = {86FF3CD6-7065-40A5-B9D3-FAC961D2AAE0} - {BF43064B-01F0-4C69-91FB-C2122BAF621D} = {B4CC5828-9638-42EC-A692-E81E8227DD84} {892028E5-32F6-45FC-8AB2-90FCBCAC4BF6} = {9FFA4649-52C4-4492-83DF-2F417C8E3819} {808F5EA9-EA09-4D72-87B4-5397D43CBA54} = {0015EC44-0BF0-4F05-80CF-72000771F6EB} {61877103-313D-4140-8449-834D5D73C72E} = {B4CC5828-9638-42EC-A692-E81E8227DD84} {79FE382A-C29C-4C32-A028-B8D1ABDF51B0} = {B60F7D25-F7B1-4D91-AC59-34AE02E7E1C5} - {57ABAF22-837A-4DA3-9C92-1BA37CCFA550} = {B60F7D25-F7B1-4D91-AC59-34AE02E7E1C5} {4EB9794B-1F12-48CE-ADC1-917E9810F29E} = {9FFA4649-52C4-4492-83DF-2F417C8E3819} {7A1C7F0B-ECB3-4C98-B74E-E5BBA63BA4A7} = {0015EC44-0BF0-4F05-80CF-72000771F6EB} {0F60DFD9-208E-443E-8D01-43C902B458A6} = {9FFA4649-52C4-4492-83DF-2F417C8E3819} {51979337-0180-48BD-BAD9-8AEF57FEF96D} = {0015EC44-0BF0-4F05-80CF-72000771F6EB} - {F7D9E1CD-298D-465A-82D2-8778C860BC46} = {86FF3CD6-7065-40A5-B9D3-FAC961D2AAE0} {5DDBA061-53A0-4835-8AAF-943F403F924F} = {6DCB803B-9FCE-456C-87B9-1365F59BD190} {BE924A9E-AE24-42A6-9C7C-EABC506A33C9} = {6DCB803B-9FCE-456C-87B9-1365F59BD190} {34B60AAC-4646-4AA8-A267-9A5DD7C097D5} = {6DCB803B-9FCE-456C-87B9-1365F59BD190} From 5e7c68f5822e2398a7a9e6fec3f5793d02c03418 Mon Sep 17 00:00:00 2001 From: Chili Date: Sun, 8 Feb 2026 04:36:03 +0900 Subject: [PATCH 187/205] remove legacy telemetry history --- .../ControlLib/AmdPowerTelemetryAdapter.cpp | 24 +- .../ControlLib/AmdPowerTelemetryAdapter.h | 14 +- .../ControlLib/AmdPowerTelemetryProvider.h | 5 +- IntelPresentMon/ControlLib/ControlLib.vcxproj | 1 - .../ControlLib/ControlLib.vcxproj.filters | 3 +- IntelPresentMon/ControlLib/CpuTelemetry.h | 13 +- .../ControlLib/IntelPowerTelemetryAdapter.cpp | 67 ++---- .../ControlLib/IntelPowerTelemetryAdapter.h | 16 +- .../ControlLib/IntelPowerTelemetryProvider.h | 5 +- .../NvidiaPowerTelemetryAdapter.cpp | 21 +- .../ControlLib/NvidiaPowerTelemetryAdapter.h | 12 +- .../ControlLib/NvidiaPowerTelemetryProvider.h | 5 +- .../ControlLib/PowerTelemetryAdapter.h | 12 +- IntelPresentMon/ControlLib/TelemetryHistory.h | 217 ------------------ IntelPresentMon/ControlLib/WmiCpu.cpp | 43 ++-- IntelPresentMon/ControlLib/WmiCpu.h | 16 +- .../PresentMonService/PMMainThread.cpp | 16 +- 17 files changed, 77 insertions(+), 413 deletions(-) delete mode 100644 IntelPresentMon/ControlLib/TelemetryHistory.h diff --git a/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.cpp b/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.cpp index 034c0a95..87cf57c6 100644 --- a/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.cpp +++ b/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #include "AmdPowerTelemetryAdapter.h" #include "Logging.h" @@ -54,11 +54,6 @@ bool AmdPowerTelemetryAdapter::GetVideoMemoryInfo(uint64_t& gpu_mem_size, uint64 } -const PresentMonPowerTelemetryInfo& AmdPowerTelemetryAdapter::GetNewest() const noexcept -{ - return *std::prev(history_.end()); -} - bool AmdPowerTelemetryAdapter::GetSustainedPowerLimit(double& sustainedPowerLimit) const noexcept { sustainedPowerLimit = 0.f; if (overdrive_version_ == 5) { @@ -120,7 +115,7 @@ double AmdPowerTelemetryAdapter::GetSustainedPowerLimit() const noexcept { return sustainedPowerLimit; } -bool AmdPowerTelemetryAdapter::Sample() noexcept { +PresentMonPowerTelemetryInfo AmdPowerTelemetryAdapter::Sample() noexcept { LARGE_INTEGER qpc; QueryPerformanceCounter(&qpc); @@ -160,11 +155,8 @@ bool AmdPowerTelemetryAdapter::Sample() noexcept { } } - // Insert telemetry into history - std::lock_guard lock{history_mutex_}; - history_.Push(info); - - return sample_return; + (void)sample_return; + return info; } bool AmdPowerTelemetryAdapter::Overdrive5Sample( @@ -467,12 +459,6 @@ bool AmdPowerTelemetryAdapter::Overdrive8Sample( } } -std::optional AmdPowerTelemetryAdapter::GetClosest( - uint64_t qpc) const noexcept { - std::lock_guard lock(history_mutex_); - return history_.GetNearest(qpc); -} - PM_DEVICE_VENDOR AmdPowerTelemetryAdapter::GetVendor() const noexcept { return PM_DEVICE_VENDOR::PM_DEVICE_VENDOR_AMD; } @@ -481,4 +467,4 @@ std::string AmdPowerTelemetryAdapter::GetName() const noexcept { return name_; } -} // namespace pwr::amd \ No newline at end of file +} // namespace pwr::amd diff --git a/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.h b/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.h index ff9641bb..fb06561e 100644 --- a/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.h +++ b/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.h @@ -1,10 +1,8 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #pragma once -#include #include #include "PowerTelemetryAdapter.h" -#include "TelemetryHistory.h" #include "Adl2Wrapper.h" namespace pwr::amd { @@ -22,10 +20,7 @@ class AmdPowerTelemetryAdapter : public PowerTelemetryAdapter { public: AmdPowerTelemetryAdapter(const Adl2Wrapper* adl_wrapper, std::string adl_adapter_name, int adl_adapter_index, int overdrive_version); - bool Sample() noexcept override; - std::optional GetClosest( - uint64_t qpc) const noexcept override; - const PresentMonPowerTelemetryInfo& GetNewest() const noexcept override; + PresentMonPowerTelemetryInfo Sample() noexcept override; PM_DEVICE_VENDOR GetVendor() const noexcept override; std::string GetName() const noexcept override; uint64_t GetDedicatedVideoMemory() const noexcept override; @@ -44,8 +39,5 @@ class AmdPowerTelemetryAdapter : public PowerTelemetryAdapter { int adl_adapter_index_ = 0; int overdrive_version_ = 0; std::string name_ = "Unknown Adapter Name"; - mutable std::mutex history_mutex_; - TelemetryHistory history_{ - PowerTelemetryAdapter::defaultHistorySize}; }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/ControlLib/AmdPowerTelemetryProvider.h b/IntelPresentMon/ControlLib/AmdPowerTelemetryProvider.h index 767dded2..b9df42f3 100644 --- a/IntelPresentMon/ControlLib/AmdPowerTelemetryProvider.h +++ b/IntelPresentMon/ControlLib/AmdPowerTelemetryProvider.h @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #define NOMINMAX @@ -8,7 +8,6 @@ #include #include "PresentMonPowerTelemetry.h" #include "PowerTelemetryProvider.h" -#include "TelemetryHistory.h" #include "Adl2Wrapper.h" namespace pwr::amd { @@ -27,4 +26,4 @@ class AmdPowerTelemetryProvider : public PowerTelemetryProvider { std::vector adl_adapter_infos_; std::vector> adapter_ptrs_; }; -} // namespace pwr::amd \ No newline at end of file +} // namespace pwr::amd diff --git a/IntelPresentMon/ControlLib/ControlLib.vcxproj b/IntelPresentMon/ControlLib/ControlLib.vcxproj index 58776432..57a2b4b3 100644 --- a/IntelPresentMon/ControlLib/ControlLib.vcxproj +++ b/IntelPresentMon/ControlLib/ControlLib.vcxproj @@ -132,7 +132,6 @@ - diff --git a/IntelPresentMon/ControlLib/ControlLib.vcxproj.filters b/IntelPresentMon/ControlLib/ControlLib.vcxproj.filters index f69441ac..b2762a98 100644 --- a/IntelPresentMon/ControlLib/ControlLib.vcxproj.filters +++ b/IntelPresentMon/ControlLib/ControlLib.vcxproj.filters @@ -74,7 +74,6 @@ - Intel @@ -130,4 +129,4 @@ Intel - \ No newline at end of file + diff --git a/IntelPresentMon/ControlLib/CpuTelemetry.h b/IntelPresentMon/ControlLib/CpuTelemetry.h index 40682b6b..dcee0bd3 100644 --- a/IntelPresentMon/ControlLib/CpuTelemetry.h +++ b/IntelPresentMon/ControlLib/CpuTelemetry.h @@ -1,8 +1,7 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #pragma once -#include #include #include #include @@ -16,10 +15,7 @@ namespace pwr::cpu { class CpuTelemetry { public: virtual ~CpuTelemetry() = default; - virtual bool Sample() noexcept = 0; - virtual const CpuTelemetryInfo& GetNewest() const noexcept = 0; - virtual std::optional GetClosest( - uint64_t qpc) const noexcept = 0; + virtual CpuTelemetryInfo Sample() noexcept = 0; void SetTelemetryCapBit(CpuTelemetryCapBits telemetryCapBit) noexcept { cpuTelemetryCapBits_.set(static_cast(telemetryCapBit)); @@ -32,9 +28,6 @@ class CpuTelemetry { std::string GetCpuName(); double GetCpuPowerLimit() { return 0.; } - // constants - static constexpr size_t defaultHistorySize = 300; - // data private: bool ExecuteWQLProcessorNameQuery(std::wstring& processor_name); @@ -43,4 +36,4 @@ class CpuTelemetry { cpuTelemetryCapBits_{}; std::string cpu_name_; }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.cpp b/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.cpp index 80314bdd..8016a9d4 100644 --- a/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.cpp +++ b/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #include "IntelPowerTelemetryAdapter.h" #include "Logging.h" @@ -151,13 +151,16 @@ namespace pwr::intel } } - bool IntelPowerTelemetryAdapter::Sample() noexcept + PresentMonPowerTelemetryInfo IntelPowerTelemetryAdapter::Sample() noexcept { - pmlog_verb(v::tele_gpu)("Sample called").pmwatch(GetName()); + pmlog_verb(v::tele_gpu)("Telemetry update called").pmwatch(GetName()); LARGE_INTEGER qpc; QueryPerformanceCounter(&qpc); bool success = true; + PresentMonPowerTelemetryInfo sample{ + .qpc = (uint64_t)qpc.QuadPart, + }; decltype(previousSampleVariant) currentSampleVariant; @@ -253,7 +256,7 @@ namespace pwr::intel } else { success = GatherSampleData(*currentSample, memory_state, - memory_bandwidth, gpu_sustained_power_limit_mw, (uint64_t)qpc.QuadPart) && success; + memory_bandwidth, gpu_sustained_power_limit_mw, (uint64_t)qpc.QuadPart, sample) && success; } } else { @@ -265,26 +268,12 @@ namespace pwr::intel } else { success = GatherSampleData(*currentSample, memory_state, - memory_bandwidth, gpu_sustained_power_limit_mw, (uint64_t)qpc.QuadPart) && success; + memory_bandwidth, gpu_sustained_power_limit_mw, (uint64_t)qpc.QuadPart, sample) && success; } } - return success; - } - - std::optional IntelPowerTelemetryAdapter::GetClosest(uint64_t qpc) const noexcept - { - std::lock_guard lock(historyMutex); - const auto nearest = history.GetNearest(qpc); - if constexpr (PMLOG_BUILD_LEVEL_ >= pmon::util::log::Level::Verbose) { - if (!nearest) { - pmlog_verb(v::tele_gpu)("Empty telemetry info sample returned").pmwatch(GetName()).pmwatch(qpc); - } - else { - pmlog_verb(v::tele_gpu)("Nearest telemetry info sampled").pmwatch(GetName()).pmwatch(qpc).pmwatch(ref::DumpStatic(*nearest)); - } - } - return nearest; + (void)success; + return sample; } PM_DEVICE_VENDOR IntelPowerTelemetryAdapter::GetVendor() const noexcept @@ -387,7 +376,8 @@ namespace pwr::intel ctl_mem_state_t& memory_state, ctl_mem_bandwidth_t& memory_bandwidth, std::optional gpu_sustained_power_limit_mw, - uint64_t qpc) + uint64_t qpc, + PresentMonPowerTelemetryInfo& sample) { bool success = true; @@ -398,33 +388,33 @@ namespace pwr::intel IGCL_ERR(result); } - PresentMonPowerTelemetryInfo pm_gpu_power_telemetry_info{ .qpc = qpc }; + sample.qpc = qpc; if (previousSampleVariant.index()) { if (const auto result = GetGPUPowerTelemetryData( - currentSample, pm_gpu_power_telemetry_info); result != CTL_RESULT_SUCCESS) + currentSample, sample); result != CTL_RESULT_SUCCESS) { success = false; IGCL_ERR(result); } if (const auto result = GetVramPowerTelemetryData( - currentSample, pm_gpu_power_telemetry_info); result != CTL_RESULT_SUCCESS) + currentSample, sample); result != CTL_RESULT_SUCCESS) { success = false; IGCL_ERR(result); } if (const auto result = GetFanPowerTelemetryData(currentSample, - pm_gpu_power_telemetry_info); result != CTL_RESULT_SUCCESS) + sample); result != CTL_RESULT_SUCCESS) { success = false; IGCL_ERR(result); } if (const auto result = GetPsuPowerTelemetryData( - currentSample, pm_gpu_power_telemetry_info); result != CTL_RESULT_SUCCESS) + currentSample, sample); result != CTL_RESULT_SUCCESS) { success = false; IGCL_ERR(result); @@ -433,21 +423,20 @@ namespace pwr::intel // Get memory state and bandwidth data if (memoryModules.size() > 0) { GetMemStateTelemetryData(memory_state, - pm_gpu_power_telemetry_info); + sample); GetMemBandwidthData(memory_bandwidth, - pm_gpu_power_telemetry_info); + sample); } // Save and convert the gpu sustained power limit - pm_gpu_power_telemetry_info.gpu_sustained_power_limit_w = + sample.gpu_sustained_power_limit_w = gpu_sustained_power_limit_mw.value_or(0.) / 1000.; if (gpu_sustained_power_limit_mw) { SetTelemetryCapBit(GpuTelemetryCapBits::gpu_sustained_power_limit); } - // Save off the calculated PresentMon power telemetry values. These are - // saved off for clients to extrace out timing information based on QPC - SavePmPowerTelemetryData(pm_gpu_power_telemetry_info); + pmlog_verb(v::tele_gpu)("Gathered telemetry info sample") + .pmwatch(GetName()).pmwatch(ref::DumpStatic(sample)); } // Save off the raw control library data for calculating time delta @@ -914,11 +903,6 @@ namespace pwr::intel } - const PresentMonPowerTelemetryInfo& IntelPowerTelemetryAdapter::GetNewest() const noexcept - { - return *std::prev(history.end()); - } - ctl_result_t IntelPowerTelemetryAdapter::GetPowerTelemetryItemUsage( const ctl_oc_telemetry_item_t& current_telemetry_item, const ctl_oc_telemetry_item_t& previous_telemetry_item, @@ -985,11 +969,4 @@ namespace pwr::intel return CTL_RESULT_SUCCESS; } - void IntelPowerTelemetryAdapter::SavePmPowerTelemetryData(PresentMonPowerTelemetryInfo& info) - { - pmlog_verb(v::tele_gpu)("Saving gathered telemetry info to history").pmwatch(GetName()).pmwatch(ref::DumpStatic(info)); - std::lock_guard lock(historyMutex); - history.Push(info); } - - } \ No newline at end of file diff --git a/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.h b/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.h index e61cf22b..88e503a0 100644 --- a/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.h +++ b/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.h @@ -1,13 +1,11 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #define NOMINMAX #include #include "igcl_api.h" #include "PowerTelemetryAdapter.h" -#include "TelemetryHistory.h" #include "ctlpvttemp_api.h" -#include #include #include @@ -17,9 +15,7 @@ namespace pwr::intel { public: IntelPowerTelemetryAdapter(ctl_device_adapter_handle_t handle); - bool Sample() noexcept override; - std::optional GetClosest(uint64_t qpc) const noexcept override; - const PresentMonPowerTelemetryInfo& GetNewest() const noexcept override; + PresentMonPowerTelemetryInfo Sample() noexcept override; PM_DEVICE_VENDOR GetVendor() const noexcept override; std::string GetName() const noexcept override; uint64_t GetDedicatedVideoMemory() const noexcept override; @@ -38,7 +34,8 @@ namespace pwr::intel ctl_mem_state_t& memory_state, ctl_mem_bandwidth_t& memory_bandwidth, std::optional gpu_sustained_power_limit_mw, - uint64_t qpc); + uint64_t qpc, + PresentMonPowerTelemetryInfo& sample); ctl_result_t EnumerateMemoryModules(); @@ -70,7 +67,6 @@ namespace pwr::intel const ctl_mem_bandwidth_t& mem_bandwidth, PresentMonPowerTelemetryInfo& pm_gpu_power_telemetry_info); - void SavePmPowerTelemetryData(PresentMonPowerTelemetryInfo& pm_gpu_power_telemetry_info); template ctl_result_t SaveTelemetry( const T& power_telemetry, @@ -97,8 +93,6 @@ namespace pwr::intel ctl_device_adapter_properties_t properties{}; std::vector memoryModules; std::vector powerDomains; - mutable std::mutex historyMutex; - TelemetryHistory history{ PowerTelemetryAdapter::defaultHistorySize }; SampleVariantType previousSampleVariant; bool useV1PowerTelemetry = true; bool useNewBandwidthTelemetry = true; @@ -117,4 +111,4 @@ namespace pwr::intel // populated on init, used to calculate fan % std::vector maxFanSpeedsRpm_; }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/ControlLib/IntelPowerTelemetryProvider.h b/IntelPresentMon/ControlLib/IntelPowerTelemetryProvider.h index c69c5d0c..0051aa8d 100644 --- a/IntelPresentMon/ControlLib/IntelPowerTelemetryProvider.h +++ b/IntelPresentMon/ControlLib/IntelPowerTelemetryProvider.h @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #define NOMINMAX @@ -10,7 +10,6 @@ #include "igcl_api.h" #include "PresentMonPowerTelemetry.h" #include "PowerTelemetryProvider.h" -#include "TelemetryHistory.h" namespace pwr::intel { @@ -28,4 +27,4 @@ namespace pwr::intel ctl_api_handle_t apiHandle = nullptr; std::vector> adapterPtrs; }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.cpp b/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.cpp index 830fe025..f0de61b4 100644 --- a/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.cpp +++ b/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #define NOMINMAX #include @@ -29,11 +29,6 @@ namespace pwr::nv - const PresentMonPowerTelemetryInfo& NvidiaPowerTelemetryAdapter::GetNewest() const noexcept - { - return *std::prev(history.end()); - } - uint64_t NvidiaPowerTelemetryAdapter::GetDedicatedVideoMemory() const noexcept { uint64_t video_mem_size = 0; nvmlMemory_t mem{}; @@ -55,7 +50,7 @@ namespace pwr::nv return 0.f; } - bool NvidiaPowerTelemetryAdapter::Sample() noexcept + PresentMonPowerTelemetryInfo NvidiaPowerTelemetryAdapter::Sample() noexcept { LARGE_INTEGER qpc; QueryPerformanceCounter(&qpc); @@ -197,17 +192,7 @@ namespace pwr::nv } } - // insert telemetry into history - std::lock_guard lock{ historyMutex }; - history.Push(info); - - return true; - } - - std::optional NvidiaPowerTelemetryAdapter::GetClosest(uint64_t qpc) const noexcept - { - std::lock_guard lock{ historyMutex }; - return history.GetNearest(qpc); + return info; } PM_DEVICE_VENDOR NvidiaPowerTelemetryAdapter::GetVendor() const noexcept diff --git a/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.h b/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.h index 41ab3057..ba35e438 100644 --- a/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.h +++ b/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.h @@ -1,9 +1,7 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #include "PowerTelemetryAdapter.h" -#include "TelemetryHistory.h" -#include #include #include "NvapiWrapper.h" #include "NvmlWrapper.h" @@ -18,9 +16,7 @@ namespace pwr::nv const NvmlWrapper* pNvml, NvPhysicalGpuHandle hGpuNvapi, std::optional hGpuNvml); - bool Sample() noexcept override; - std::optional GetClosest(uint64_t qpc) const noexcept override; - const PresentMonPowerTelemetryInfo& GetNewest() const noexcept override; + PresentMonPowerTelemetryInfo Sample() noexcept override; PM_DEVICE_VENDOR GetVendor() const noexcept override; std::string GetName() const noexcept override; uint64_t GetDedicatedVideoMemory() const noexcept override; @@ -34,8 +30,6 @@ namespace pwr::nv NvPhysicalGpuHandle hNvapi; std::optional hNvml; std::string name = "Unknown Adapter Name"; - mutable std::mutex historyMutex; - TelemetryHistory history{ PowerTelemetryAdapter::defaultHistorySize }; bool useNvmlTemperature = false; }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/ControlLib/NvidiaPowerTelemetryProvider.h b/IntelPresentMon/ControlLib/NvidiaPowerTelemetryProvider.h index 5150ebda..662c1022 100644 --- a/IntelPresentMon/ControlLib/NvidiaPowerTelemetryProvider.h +++ b/IntelPresentMon/ControlLib/NvidiaPowerTelemetryProvider.h @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #define NOMINMAX @@ -9,7 +9,6 @@ #include #include "PresentMonPowerTelemetry.h" #include "PowerTelemetryProvider.h" -#include "TelemetryHistory.h" #include "NvapiWrapper.h" #include "NvmlWrapper.h" @@ -27,4 +26,4 @@ namespace pwr::nv NvmlWrapper nvml; std::vector> adapterPtrs; }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/ControlLib/PowerTelemetryAdapter.h b/IntelPresentMon/ControlLib/PowerTelemetryAdapter.h index 3893b404..a932c257 100644 --- a/IntelPresentMon/ControlLib/PowerTelemetryAdapter.h +++ b/IntelPresentMon/ControlLib/PowerTelemetryAdapter.h @@ -1,10 +1,9 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #include #include -#include #include #include "PresentMonPowerTelemetry.h" #include "../PresentMonAPI2/PresentMonAPI.h" @@ -18,9 +17,7 @@ namespace pwr using SetTelemetryCapBitset = std::bitset(GpuTelemetryCapBits::gpu_telemetry_count)>; // functions virtual ~PowerTelemetryAdapter() = default; - virtual bool Sample() noexcept = 0; - virtual std::optional GetClosest(uint64_t qpc) const noexcept = 0; - virtual const PresentMonPowerTelemetryInfo& GetNewest() const noexcept = 0; + virtual PresentMonPowerTelemetryInfo Sample() noexcept = 0; virtual PM_DEVICE_VENDOR GetVendor() const noexcept = 0; virtual std::string GetName() const noexcept = 0; virtual uint64_t GetDedicatedVideoMemory() const noexcept = 0; @@ -29,11 +26,8 @@ namespace pwr void SetTelemetryCapBit(GpuTelemetryCapBits telemetryCapBit) noexcept; SetTelemetryCapBitset GetPowerTelemetryCapBits(); bool HasTelemetryCapBit(GpuTelemetryCapBits bit) const; - // constants - static constexpr size_t defaultHistorySize = 300; - private: // data SetTelemetryCapBitset gpuTelemetryCapBits_{}; }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/ControlLib/TelemetryHistory.h b/IntelPresentMon/ControlLib/TelemetryHistory.h deleted file mode 100644 index e573772f..00000000 --- a/IntelPresentMon/ControlLib/TelemetryHistory.h +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once -#include "PresentMonPowerTelemetry.h" -#include "PowerTelemetryProvider.h" -#include - -namespace pwr -{ - template - class TelemetryHistory - { - public: - class ConstIterator - { - public: - using iterator_category = std::random_access_iterator_tag; - using value_type = T; - using reference = value_type&; - using pointer = value_type*; - using difference_type = ptrdiff_t; - - ConstIterator() = delete; - ~ConstIterator() = default; - ConstIterator(const TelemetryHistory* pContainer, size_t unwrapped_offset) noexcept : pContainer{ pContainer }, unwrapped_offset{ unwrapped_offset } {} - ConstIterator(const ConstIterator& rhs) noexcept : pContainer{ rhs.pContainer }, unwrapped_offset{ rhs.unwrapped_offset } {} - ConstIterator& operator=(const ConstIterator& rhs) noexcept - { - // Self-assignment check - if (this != &rhs) { - pContainer = rhs.pContainer; - unwrapped_offset = rhs.unwrapped_offset; - } - return *this; - } - ConstIterator& operator+=(difference_type rhs) noexcept - { - unwrapped_offset += rhs; - return *this; - } - ConstIterator& operator-=(difference_type rhs) noexcept - { - unwrapped_offset -= rhs; - return *this; - } - const value_type& operator*() const noexcept - { - return pContainer->buffer[WrapIndex_(unwrapped_offset + pContainer->indexBegin)]; - } - const value_type* operator->() const noexcept - { - return &**this; - } - const value_type& operator[](size_t rhs) const noexcept - { - return pContainer->buffer[WrapIndex_(unwrapped_offset + rhs + pContainer->indexBegin)]; - } - - ConstIterator& operator++() noexcept - { - ++unwrapped_offset; - return *this; - } - ConstIterator& operator--() noexcept - { - --unwrapped_offset; - return *this; - } - ConstIterator operator++(int) noexcept { ConstIterator tmp(*this); ++(*this); return tmp; } - ConstIterator operator--(int) noexcept { ConstIterator tmp(*this); --(*this); return tmp; } - difference_type operator-(const ConstIterator& rhs) const noexcept - { - return difference_type(unwrapped_offset) - difference_type(rhs.unwrapped_offset); - } - ConstIterator operator+(difference_type rhs) const noexcept - { - auto dup = *this; - return dup += rhs; - } - ConstIterator operator-(difference_type rhs) const noexcept - { - auto dup = *this; - return dup -= rhs; - } - - bool operator==(const ConstIterator& rhs) const noexcept { return unwrapped_offset == rhs.unwrapped_offset; } - bool operator!=(const ConstIterator& rhs) const noexcept { return unwrapped_offset != rhs.unwrapped_offset; } - bool operator>(const ConstIterator& rhs) const noexcept { return unwrapped_offset > rhs.unwrapped_offset; } - bool operator<(const ConstIterator& rhs) const noexcept { return unwrapped_offset < rhs.unwrapped_offset; } - bool operator>=(const ConstIterator& rhs) const noexcept { return unwrapped_offset >= rhs.unwrapped_offset; } - bool operator<=(const ConstIterator& rhs) const noexcept { return unwrapped_offset <= rhs.unwrapped_offset; } - private: - // functions - size_t GetSize_() const noexcept - { - return pContainer->buffer.size(); - } - // only wraps positive excursions - size_t WrapIndex_(size_t unwrapped) const noexcept - { - return unwrapped % GetSize_(); - } - - // data - const TelemetryHistory* pContainer; - size_t unwrapped_offset; - }; - - TelemetryHistory(size_t size) noexcept; - void Push(const T& info) noexcept; - std::optional GetNearest(uint64_t qpc) const noexcept; - ConstIterator begin() const noexcept; - ConstIterator end() const noexcept; - private: - std::vector buffer; - // indexBegin==buffer.size initial condition is necessary to indicate an empty container - // (begin==end would normally indicate empty, but in a cyclic container this is ambiguous) - size_t indexBegin; - size_t indexEnd = 0; - }; - - template - TelemetryHistory::TelemetryHistory(size_t size) noexcept - : - indexBegin{ size } - { - buffer.resize(size); - } - - template - void TelemetryHistory::Push(const T& info) noexcept - { - // indexBegin set to buffer size only in initial state 0 entries pushed - if (indexBegin == buffer.size()) - { - buffer[0] = info; - indexBegin = 0; - indexEnd = 1; - } - else if (indexBegin == indexEnd) - { - // full circle buffer, newest overwrites oldest - buffer[indexEnd] = info; - if (++indexEnd >= buffer.size()) - { - indexEnd = 0; - } - indexBegin = indexEnd; - } - else - { - // buffer still filling - buffer[indexEnd] = info; - if (++indexEnd >= buffer.size()) - { - indexEnd = 0; - } - } - } - - template - std::optional TelemetryHistory::GetNearest(uint64_t qpc) const noexcept - { - const auto begin = this->begin(); - const auto end = this->end(); - - // return nothing if history empty - if (begin == end) return {}; - - const auto last = end - 1; - - // if outside the qpc history range return the closest values - if (qpc > last->qpc) { - return *last; - } else if (qpc < begin->qpc) { - return *begin; - } - - // find lowest not less than query qpc - const auto i = std::lower_bound(begin, end, qpc, [](const auto& a, auto b) { - return a.qpc < b; - }); - - // if we're right on the money, no need to find closest among 2 neighboring - if (i->qpc == qpc) return { *i }; - - const auto distanceToLower = qpc - std::prev(i)->qpc; - const auto distanceToUpper = i->qpc - qpc; - - return distanceToUpper <= distanceToLower ? - std::optional{ *i } : - std::optional{ *std::prev(i) }; - } - - template - typename TelemetryHistory::ConstIterator TelemetryHistory::begin() const noexcept - { - return ConstIterator{ this, 0 }; - } - - template - typename TelemetryHistory::ConstIterator TelemetryHistory::end() const noexcept - { - if (indexBegin == indexEnd) // begin/end indices locked mean container full - { - return ConstIterator{ this, buffer.size() }; - } - else if (indexBegin == buffer.size()) // special case for empty container - { - return ConstIterator{ this, 0 }; - } - else - { - return ConstIterator{ this, indexEnd }; // not yet cycling (not ful capacity) - } - } -} \ No newline at end of file diff --git a/IntelPresentMon/ControlLib/WmiCpu.cpp b/IntelPresentMon/ControlLib/WmiCpu.cpp index c1d478de..34edc596 100644 --- a/IntelPresentMon/ControlLib/WmiCpu.cpp +++ b/IntelPresentMon/ControlLib/WmiCpu.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #include #include "WmiCpu.h" @@ -48,7 +48,7 @@ WmiCpu::WmiCpu() { // Most counters require two sample values to display a formatted value. // PDH stores the current sample value and the previously collected // sample value. This call retrieves the first value that will be used - // by PdhGetFormattedCounterValue in the Sample() call. + // by PdhGetFormattedCounterValue in the next data collection. if (const auto result = PdhCollectQueryData(query_.get()); result != ERROR_SUCCESS) { throw std::runtime_error{ @@ -73,27 +73,26 @@ WmiCpu::WmiCpu() { } -const CpuTelemetryInfo& WmiCpu::GetNewest() const noexcept -{ - return *std::prev(history_.end()); -} - -bool WmiCpu::Sample() noexcept { +CpuTelemetryInfo WmiCpu::Sample() noexcept { DWORD counter_type; LARGE_INTEGER qpc; QueryPerformanceCounter(&qpc); - if (qpc.QuadPart < next_sample_qpc_.QuadPart) { - return true; - } + const bool should_collect = qpc.QuadPart >= next_sample_qpc_.QuadPart; CpuTelemetryInfo info{ .qpc = (uint64_t)qpc.QuadPart, }; - if (const auto result = PdhCollectQueryData(query_.get()); - result != ERROR_SUCCESS) { - return false; + if (should_collect) { + if (const auto result = PdhCollectQueryData(query_.get()); + result != ERROR_SUCCESS) { + LOG(INFO) << "PdhCollectQueryData failed. Result:" << result << std::endl; + } else { + // Update the next sample qpc based on the current sample qpc + // and adding in the frequency + next_sample_qpc_.QuadPart = qpc.QuadPart + frequency_.QuadPart; + } } // Sample cpu clock. This is an approximation using the frequency and then @@ -139,21 +138,7 @@ bool WmiCpu::Sample() noexcept { } } - // insert telemetry into history - std::lock_guard lock{history_mutex_}; - history_.Push(info); - - // Update the next sample qpc based on the current sample qpc - // and adding in the frequency - next_sample_qpc_.QuadPart = qpc.QuadPart + frequency_.QuadPart; - - return true; + return info; } -std::optional WmiCpu::GetClosest(uint64_t qpc) - const noexcept { - std::lock_guard lock{history_mutex_}; - return history_.GetNearest(qpc); } - -} \ No newline at end of file diff --git a/IntelPresentMon/ControlLib/WmiCpu.h b/IntelPresentMon/ControlLib/WmiCpu.h index 6d8370c8..1fb67b42 100644 --- a/IntelPresentMon/ControlLib/WmiCpu.h +++ b/IntelPresentMon/ControlLib/WmiCpu.h @@ -1,13 +1,10 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #define NOMINMAX #include #include #include "CpuTelemetry.h" -#include "TelemetryHistory.h" -#include -#include namespace pwr::cpu::wmi { @@ -18,15 +15,11 @@ struct PDHQueryDeleter { class WmiCpu : public CpuTelemetry { public: WmiCpu(); - bool Sample() noexcept override; - const CpuTelemetryInfo& GetNewest() const noexcept override; - std::optional GetClosest( - uint64_t qpc) const noexcept override; + CpuTelemetryInfo Sample() noexcept override; // types class NonGraphicsDeviceException : public std::exception {}; private: - // data std::unique_ptr, PDHQueryDeleter> query_; HCOUNTER processor_frequency_counter_; @@ -35,9 +28,6 @@ class WmiCpu : public CpuTelemetry { LARGE_INTEGER next_sample_qpc_ = {}; LARGE_INTEGER frequency_ = {}; std::string cpu_name_; - - mutable std::mutex history_mutex_; - TelemetryHistory history_{CpuTelemetry::defaultHistorySize}; }; -} // namespace pwr::cpu::wmi \ No newline at end of file +} // namespace pwr::cpu::wmi diff --git a/IntelPresentMon/PresentMonService/PMMainThread.cpp b/IntelPresentMon/PresentMonService/PMMainThread.cpp index dfcf2713..bb0d086e 100644 --- a/IntelPresentMon/PresentMonService/PMMainThread.cpp +++ b/IntelPresentMon/PresentMonService/PMMainThread.cpp @@ -344,7 +344,7 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, } // we first wait for a client control connection before populating telemetry container - // after populating, we sample each adapter to gather availability information + // after populating, we refresh each adapter to gather availability information // this is deferred until client connection in order to increase the probability that // telemetry metric availability is accurately assessed { @@ -356,9 +356,9 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, pmon::util::QpcTimer timer; ptc->Repopulate(); for (auto&& [i, adapter] : ptc->GetPowerTelemetryAdapters() | vi::enumerate) { - // sample 2x here as workaround/kludge because Intel provider misreports 1st sample - adapter->Sample(); + // refresh 2x here as workaround/kludge because Intel provider misreports 1st sample adapter->Sample(); + const auto sample = adapter->Sample(); pComms->RegisterGpuDevice(adapter->GetVendor(), adapter->GetName(), ipc::intro::ConvertBitset(adapter->GetPowerTelemetryCapBits())); // after registering, we know that at least the store is available even @@ -374,7 +374,6 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, // TODO: make this fully static // infer number of fans by the size of the telemetry ring array for fan speed const auto nFans = gpuStore.telemetryData.ArraySize(PM_METRIC_GPU_FAN_SPEED); - auto& sample = adapter->GetNewest(); for (size_t i = 0; i < nFans; i++) { gpuStore.statics.maxFanSpeedRpm.push_back(sample.max_fan_speed_rpm[i]); } @@ -439,10 +438,8 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, auto& adapters = ptc->GetPowerTelemetryAdapters(); for (size_t idx = 0; idx < adapters.size(); ++idx) { auto& adapter = adapters[idx]; - adapter->Sample(); - // Get the newest sample from the provider - const auto& sample = adapter->GetNewest(); + const auto sample = adapter->Sample(); // Retrieve the matching GPU store. auto& store = pComms->GetGpuDataStore(uint32_t(idx + 1)); @@ -537,11 +534,10 @@ void CpuTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, ipc::Ser } while (!WaitAnyEventFor(0ms, srv->GetServiceStopHandle())) { // TODO:streamer replace this flow with a call that populates rings of a store - cpu->Sample(); // placeholder routine shim to translate cpu tranfer struct into rings // replace with a direct mapping on PM_METRIC that obviates the switch auto& store = pComms->GetSystemDataStore(); - auto& sample = cpu->GetNewest(); + const auto sample = cpu->Sample(); for (auto&& [metric, ringVariant] : store.telemetryData.Rings()) { // all cpu telemetry is double auto& ringVect = std::get>(ringVariant); @@ -700,7 +696,7 @@ void PresentMonMainThread(Service* const pSvc) if (cpu) { pm.SetCpu(cpu); - // sample once to populate the cap bits + // refresh once to populate the cap bits cpu->Sample(); // determine vendor based on device name // TODO: move this logic either into system (CPU) provider or From fadc8eb24ceccdf42769daecd1b2acb2d80beb91 Mon Sep 17 00:00:00 2001 From: Chili Date: Sun, 8 Feb 2026 05:13:58 +0900 Subject: [PATCH 188/205] remove legacy support cli/paths and add metric usage updates from mid --- .../InterimBroadcasterTests.cpp | 13 +- .../PresentMonMiddleware/Middleware.cpp | 62 +++++++- .../PresentMonMiddleware/Middleware.h | 15 +- .../PresentMonService/CliOptions.h | 2 - .../PresentMonService/PMMainThread.cpp | 134 ++++++------------ 5 files changed, 121 insertions(+), 105 deletions(-) diff --git a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp index 49c534b2..ac50e2fb 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp +++ b/IntelPresentMon/PresentMonAPI2Tests/InterimBroadcasterTests.cpp @@ -135,7 +135,7 @@ namespace InterimBroadcasterTests public: TEST_METHOD_INITIALIZE(Setup) { - fixture_.Setup({ "--new-telemetry-activation"s }); + fixture_.Setup(); } TEST_METHOD_CLEANUP(Cleanup) { @@ -353,7 +353,7 @@ namespace InterimBroadcasterTests public: TEST_METHOD_INITIALIZE(Setup) { - fixture_.Setup({"--new-telemetry-activation"s}); + fixture_.Setup(); } TEST_METHOD_CLEANUP(Cleanup) { @@ -582,7 +582,7 @@ namespace InterimBroadcasterTests public: TEST_METHOD_INITIALIZE(Setup) { - fixture_.Setup({ "--new-telemetry-activation"s }); + fixture_.Setup(); } TEST_METHOD_CLEANUP(Cleanup) { @@ -993,8 +993,7 @@ namespace InterimBroadcasterTests TEST_METHOD_INITIALIZE(Setup) { fixture_.Setup({ - "--etl-test-file"s, R"(..\..\Tests\AuxData\Data\P00HeaWin2080.etl)"s, - "--disable-legacy-backpressure"s + "--etl-test-file"s, R"(..\..\Tests\AuxData\Data\P00HeaWin2080.etl)"s }); } TEST_METHOD_CLEANUP(Cleanup) @@ -1109,7 +1108,6 @@ namespace InterimBroadcasterTests { fixture_.Setup({ "--etl-test-file"s, R"(..\..\Tests\AuxData\Data\P01TimeSpyDemoFS2080.etl)"s, - "--disable-legacy-backpressure"s, "--frame-ring-samples"s, "32"s, }); } @@ -1169,8 +1167,7 @@ namespace InterimBroadcasterTests TEST_METHOD_INITIALIZE(Setup) { fixture_.Setup({ - "--etl-test-file"s, R"(..\..\Tests\AuxData\Data\P01TimeSpyDemoFS2080.etl)"s, - "--disable-legacy-backpressure"s + "--etl-test-file"s, R"(..\..\Tests\AuxData\Data\P01TimeSpyDemoFS2080.etl)"s }); } TEST_METHOD_CLEANUP(Cleanup) diff --git a/IntelPresentMon/PresentMonMiddleware/Middleware.cpp b/IntelPresentMon/PresentMonMiddleware/Middleware.cpp index a4c169bf..abb2c40e 100644 --- a/IntelPresentMon/PresentMonMiddleware/Middleware.cpp +++ b/IntelPresentMon/PresentMonMiddleware/Middleware.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "../CommonUtilities/mt/Thread.h" #include "../CommonUtilities/log/Log.h" #include "../CommonUtilities/Qpc.h" @@ -141,7 +142,18 @@ namespace pmon::mid { pmlog_dbg("Registering dynamic query").pmwatch(queryElements.size()).pmwatch(windowSizeMs).pmwatch(metricOffsetMs); const auto qpcPeriod = util::GetTimestampPeriodSeconds(); - return new PM_DYNAMIC_QUERY{ queryElements, windowSizeMs, metricOffsetMs, qpcPeriod, *pComms_, *this }; + auto* query = new PM_DYNAMIC_QUERY{ queryElements, windowSizeMs, metricOffsetMs, qpcPeriod, *pComms_, *this }; + RegisterMetricUsage_(query, queryElements); + return query; + } + + void Middleware::FreeDynamicQuery(const PM_DYNAMIC_QUERY* pQuery) + { + if (pQuery == nullptr) { + return; + } + UnregisterMetricUsage_(pQuery); + delete pQuery; } void Middleware::PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, @@ -203,11 +215,13 @@ namespace pmon::mid { auto pQuery = new PM_FRAME_QUERY{ queryElements, *this, *pComms_, GetIntrospectionRoot_() }; blobSize = (uint32_t)pQuery->GetBlobSize(); + RegisterMetricUsage_(pQuery, queryElements); return pQuery; } void mid::Middleware::FreeFrameEventQuery(const PM_FRAME_QUERY* pQuery) { + UnregisterMetricUsage_(pQuery); delete const_cast(pQuery); } @@ -256,4 +270,50 @@ namespace pmon::mid return *it->second; } } + + void Middleware::RegisterMetricUsage_(const void* queryHandle, std::span queryElements) + { + if (queryHandle == nullptr) { + pmlog_warn("Attempting to register metric usage with null query handle"); + return; + } + std::vector keys; + keys.reserve(queryElements.size()); + for (const auto& element : queryElements) { + keys.push_back(QueryMetricKey{ + .metric = element.metric, + .deviceId = element.deviceId, + .arrayIndex = element.arrayIndex, + }); + } + queryMetricUsage_[queryHandle] = std::move(keys); + UpdateMetricUsage_(); + } + + void Middleware::UnregisterMetricUsage_(const void* queryHandle) + { + if (queryHandle == nullptr) { + pmlog_warn("Attempting to unregister metric usage with null query handle"); + return; + } + if (queryMetricUsage_.erase(queryHandle) > 0) { + UpdateMetricUsage_(); + } + } + + void Middleware::UpdateMetricUsage_() + { + std::unordered_set usage; + const auto& introRoot = GetIntrospectionRoot_(); + for (const auto& [handle, elements] : queryMetricUsage_) { + for (const auto& element : elements) { + usage.insert(svc::acts::MetricUse{ + .metricId = element.metric, + .deviceId = element.deviceId, + .arrayIdx = element.arrayIndex, + }); + } + } + pActionClient_->DispatchSync(svc::acts::ReportMetricUse::Params{ std::move(usage) }); + } } diff --git a/IntelPresentMon/PresentMonMiddleware/Middleware.h b/IntelPresentMon/PresentMonMiddleware/Middleware.h index 5bd7f171..a027d2de 100644 --- a/IntelPresentMon/PresentMonMiddleware/Middleware.h +++ b/IntelPresentMon/PresentMonMiddleware/Middleware.h @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include "../IntelPresentMon/CommonUtilities/mc/SwapChainState.h" namespace pmapi::intro @@ -32,7 +34,7 @@ namespace pmon::mid void SetEtwFlushPeriod(std::optional periodMs); void FlushFrames(uint32_t processId); PM_DYNAMIC_QUERY* RegisterDynamicQuery(std::span queryElements, double windowSizeMs, double metricOffsetMs); - void FreeDynamicQuery(const PM_DYNAMIC_QUERY* pQuery) { (void)pQuery; } + void FreeDynamicQuery(const PM_DYNAMIC_QUERY* pQuery); void PollDynamicQuery(const PM_DYNAMIC_QUERY* pQuery, uint32_t processId, uint8_t* pBlob, uint32_t* numSwapChains, std::optional nowTimestamp = {}); void PollStaticQuery(const PM_QUERY_ELEMENT& element, uint32_t processId, uint8_t* pBlob); @@ -43,9 +45,18 @@ namespace pmon::mid uint32_t StartEtlLogging(); std::string FinishEtlLogging(uint32_t etlLogSessionHandle); private: + struct QueryMetricKey + { + PM_METRIC metric; + uint32_t deviceId; + uint32_t arrayIndex; + }; // functions const pmapi::intro::Root& GetIntrospectionRoot_(); FrameMetricsSource& GetFrameMetricSource_(uint32_t pid) const; + void RegisterMetricUsage_(const void* queryHandle, std::span queryElements); + void UnregisterMetricUsage_(const void* queryHandle); + void UpdateMetricUsage_(); // data // action client connection to service RPC std::shared_ptr pActionClient_; @@ -55,5 +66,7 @@ namespace pmon::mid std::unique_ptr pIntroRoot_; // Frame metrics sources mapped to process id std::map> frameMetricsSources_; + // Query handles mapped to their metric usage keys + std::unordered_map> queryMetricUsage_; }; } diff --git a/IntelPresentMon/PresentMonService/CliOptions.h b/IntelPresentMon/PresentMonService/CliOptions.h index f99d5aa1..0ea7e091 100644 --- a/IntelPresentMon/PresentMonService/CliOptions.h +++ b/IntelPresentMon/PresentMonService/CliOptions.h @@ -43,8 +43,6 @@ namespace clio private: Group gt_{ this, "Testing", "Automated testing features" }; public: Flag enableTestControl{ this, "--enable-test-control", "Enable test control over stdio" }; - Flag disableLegacyBackpressure{ this, "--disable-legacy-backpressure", "Disable backpressure to enable testing of new IPC" }; - Flag newTelemetryActivation{ this, "--new-telemetry-activation", "Enable the new telemetry activation via metric usage tracking" }; static constexpr const char* description = "Intel PresentMon service for frame and system performance measurement"; static constexpr const char* name = "PresentMonService.exe"; diff --git a/IntelPresentMon/PresentMonService/PMMainThread.cpp b/IntelPresentMon/PresentMonService/PMMainThread.cpp index bb0d086e..7efc84a0 100644 --- a/IntelPresentMon/PresentMonService/PMMainThread.cpp +++ b/IntelPresentMon/PresentMonService/PMMainThread.cpp @@ -331,7 +331,7 @@ static void PopulateGpuTelemetryRings_( void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, - PowerTelemetryContainer* const ptc, ipc::ServiceComms* const pComms, bool newActivation) + PowerTelemetryContainer* const ptc, ipc::ServiceComms* const pComms) { using util::win::WaitAnyEvent; using util::win::WaitAnyEventFor; @@ -387,41 +387,25 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, { IntervalWaiter waiter{ 0.016 }; while (true) { - if (newActivation) { - pmlog_dbg("(re)starting gpu idle wait (new)"); - if (WaitAnyEvent(pm->GetDeviceUsageEvent(), srv->GetServiceStopHandle()) == 1) { - pmlog_dbg("gpu telemetry received exit code, thread exiting"); - return; - } - else { - // if any of our gpu telemetry devices are active enter polling loop - bool hasActive = false; - for (auto&& [i, ad] : ptc->GetPowerTelemetryAdapters() | vi::enumerate) { - const auto deviceId = uint32_t(i) + 1; - if (pm->CheckDeviceMetricUsage(deviceId)) { - pmlog_dbg("detected gpu active").pmwatch(deviceId); - hasActive = true; - break; - } - } - if (!hasActive) { - pmlog_dbg("received device usage event, but no gpu tele device was active"); - continue; - } - } + pmlog_dbg("(re)starting gpu idle wait"); + if (WaitAnyEvent(pm->GetDeviceUsageEvent(), srv->GetServiceStopHandle()) == 1) { + pmlog_dbg("gpu telemetry received exit code, thread exiting"); + return; } else { - // TODO: remove legacy branch here - pmlog_dbg("(re)starting gpu idle wait (legacy)"); - const HANDLE events[]{ - pm->GetStreamingStartHandle(), - srv->GetServiceStopHandle(), - }; - auto waitResult = WaitForMultipleObjects((DWORD)std::size(events), events, FALSE, INFINITE); - // TODO: check for wait result error - // if events[1] was signalled, that means service is stopping so exit thread - if ((waitResult - WAIT_OBJECT_0) == 1) { - return; + // if any of our gpu telemetry devices are active enter polling loop + bool hasActive = false; + for (auto&& [i, ad] : ptc->GetPowerTelemetryAdapters() | vi::enumerate) { + const auto deviceId = uint32_t(i) + 1; + if (pm->CheckDeviceMetricUsage(deviceId)) { + pmlog_dbg("detected gpu active").pmwatch(deviceId); + hasActive = true; + break; + } + } + if (!hasActive) { + pmlog_dbg("received device usage event, but no gpu tele device was active"); + continue; } } // otherwise we assume streaming has started and we begin the polling loop @@ -451,26 +435,16 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, waiter.SetInterval(pm->GetGpuTelemetryPeriod() / 1000.); waiter.Wait(); // conditions for ending active poll and returning to idle state - if (newActivation) { - // go dormant if no gpu devices are in use - bool anyUsed = false; - for (size_t idx = 0; idx < adapters.size(); ++idx) { - if (pm->CheckDeviceMetricUsage(uint32_t(idx + 1))) { - anyUsed = true; - break; - } - } - if (!anyUsed) { + // go dormant if no gpu devices are in use + bool anyUsed = false; + for (size_t idx = 0; idx < adapters.size(); ++idx) { + if (pm->CheckDeviceMetricUsage(uint32_t(idx + 1))) { + anyUsed = true; break; } } - else { - // go dormant if there are no active streams left - // TODO: consider race condition here if client stops and starts streams rapidly - // TODO: remove this legacy branch - if (!pm->HasLiveTargets()) { - break; - } + if (!anyUsed) { + break; } } } @@ -482,7 +456,7 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, } void CpuTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, ipc::ServiceComms* pComms, - pwr::cpu::CpuTelemetry* const cpu, bool newActivation) noexcept + pwr::cpu::CpuTelemetry* const cpu) noexcept { using util::win::WaitAnyEvent; using util::win::WaitAnyEventFor; @@ -501,35 +475,19 @@ void CpuTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, ipc::Ser IntervalWaiter waiter{ 0.016 }; while (true) { - if (newActivation) { - pmlog_dbg("(re)starting system idle wait (new)"); - if (WaitAnyEvent(pm->GetDeviceUsageEvent(), srv->GetServiceStopHandle()) == 1) { - pmlog_dbg("system telemetry received exit code, thread exiting"); - return; - } - else { - // if system telemetry metrics active enter active polling loop - if (pm->CheckDeviceMetricUsage(ipc::kSystemDeviceId)) { - pmlog_dbg("detected system active"); - } - else { - pmlog_dbg("received device usage event, but system tele device was not active"); - continue; - } - } + pmlog_dbg("(re)starting system idle wait"); + if (WaitAnyEvent(pm->GetDeviceUsageEvent(), srv->GetServiceStopHandle()) == 1) { + pmlog_dbg("system telemetry received exit code, thread exiting"); + return; } else { - // TODO: remove legacy branch here - pmlog_dbg("(re)starting system idle wait (legacy)"); - const HANDLE events[]{ - pm->GetStreamingStartHandle(), - srv->GetServiceStopHandle(), - }; - auto waitResult = WaitForMultipleObjects((DWORD)std::size(events), events, FALSE, INFINITE); - // TODO: check for wait result error - // if events[1] was signalled, that means service is stopping so exit thread - if ((waitResult - WAIT_OBJECT_0) == 1) { - return; + // if system telemetry metrics active enter active polling loop + if (pm->CheckDeviceMetricUsage(ipc::kSystemDeviceId)) { + pmlog_dbg("detected system active"); + } + else { + pmlog_dbg("received device usage event, but system tele device was not active"); + continue; } } while (!WaitAnyEventFor(0ms, srv->GetServiceStopHandle())) { @@ -567,18 +525,8 @@ void CpuTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, ipc::Ser waiter.SetInterval(pm->GetGpuTelemetryPeriod() / 1000.); waiter.Wait(); // conditions for ending active poll and returning to idle state - if (newActivation) { - if (!pm->CheckDeviceMetricUsage(ipc::kSystemDeviceId)) { - break; - } - } - else { - // go dormant if there are no active streams left - // TODO: consider race condition here if client stops and starts streams rapidly - // TODO: remove this legacy branch - if (!pm->HasLiveTargets()) { - break; - } + if (!pm->CheckDeviceMetricUsage(ipc::kSystemDeviceId)) { + break; } } } @@ -675,7 +623,7 @@ void PresentMonMainThread(Service* const pSvc) try { gpuTelemetryThread = std::jthread{ PowerTelemetryThreadEntry_, pSvc, &pm, &ptc, - pComms.get(), opt.newTelemetryActivation }; + pComms.get() }; } catch (...) { LOG(ERROR) << "failed creating gpu(power) telemetry thread" << std::endl; @@ -725,7 +673,7 @@ void PresentMonMainThread(Service* const pSvc) systemStore.statics.cpuPowerLimit = cpu->GetCpuPowerLimit(); systemStore.statics.cpuVendor = vendor; cpuTelemetryThread = std::jthread{ CpuTelemetryThreadEntry_, pSvc, &pm, pComms.get(), - cpu.get(), opt.newTelemetryActivation }; + cpu.get() }; } else { // We were unable to determine the cpu. pComms->RegisterCpuDevice(PM_DEVICE_VENDOR_UNKNOWN, "UNKNOWN_CPU", From 5dbe16bb0757c9d4271816ee32d9c75a38f30c3b Mon Sep 17 00:00:00 2001 From: Chili Date: Sun, 8 Feb 2026 06:27:05 +0900 Subject: [PATCH 189/205] remove SelectAdapter; adapter idx => id everywhere; poll active gpus --- .../source/util/KernelActionRegistration.cpp | 3 +- .../ControlLib/AmdPowerTelemetryAdapter.cpp | 5 +- .../ControlLib/AmdPowerTelemetryAdapter.h | 2 +- .../ControlLib/AmdPowerTelemetryProvider.cpp | 8 +-- .../ControlLib/AmdPowerTelemetryProvider.h | 3 +- .../ControlLib/DeviceIdAllocator.h | 24 +++++++++ .../ControlLib/IntelPowerTelemetryAdapter.cpp | 3 +- .../ControlLib/IntelPowerTelemetryAdapter.h | 2 +- .../IntelPowerTelemetryProvider.cpp | 8 +-- .../ControlLib/IntelPowerTelemetryProvider.h | 3 +- .../NvidiaPowerTelemetryAdapter.cpp | 2 + .../ControlLib/NvidiaPowerTelemetryAdapter.h | 1 + .../NvidiaPowerTelemetryProvider.cpp | 10 ++-- .../ControlLib/NvidiaPowerTelemetryProvider.h | 3 +- .../ControlLib/PowerTelemetryAdapter.cpp | 12 ++++- .../ControlLib/PowerTelemetryAdapter.h | 10 +++- .../PowerTelemetryProviderFactory.cpp | 12 ++--- .../PowerTelemetryProviderFactory.h | 7 +-- IntelPresentMon/Core/source/kernel/Kernel.cpp | 13 +---- IntelPresentMon/Core/source/kernel/Kernel.h | 5 +- .../Core/source/pmon/PresentMon.cpp | 27 ++++------ IntelPresentMon/Core/source/pmon/PresentMon.h | 4 +- .../Interprocess/source/Interprocess.cpp | 4 +- .../Interprocess/source/Interprocess.h | 2 +- .../KernelProcess/KernelProcess.vcxproj | 5 +- .../KernelProcess.vcxproj.filters | 5 +- .../KernelProcess/kact/AllActions.h | 5 +- .../KernelProcess/kact/SetAdapter.h | 51 ------------------ .../PresentMonAPI2Tests/TestCommands.h | 3 +- .../ActionExecutionContext.h | 1 - .../PresentMonService/AllActions.h | 1 - .../PresentMonService/PMMainThread.cpp | 32 ++++++----- .../PowerTelemetryContainer.cpp | 6 +-- .../PowerTelemetryContainer.h | 6 ++- .../PresentMonService/PresentMon.cpp | 6 --- .../PresentMonService/PresentMon.h | 1 - .../PresentMonService.vcxproj | 1 - .../PresentMonService.vcxproj.filters | 5 +- .../PresentMonService/PresentMonSession.cpp | 11 ---- .../PresentMonService/PresentMonSession.h | 4 -- .../acts/EnumerateAdapters.h | 9 ++-- .../PresentMonService/acts/SelectAdapter.h | 54 ------------------- 42 files changed, 132 insertions(+), 247 deletions(-) create mode 100644 IntelPresentMon/ControlLib/DeviceIdAllocator.h delete mode 100644 IntelPresentMon/KernelProcess/kact/SetAdapter.h delete mode 100644 IntelPresentMon/PresentMonService/acts/SelectAdapter.h diff --git a/IntelPresentMon/AppCef/source/util/KernelActionRegistration.cpp b/IntelPresentMon/AppCef/source/util/KernelActionRegistration.cpp index 51d092a4..d11dc669 100644 --- a/IntelPresentMon/AppCef/source/util/KernelActionRegistration.cpp +++ b/IntelPresentMon/AppCef/source/util/KernelActionRegistration.cpp @@ -1,4 +1,4 @@ -#include "IpcInvocationManager.h" +#include "IpcInvocationManager.h" #include "../../../KernelProcess/kact/AllActions.h" namespace p2c::client::util::kact { template @@ -9,7 +9,6 @@ namespace p2c::client::util::kact { IpcActionRegistrator reg_ibind_EnumerateAdapters_; IpcActionRegistrator reg_ibind_Introspect_; IpcActionRegistrator reg_ibind_PushSpecification_; - IpcActionRegistrator reg_ibind_SetAdapter_; IpcActionRegistrator reg_ibind_SetCapture_; IpcActionRegistrator reg_ibind_BindHotkey_; IpcActionRegistrator reg_ibind_ClearHotkey_; diff --git a/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.cpp b/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.cpp index 87cf57c6..8521cae0 100644 --- a/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.cpp +++ b/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.cpp @@ -25,9 +25,10 @@ int operator>>(AmdResultGrabber g, AmdCheckerToken) noexcept { } AmdPowerTelemetryAdapter::AmdPowerTelemetryAdapter( - const Adl2Wrapper* adl2_wrapper, std::string adl_adapter_name, + uint32_t deviceId, const Adl2Wrapper* adl2_wrapper, std::string adl_adapter_name, int adl_adapter_index, int overdrive_version) - : adl2_(adl2_wrapper), + : PowerTelemetryAdapter(deviceId), + adl2_(adl2_wrapper), name_(std::move(adl_adapter_name)), adl_adapter_index_(adl_adapter_index), overdrive_version_(overdrive_version) {} diff --git a/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.h b/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.h index fb06561e..45361e5e 100644 --- a/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.h +++ b/IntelPresentMon/ControlLib/AmdPowerTelemetryAdapter.h @@ -18,7 +18,7 @@ int operator>>(AmdResultGrabber, AmdCheckerToken) noexcept; class AmdPowerTelemetryAdapter : public PowerTelemetryAdapter { public: - AmdPowerTelemetryAdapter(const Adl2Wrapper* adl_wrapper, std::string adl_adapter_name, + AmdPowerTelemetryAdapter(uint32_t deviceId, const Adl2Wrapper* adl_wrapper, std::string adl_adapter_name, int adl_adapter_index, int overdrive_version); PresentMonPowerTelemetryInfo Sample() noexcept override; PM_DEVICE_VENDOR GetVendor() const noexcept override; diff --git a/IntelPresentMon/ControlLib/AmdPowerTelemetryProvider.cpp b/IntelPresentMon/ControlLib/AmdPowerTelemetryProvider.cpp index b15163ec..8df1b950 100644 --- a/IntelPresentMon/ControlLib/AmdPowerTelemetryProvider.cpp +++ b/IntelPresentMon/ControlLib/AmdPowerTelemetryProvider.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #include #include @@ -10,7 +10,7 @@ namespace pwr::amd { -AmdPowerTelemetryProvider::AmdPowerTelemetryProvider() { +AmdPowerTelemetryProvider::AmdPowerTelemetryProvider(DeviceIdAllocator& allocator) { int count = 0; if (!adl_.Ok(adl_.Adapter_NumberOfAdapters_Get(&count))) { throw std::runtime_error{"Failed to get number of amd gpus"}; @@ -67,7 +67,7 @@ AmdPowerTelemetryProvider::AmdPowerTelemetryProvider() { try { std::string adl_adapter_name = ai.strAdapterName; adapter_ptrs_.push_back(std::make_shared( - &adl_, adl_adapter_name, ai.iAdapterIndex, overdrive_version)); + allocator.Next(), &adl_, adl_adapter_name, ai.iAdapterIndex, overdrive_version)); } catch (const std::exception& e) { TELE_ERR(e.what()); } catch (...) { @@ -91,4 +91,4 @@ uint32_t AmdPowerTelemetryProvider::GetAdapterCount() const noexcept { return (uint32_t)adapter_ptrs_.size(); } -} // namespace pwr::amd \ No newline at end of file +} // namespace pwr::amd diff --git a/IntelPresentMon/ControlLib/AmdPowerTelemetryProvider.h b/IntelPresentMon/ControlLib/AmdPowerTelemetryProvider.h index b9df42f3..c79fb500 100644 --- a/IntelPresentMon/ControlLib/AmdPowerTelemetryProvider.h +++ b/IntelPresentMon/ControlLib/AmdPowerTelemetryProvider.h @@ -9,11 +9,12 @@ #include "PresentMonPowerTelemetry.h" #include "PowerTelemetryProvider.h" #include "Adl2Wrapper.h" +#include "DeviceIdAllocator.h" namespace pwr::amd { class AmdPowerTelemetryProvider : public PowerTelemetryProvider { public: - AmdPowerTelemetryProvider(); + explicit AmdPowerTelemetryProvider(DeviceIdAllocator& allocator); AmdPowerTelemetryProvider(const AmdPowerTelemetryProvider& t) = delete; AmdPowerTelemetryProvider& operator=(const AmdPowerTelemetryProvider& t) = delete; ~AmdPowerTelemetryProvider() override; diff --git a/IntelPresentMon/ControlLib/DeviceIdAllocator.h b/IntelPresentMon/ControlLib/DeviceIdAllocator.h new file mode 100644 index 00000000..1e1e7f48 --- /dev/null +++ b/IntelPresentMon/ControlLib/DeviceIdAllocator.h @@ -0,0 +1,24 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: MIT +#pragma once + +#include +#include + +namespace pwr +{ + class DeviceIdAllocator + { + public: + explicit DeviceIdAllocator(uint32_t startId = 1) noexcept + : nextId_{ startId } {} + + uint32_t Next() noexcept + { + return nextId_.fetch_add(1, std::memory_order_relaxed); + } + + private: + std::atomic nextId_; + }; +} diff --git a/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.cpp b/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.cpp index 8016a9d4..16df7d7b 100644 --- a/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.cpp +++ b/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.cpp @@ -71,8 +71,9 @@ namespace pwr::intel // public interface functions - IntelPowerTelemetryAdapter::IntelPowerTelemetryAdapter(ctl_device_adapter_handle_t handle) + IntelPowerTelemetryAdapter::IntelPowerTelemetryAdapter(uint32_t deviceId, ctl_device_adapter_handle_t handle) : + PowerTelemetryAdapter(deviceId), deviceHandle{ handle } { properties = { diff --git a/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.h b/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.h index 88e503a0..7f1be041 100644 --- a/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.h +++ b/IntelPresentMon/ControlLib/IntelPowerTelemetryAdapter.h @@ -14,7 +14,7 @@ namespace pwr::intel class IntelPowerTelemetryAdapter : public PowerTelemetryAdapter { public: - IntelPowerTelemetryAdapter(ctl_device_adapter_handle_t handle); + IntelPowerTelemetryAdapter(uint32_t deviceId, ctl_device_adapter_handle_t handle); PresentMonPowerTelemetryInfo Sample() noexcept override; PM_DEVICE_VENDOR GetVendor() const noexcept override; std::string GetName() const noexcept override; diff --git a/IntelPresentMon/ControlLib/IntelPowerTelemetryProvider.cpp b/IntelPresentMon/ControlLib/IntelPowerTelemetryProvider.cpp index 918cefec..4faeb99b 100644 --- a/IntelPresentMon/ControlLib/IntelPowerTelemetryProvider.cpp +++ b/IntelPresentMon/ControlLib/IntelPowerTelemetryProvider.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #include "IntelPowerTelemetryProvider.h" #include "IntelPowerTelemetryAdapter.h" @@ -12,7 +12,7 @@ using v = log::V; namespace pwr::intel { - IntelPowerTelemetryProvider::IntelPowerTelemetryProvider() + IntelPowerTelemetryProvider::IntelPowerTelemetryProvider(DeviceIdAllocator& allocator) { // TODO(megalvan): Currently using the default Id of all zeros. Do we need // to obtain a legit application Id or is default fine? @@ -57,7 +57,7 @@ namespace pwr::intel for (auto& handle : handles) { try { - adapterPtrs.push_back(std::make_shared(handle)); + adapterPtrs.push_back(std::make_shared(allocator.Next(), handle)); } catch (const IntelPowerTelemetryAdapter::NonGraphicsDeviceException&) {} catch (const std::exception& e) { TELE_ERR(e.what()); } @@ -85,4 +85,4 @@ namespace pwr::intel { return (uint32_t)adapterPtrs.size(); } -} \ No newline at end of file +} diff --git a/IntelPresentMon/ControlLib/IntelPowerTelemetryProvider.h b/IntelPresentMon/ControlLib/IntelPowerTelemetryProvider.h index 0051aa8d..b51d3f33 100644 --- a/IntelPresentMon/ControlLib/IntelPowerTelemetryProvider.h +++ b/IntelPresentMon/ControlLib/IntelPowerTelemetryProvider.h @@ -10,13 +10,14 @@ #include "igcl_api.h" #include "PresentMonPowerTelemetry.h" #include "PowerTelemetryProvider.h" +#include "DeviceIdAllocator.h" namespace pwr::intel { class IntelPowerTelemetryProvider : public PowerTelemetryProvider { public: - IntelPowerTelemetryProvider(); + explicit IntelPowerTelemetryProvider(DeviceIdAllocator& allocator); IntelPowerTelemetryProvider(const IntelPowerTelemetryProvider& t) = delete; IntelPowerTelemetryProvider& operator=(const IntelPowerTelemetryProvider& t) = delete; ~IntelPowerTelemetryProvider() override; diff --git a/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.cpp b/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.cpp index f0de61b4..24861179 100644 --- a/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.cpp +++ b/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.cpp @@ -10,11 +10,13 @@ namespace pwr::nv { NvidiaPowerTelemetryAdapter::NvidiaPowerTelemetryAdapter( + uint32_t deviceId, const NvapiWrapper* pNvapi, const NvmlWrapper* pNvml, NvPhysicalGpuHandle hGpuNvapi, std::optional hGpuNvml) : + PowerTelemetryAdapter(deviceId), nvapi{ pNvapi }, nvml{ pNvml }, hNvapi{ hGpuNvapi }, diff --git a/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.h b/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.h index ba35e438..efe7297f 100644 --- a/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.h +++ b/IntelPresentMon/ControlLib/NvidiaPowerTelemetryAdapter.h @@ -12,6 +12,7 @@ namespace pwr::nv { public: NvidiaPowerTelemetryAdapter( + uint32_t deviceId, const NvapiWrapper* pNvapi, const NvmlWrapper* pNvml, NvPhysicalGpuHandle hGpuNvapi, diff --git a/IntelPresentMon/ControlLib/NvidiaPowerTelemetryProvider.cpp b/IntelPresentMon/ControlLib/NvidiaPowerTelemetryProvider.cpp index 32feb622..93f412bb 100644 --- a/IntelPresentMon/ControlLib/NvidiaPowerTelemetryProvider.cpp +++ b/IntelPresentMon/ControlLib/NvidiaPowerTelemetryProvider.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #include "NvidiaPowerTelemetryProvider.h" #include "NvidiaPowerTelemetryAdapter.h" @@ -7,7 +7,7 @@ namespace pwr::nv { - NvidiaPowerTelemetryProvider::NvidiaPowerTelemetryProvider() + NvidiaPowerTelemetryProvider::NvidiaPowerTelemetryProvider(DeviceIdAllocator& allocator) { // enumerate nvapi gpu device handles std::vector> nvapiHandlePairs; @@ -62,10 +62,10 @@ namespace pwr::nv // create adaptor object for each api adapter handle pair // lambda to factor out repeated code pattern adapter creation and insertion into container, with exception quelling - const auto TryCreateAddAdapter = [this](NvPhysicalGpuHandle nvapiHandle, std::optional nvmlHandle) { + const auto TryCreateAddAdapter = [this, &allocator](NvPhysicalGpuHandle nvapiHandle, std::optional nvmlHandle) { try { adapterPtrs.push_back(std::make_shared( - &nvapi, &nvml, nvapiHandle, nvmlHandle + allocator.Next(), &nvapi, &nvml, nvapiHandle, nvmlHandle )); } catch (const std::exception& e) { TELE_ERR(e.what()); } @@ -108,4 +108,4 @@ namespace pwr::nv { return (uint32_t)adapterPtrs.size(); } -} \ No newline at end of file +} diff --git a/IntelPresentMon/ControlLib/NvidiaPowerTelemetryProvider.h b/IntelPresentMon/ControlLib/NvidiaPowerTelemetryProvider.h index 662c1022..a778b752 100644 --- a/IntelPresentMon/ControlLib/NvidiaPowerTelemetryProvider.h +++ b/IntelPresentMon/ControlLib/NvidiaPowerTelemetryProvider.h @@ -11,13 +11,14 @@ #include "PowerTelemetryProvider.h" #include "NvapiWrapper.h" #include "NvmlWrapper.h" +#include "DeviceIdAllocator.h" namespace pwr::nv { class NvidiaPowerTelemetryProvider : public PowerTelemetryProvider { public: - NvidiaPowerTelemetryProvider(); + explicit NvidiaPowerTelemetryProvider(DeviceIdAllocator& allocator); const std::vector>& GetAdapters() noexcept override; uint32_t GetAdapterCount() const noexcept override; diff --git a/IntelPresentMon/ControlLib/PowerTelemetryAdapter.cpp b/IntelPresentMon/ControlLib/PowerTelemetryAdapter.cpp index 35c045f6..f50567f6 100644 --- a/IntelPresentMon/ControlLib/PowerTelemetryAdapter.cpp +++ b/IntelPresentMon/ControlLib/PowerTelemetryAdapter.cpp @@ -1,4 +1,4 @@ -#include "PowerTelemetryAdapter.h" +#include "PowerTelemetryAdapter.h" #include "Logging.h" #include "../CommonUtilities/ref/WrapReflect.h" #include "../CommonUtilities/ref/StaticReflection.h" @@ -12,6 +12,14 @@ namespace pwr using v = ::pmon::util::log::V; using ::pmon::util::log::GlobalPolicy; + PowerTelemetryAdapter::PowerTelemetryAdapter(uint32_t deviceId) noexcept + : deviceId_{ deviceId } {} + + uint32_t PowerTelemetryAdapter::GetDeviceId() const noexcept + { + return deviceId_; + } + void PowerTelemetryAdapter::SetTelemetryCapBit(GpuTelemetryCapBits telemetryCapBit) noexcept { if (GlobalPolicy::VCheck(v::tele_gpu)) { @@ -34,4 +42,4 @@ namespace pwr .pmwatch(GetName()).pmwatch(gpuTelemetryCapBits_.test(size_t(bit))); return gpuTelemetryCapBits_.test(size_t(bit)); } -} \ No newline at end of file +} diff --git a/IntelPresentMon/ControlLib/PowerTelemetryAdapter.h b/IntelPresentMon/ControlLib/PowerTelemetryAdapter.h index a932c257..6f5b257e 100644 --- a/IntelPresentMon/ControlLib/PowerTelemetryAdapter.h +++ b/IntelPresentMon/ControlLib/PowerTelemetryAdapter.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "PresentMonPowerTelemetry.h" #include "../PresentMonAPI2/PresentMonAPI.h" @@ -22,12 +23,17 @@ namespace pwr virtual std::string GetName() const noexcept = 0; virtual uint64_t GetDedicatedVideoMemory() const noexcept = 0; virtual uint64_t GetVideoMemoryMaxBandwidth() const noexcept = 0; - virtual double GetSustainedPowerLimit() const noexcept = 0; + virtual double GetSustainedPowerLimit() const noexcept = 0; + uint32_t GetDeviceId() const noexcept; void SetTelemetryCapBit(GpuTelemetryCapBits telemetryCapBit) noexcept; SetTelemetryCapBitset GetPowerTelemetryCapBits(); bool HasTelemetryCapBit(GpuTelemetryCapBits bit) const; - private: + protected: + explicit PowerTelemetryAdapter(uint32_t deviceId) noexcept; + + private: // data SetTelemetryCapBitset gpuTelemetryCapBits_{}; + const uint32_t deviceId_ = 0; }; } diff --git a/IntelPresentMon/ControlLib/PowerTelemetryProviderFactory.cpp b/IntelPresentMon/ControlLib/PowerTelemetryProviderFactory.cpp index 3724dc15..eec1143c 100644 --- a/IntelPresentMon/ControlLib/PowerTelemetryProviderFactory.cpp +++ b/IntelPresentMon/ControlLib/PowerTelemetryProviderFactory.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #include "PowerTelemetryProviderFactory.h" #include "IntelPowerTelemetryProvider.h" @@ -7,13 +7,13 @@ namespace pwr { - std::unique_ptr PowerTelemetryProviderFactory::Make(PM_DEVICE_VENDOR vendor) + std::unique_ptr PowerTelemetryProviderFactory::Make(PM_DEVICE_VENDOR vendor, DeviceIdAllocator& allocator) { switch (vendor) { - case PM_DEVICE_VENDOR_INTEL: return std::make_unique(); - case PM_DEVICE_VENDOR_NVIDIA: return std::make_unique(); - case PM_DEVICE_VENDOR_AMD: return std::make_unique (); + case PM_DEVICE_VENDOR_INTEL: return std::make_unique(allocator); + case PM_DEVICE_VENDOR_NVIDIA: return std::make_unique(allocator); + case PM_DEVICE_VENDOR_AMD: return std::make_unique(allocator); } return {}; } -} \ No newline at end of file +} diff --git a/IntelPresentMon/ControlLib/PowerTelemetryProviderFactory.h b/IntelPresentMon/ControlLib/PowerTelemetryProviderFactory.h index dda0ec2d..37fa9d36 100644 --- a/IntelPresentMon/ControlLib/PowerTelemetryProviderFactory.h +++ b/IntelPresentMon/ControlLib/PowerTelemetryProviderFactory.h @@ -1,7 +1,8 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #include "PowerTelemetryProvider.h" +#include "DeviceIdAllocator.h" #include "../PresentMonAPI2/PresentMonAPI.h" #include @@ -10,6 +11,6 @@ namespace pwr class PowerTelemetryProviderFactory { public: - static std::unique_ptr Make(PM_DEVICE_VENDOR vendor); + static std::unique_ptr Make(PM_DEVICE_VENDOR vendor, DeviceIdAllocator& allocator); }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/Core/source/kernel/Kernel.cpp b/IntelPresentMon/Core/source/kernel/Kernel.cpp index e49dd304..dd54f524 100644 --- a/IntelPresentMon/Core/source/kernel/Kernel.cpp +++ b/IntelPresentMon/Core/source/kernel/Kernel.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #include "Kernel.h" #include @@ -95,17 +95,6 @@ namespace p2c::kern cv.notify_one(); } - void Kernel::SetAdapter(uint32_t id) - { - HandleMarshalledException_(); - std::lock_guard lk{ mtx }; - if (!pm) { - pmlog_warn("presentmon not initialized"); - return; - } - pm->SetAdapter(id); - } - const pmapi::intro::Root& Kernel::GetIntrospectionRoot() const { HandleMarshalledException_(); diff --git a/IntelPresentMon/Core/source/kernel/Kernel.h b/IntelPresentMon/Core/source/kernel/Kernel.h index 098fa7ce..dd520658 100644 --- a/IntelPresentMon/Core/source/kernel/Kernel.h +++ b/IntelPresentMon/Core/source/kernel/Kernel.h @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #include @@ -51,7 +51,6 @@ namespace p2c::kern std::optional overrideTargetName, const GfxLayer::Extension::OverlayConfig& cfg); void ClearOverlay(); - void SetAdapter(uint32_t id); std::vector EnumerateAdapters() const; void SetCapture(bool active); void SetEtlLogging(bool active); @@ -84,4 +83,4 @@ namespace p2c::kern ::pmon::util::mt::Thread thread; bool headless; }; -} \ No newline at end of file +} diff --git a/IntelPresentMon/Core/source/pmon/PresentMon.cpp b/IntelPresentMon/Core/source/pmon/PresentMon.cpp index c7f6466a..846a35ed 100644 --- a/IntelPresentMon/Core/source/pmon/PresentMon.cpp +++ b/IntelPresentMon/Core/source/pmon/PresentMon.cpp @@ -85,17 +85,6 @@ namespace p2c::pmon } return infos; } - void PresentMon::SetAdapter(uint32_t id) - { - pmlog_info(std::format("Set active adapter to [{}]", id)); - if (id == 0) { - pmlog_warn("Adapter was set to id 0; resetting"); - selectedAdapter.reset(); - } - else { - selectedAdapter = id; - } - } void PresentMon::SetEtlLogging(bool active) { pmlog_info("Setting etl logging").pmwatch(active); @@ -136,13 +125,9 @@ namespace p2c::pmon constexpr bool omitUnavailableColumns = false; // make the frame data writer - return std::make_shared(std::move(path), processTracker, selectedAdapter.value_or(1), + return std::make_shared(std::move(path), processTracker, GetDefaultGpuDeviceId_(), *pSession, std::move(statsPath), *pIntrospectionRoot, omitUnavailableColumns); } - std::optional PresentMon::GetSelectedAdapter() const - { - return selectedAdapter; - } const pmapi::intro::Root& PresentMon::GetIntrospectionRoot() const { return *pIntrospectionRoot; @@ -161,4 +146,14 @@ namespace p2c::pmon { return etwFlushPeriodMs; } + + uint32_t PresentMon::GetDefaultGpuDeviceId_() const + { + for (const auto& device : pIntrospectionRoot->GetDevices()) { + if (device.GetType() == PM_DEVICE_TYPE_GRAPHICS_ADAPTER) { + return device.GetId(); + } + } + return 0; + } } diff --git a/IntelPresentMon/Core/source/pmon/PresentMon.h b/IntelPresentMon/Core/source/pmon/PresentMon.h index a6a08b9a..ced1fa93 100644 --- a/IntelPresentMon/Core/source/pmon/PresentMon.h +++ b/IntelPresentMon/Core/source/pmon/PresentMon.h @@ -35,16 +35,15 @@ namespace p2c::pmon std::optional GetEtwFlushPeriod(); // std::wstring GetCpuName() const; std::vector EnumerateAdapters() const; - void SetAdapter(uint32_t id); void SetEtlLogging(bool active); std::optional GetPid() const; const pmapi::ProcessTracker& GetTracker() const; std::shared_ptr MakeRawFrameDataWriter(std::wstring path, std::optional statsPath, uint32_t pid); - std::optional GetSelectedAdapter() const; const pmapi::intro::Root& GetIntrospectionRoot() const; pmapi::Session& GetSession(); private: + uint32_t GetDefaultGpuDeviceId_() const; double window = -1.; uint32_t telemetrySamplePeriod = 0; std::optional etwFlushPeriodMs; @@ -52,6 +51,5 @@ namespace p2c::pmon std::unique_ptr pSession; std::shared_ptr pIntrospectionRoot; pmapi::ProcessTracker processTracker; - std::optional selectedAdapter; }; } diff --git a/IntelPresentMon/Interprocess/source/Interprocess.cpp b/IntelPresentMon/Interprocess/source/Interprocess.cpp index 2c535e89..b4d7bc51 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.cpp +++ b/IntelPresentMon/Interprocess/source/Interprocess.cpp @@ -60,12 +60,11 @@ namespace pmon::ipc { return *pRoot_; } - void RegisterGpuDevice(PM_DEVICE_VENDOR vendor, + void RegisterGpuDevice(uint32_t deviceId, PM_DEVICE_VENDOR vendor, std::string deviceName, const MetricCapabilities& caps) override { auto lck = LockIntrospectionMutexExclusive_(); - const auto deviceId = nextDeviceIndex_++; pmlog_dbg("GPU metric capabilities") .pmwatch(deviceId) .pmwatch(deviceName) @@ -292,7 +291,6 @@ namespace pmon::ipc ShmUniquePtr pIntroMutex_; ShmUniquePtr pIntroSemaphore_; ShmUniquePtr pRoot_; - uint32_t nextDeviceIndex_ = 1; bool introGpuComplete_ = false; bool introCpuComplete_ = false; diff --git a/IntelPresentMon/Interprocess/source/Interprocess.h b/IntelPresentMon/Interprocess/source/Interprocess.h index 046a0641..4af56e6c 100644 --- a/IntelPresentMon/Interprocess/source/Interprocess.h +++ b/IntelPresentMon/Interprocess/source/Interprocess.h @@ -20,7 +20,7 @@ namespace pmon::ipc public: virtual ~ServiceComms() = default; virtual intro::IntrospectionRoot& GetIntrospectionRoot() = 0; - virtual void RegisterGpuDevice(PM_DEVICE_VENDOR vendor, std::string deviceName, const MetricCapabilities& caps) = 0; + virtual void RegisterGpuDevice(uint32_t deviceId, PM_DEVICE_VENDOR vendor, std::string deviceName, const MetricCapabilities& caps) = 0; virtual void FinalizeGpuDevices() = 0; virtual void RegisterCpuDevice(PM_DEVICE_VENDOR vendor, std::string deviceName, const MetricCapabilities& caps) = 0; virtual const ShmNamer& GetNamer() const = 0; diff --git a/IntelPresentMon/KernelProcess/KernelProcess.vcxproj b/IntelPresentMon/KernelProcess/KernelProcess.vcxproj index e8ec2804..ba83d4fb 100644 --- a/IntelPresentMon/KernelProcess/KernelProcess.vcxproj +++ b/IntelPresentMon/KernelProcess/KernelProcess.vcxproj @@ -1,4 +1,4 @@ - + @@ -171,7 +171,6 @@ - @@ -194,4 +193,4 @@ - \ No newline at end of file + diff --git a/IntelPresentMon/KernelProcess/KernelProcess.vcxproj.filters b/IntelPresentMon/KernelProcess/KernelProcess.vcxproj.filters index 36c86601..311ebca4 100644 --- a/IntelPresentMon/KernelProcess/KernelProcess.vcxproj.filters +++ b/IntelPresentMon/KernelProcess/KernelProcess.vcxproj.filters @@ -44,9 +44,6 @@ Header Files - - Header Files - Header Files @@ -79,4 +76,4 @@ - \ No newline at end of file + diff --git a/IntelPresentMon/KernelProcess/kact/AllActions.h b/IntelPresentMon/KernelProcess/kact/AllActions.h index d07b3cfe..f51d9b79 100644 --- a/IntelPresentMon/KernelProcess/kact/AllActions.h +++ b/IntelPresentMon/KernelProcess/kact/AllActions.h @@ -1,10 +1,9 @@ -#pragma once +#pragma once #include "OpenSession.h" -#include "SetAdapter.h" #include "SetCapture.h" #include "EnumerateAdapters.h" #include "Introspect.h" #include "PushSpecification.h" #include "BindHotkey.h" #include "ClearHotkey.h" -#include "SetEtlLogging.h" \ No newline at end of file +#include "SetEtlLogging.h" diff --git a/IntelPresentMon/KernelProcess/kact/SetAdapter.h b/IntelPresentMon/KernelProcess/kact/SetAdapter.h deleted file mode 100644 index 38e80b67..00000000 --- a/IntelPresentMon/KernelProcess/kact/SetAdapter.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once -#include "../../Interprocess/source/act/ActionHelper.h" -#include "KernelExecutionContext.h" -#include -#include "../../Core/source/kernel/Kernel.h" - -#define ACT_NAME SetAdapter -#define ACT_EXEC_CTX KernelExecutionContext -#define ACT_TYPE AsyncActionBase_ -#define ACT_NS kproc::kact - -namespace ACT_NS -{ - using namespace ::pmon::ipc::act; - - class ACT_NAME : public ACT_TYPE - { - public: - static constexpr const char* Identifier = STRINGIFY(ACT_NAME); - struct Params - { - uint32_t id; - - template void serialize(A& ar) { - ar(id); - } - }; - struct Response { - template void serialize(A& ar) { - } - }; - private: - friend class ACT_TYPE; - static Response Execute_(const ACT_EXEC_CTX& ctx, SessionContext& stx, Params&& in) - { - (*ctx.ppKernel)->SetAdapter(in.id); - return {}; - } - }; - -#ifdef PM_ASYNC_ACTION_REGISTRATION_ - ACTION_REG(); -#endif -} - -ACTION_TRAITS_DEF(); - -#undef ACT_NAME -#undef ACT_EXEC_CTX -#undef ACT_NS -#undef ACT_TYPE \ No newline at end of file diff --git a/IntelPresentMon/PresentMonAPI2Tests/TestCommands.h b/IntelPresentMon/PresentMonAPI2Tests/TestCommands.h index 5664af66..a42e1cb4 100644 --- a/IntelPresentMon/PresentMonAPI2Tests/TestCommands.h +++ b/IntelPresentMon/PresentMonAPI2Tests/TestCommands.h @@ -16,14 +16,13 @@ namespace pmon::test // new ipc tracking std::set trackedPids; std::set frameStorePids; - uint32_t activeAdapterId; uint32_t telemetryPeriodMs; std::optional etwFlushPeriodMs; template void serialize(Archive& ar) { - ar(trackedPids, frameStorePids, activeAdapterId, telemetryPeriodMs, etwFlushPeriodMs); + ar(trackedPids, frameStorePids, telemetryPeriodMs, etwFlushPeriodMs); } }; } diff --git a/IntelPresentMon/PresentMonService/ActionExecutionContext.h b/IntelPresentMon/PresentMonService/ActionExecutionContext.h index 5d665180..5af9e352 100644 --- a/IntelPresentMon/PresentMonService/ActionExecutionContext.h +++ b/IntelPresentMon/PresentMonService/ActionExecutionContext.h @@ -85,7 +85,6 @@ namespace pmon::svc::acts std::map trackedPids; // etl recording functionality support std::set etwLogSessionIds; - std::optional requestedAdapterId; std::optional requestedTelemetryPeriodMs; std::optional requestedEtwFlushPeriodMs; std::string clientBuildId; diff --git a/IntelPresentMon/PresentMonService/AllActions.h b/IntelPresentMon/PresentMonService/AllActions.h index 49b90922..5f880547 100644 --- a/IntelPresentMon/PresentMonService/AllActions.h +++ b/IntelPresentMon/PresentMonService/AllActions.h @@ -5,7 +5,6 @@ #include "acts/GetStaticCpuMetrics.h" #include "acts/OpenSession.h" #include "acts/ReportMetricUse.h" -#include "acts/SelectAdapter.h" #include "acts/SetEtwFlushPeriod.h" #include "acts/SetTelemetryPeriod.h" #include "acts/StartEtlLogging.h" diff --git a/IntelPresentMon/PresentMonService/PMMainThread.cpp b/IntelPresentMon/PresentMonService/PMMainThread.cpp index 7efc84a0..e72ad777 100644 --- a/IntelPresentMon/PresentMonService/PMMainThread.cpp +++ b/IntelPresentMon/PresentMonService/PMMainThread.cpp @@ -355,15 +355,16 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, } pmon::util::QpcTimer timer; ptc->Repopulate(); - for (auto&& [i, adapter] : ptc->GetPowerTelemetryAdapters() | vi::enumerate) { + for (auto&& adapter : ptc->GetPowerTelemetryAdapters()) { + const auto deviceId = adapter->GetDeviceId(); // refresh 2x here as workaround/kludge because Intel provider misreports 1st sample adapter->Sample(); const auto sample = adapter->Sample(); - pComms->RegisterGpuDevice(adapter->GetVendor(), adapter->GetName(), + pComms->RegisterGpuDevice(deviceId, adapter->GetVendor(), adapter->GetName(), ipc::intro::ConvertBitset(adapter->GetPowerTelemetryCapBits())); // after registering, we know that at least the store is available even // if the introspection itself is not complete - auto& gpuStore = pComms->GetGpuDataStore(uint32_t(i + 1)); + auto& gpuStore = pComms->GetGpuDataStore(deviceId); // TODO: replace this placeholder routine for populating statics gpuStore.statics.name = adapter->GetName().c_str(); gpuStore.statics.vendor = adapter->GetVendor(); @@ -395,8 +396,8 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, else { // if any of our gpu telemetry devices are active enter polling loop bool hasActive = false; - for (auto&& [i, ad] : ptc->GetPowerTelemetryAdapters() | vi::enumerate) { - const auto deviceId = uint32_t(i) + 1; + for (auto&& adapter : ptc->GetPowerTelemetryAdapters()) { + const auto deviceId = adapter->GetDeviceId(); if (pm->CheckDeviceMetricUsage(deviceId)) { pmlog_dbg("detected gpu active").pmwatch(deviceId); hasActive = true; @@ -417,16 +418,18 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, // TODO: log error here or inside of repopulate ptc->Repopulate(); } - // poll all gpu adapter devices - // TODO: only poll devices that are actually active + // poll all gpu adapter devices, skipping inactive devices auto& adapters = ptc->GetPowerTelemetryAdapters(); - for (size_t idx = 0; idx < adapters.size(); ++idx) { - auto& adapter = adapters[idx]; + for (auto&& adapter : adapters) { + const auto deviceId = adapter->GetDeviceId(); + if (!pm->CheckDeviceMetricUsage(deviceId)) { + continue; + } // Get the newest sample from the provider const auto sample = adapter->Sample(); // Retrieve the matching GPU store. - auto& store = pComms->GetGpuDataStore(uint32_t(idx + 1)); + auto& store = pComms->GetGpuDataStore(deviceId); PopulateGpuTelemetryRings_(store, sample); } @@ -436,13 +439,8 @@ void PowerTelemetryThreadEntry_(Service* const srv, PresentMon* const pm, waiter.Wait(); // conditions for ending active poll and returning to idle state // go dormant if no gpu devices are in use - bool anyUsed = false; - for (size_t idx = 0; idx < adapters.size(); ++idx) { - if (pm->CheckDeviceMetricUsage(uint32_t(idx + 1))) { - anyUsed = true; - break; - } - } + const bool anyUsed = std::ranges::any_of(adapters, + [&](const auto& adapter) { return pm->CheckDeviceMetricUsage(adapter->GetDeviceId()); }); if (!anyUsed) { break; } diff --git a/IntelPresentMon/PresentMonService/PowerTelemetryContainer.cpp b/IntelPresentMon/PresentMonService/PowerTelemetryContainer.cpp index eae1a82d..2bbb1282 100644 --- a/IntelPresentMon/PresentMonService/PowerTelemetryContainer.cpp +++ b/IntelPresentMon/PresentMonService/PowerTelemetryContainer.cpp @@ -1,4 +1,4 @@ -#include "PowerTelemetryContainer.h" +#include "PowerTelemetryContainer.h" #include "../ControlLib/Exceptions.h" #include #include @@ -15,7 +15,7 @@ bool PowerTelemetryContainer::Repopulate() { for (int iVendor = 0; iVendor < int(PM_DEVICE_VENDOR_UNKNOWN); iVendor++) { try { if (auto pProvider = pwr::PowerTelemetryProviderFactory::Make( - PM_DEVICE_VENDOR(iVendor))) { + PM_DEVICE_VENDOR(iVendor), device_id_allocator_)) { telemetry_providers_.push_back(std::move(pProvider)); } } @@ -48,4 +48,4 @@ bool PowerTelemetryContainer::Repopulate() { catch (...) { return false; } -} \ No newline at end of file +} diff --git a/IntelPresentMon/PresentMonService/PowerTelemetryContainer.h b/IntelPresentMon/PresentMonService/PowerTelemetryContainer.h index e75d4e76..75e199a6 100644 --- a/IntelPresentMon/PresentMonService/PowerTelemetryContainer.h +++ b/IntelPresentMon/PresentMonService/PowerTelemetryContainer.h @@ -1,7 +1,8 @@ -// Copyright (C) 2022 Intel Corporation +// Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: MIT #pragma once #include "../ControlLib/PowerTelemetryProviderFactory.h" +#include "../ControlLib/DeviceIdAllocator.h" class PowerTelemetryContainer { public: @@ -13,4 +14,5 @@ class PowerTelemetryContainer { private: std::vector> telemetry_providers_; std::vector> telemetry_adapters_; -}; \ No newline at end of file + pwr::DeviceIdAllocator device_id_allocator_; +}; diff --git a/IntelPresentMon/PresentMonService/PresentMon.cpp b/IntelPresentMon/PresentMonService/PresentMon.cpp index 55ff7219..8d6bdedf 100644 --- a/IntelPresentMon/PresentMonService/PresentMon.cpp +++ b/IntelPresentMon/PresentMonService/PresentMon.cpp @@ -44,12 +44,6 @@ std::vector> PresentMon::EnumerateAd return pSession_->EnumerateAdapters(); } -PM_STATUS PresentMon::SelectAdapter(uint32_t adapter_id) -{ - // Only the real time trace uses the control libary interface - return pSession_->SelectAdapter(adapter_id); -} - void PresentMon::StartPlayback() { if (auto pPlaybackSession = dynamic_cast(pSession_.get())) { diff --git a/IntelPresentMon/PresentMonService/PresentMon.h b/IntelPresentMon/PresentMonService/PresentMon.h index bc2a0813..fbe3a2f6 100644 --- a/IntelPresentMon/PresentMonService/PresentMon.h +++ b/IntelPresentMon/PresentMonService/PresentMon.h @@ -36,7 +36,6 @@ class PresentMon std::vector> EnumerateAdapters(); std::string GetCpuName() { return pSession_->GetCpuName(); } double GetCpuPowerLimit() { return pSession_->GetCpuPowerLimit(); } - PM_STATUS SelectAdapter(uint32_t adapter_id); PM_STATUS SetGpuTelemetryPeriod(std::optional telemetryPeriodRequestsMs) { return pSession_->SetGpuTelemetryPeriod(telemetryPeriodRequestsMs); diff --git a/IntelPresentMon/PresentMonService/PresentMonService.vcxproj b/IntelPresentMon/PresentMonService/PresentMonService.vcxproj index 5b9c9e9f..dfc8d6aa 100644 --- a/IntelPresentMon/PresentMonService/PresentMonService.vcxproj +++ b/IntelPresentMon/PresentMonService/PresentMonService.vcxproj @@ -135,7 +135,6 @@ - diff --git a/IntelPresentMon/PresentMonService/PresentMonService.vcxproj.filters b/IntelPresentMon/PresentMonService/PresentMonService.vcxproj.filters index b500859e..aafdd0cd 100644 --- a/IntelPresentMon/PresentMonService/PresentMonService.vcxproj.filters +++ b/IntelPresentMon/PresentMonService/PresentMonService.vcxproj.filters @@ -1,4 +1,4 @@ - + @@ -36,7 +36,6 @@ - @@ -58,4 +57,4 @@ - \ No newline at end of file + diff --git a/IntelPresentMon/PresentMonService/PresentMonSession.cpp b/IntelPresentMon/PresentMonService/PresentMonSession.cpp index cc82dc22..f05410ef 100644 --- a/IntelPresentMon/PresentMonService/PresentMonSession.cpp +++ b/IntelPresentMon/PresentMonService/PresentMonSession.cpp @@ -21,7 +21,6 @@ pmon::test::service::Status PresentMonSession::GetTestingStatus() const return pmon::test::service::Status{ .trackedPids = std::move(trackedPids), .frameStorePids = std::move(frameStorePids), - .activeAdapterId = current_telemetry_adapter_id_, .telemetryPeriodMs = gpu_telemetry_period_ms_, .etwFlushPeriodMs = etw_flush_period_ms_, }; @@ -58,16 +57,6 @@ double PresentMonSession::GetCpuPowerLimit() { } } -PM_STATUS PresentMonSession::SelectAdapter(uint32_t adapter_id) { - if (telemetry_container_) { - if (adapter_id > telemetry_container_->GetPowerTelemetryAdapters().size()) { - return PM_STATUS_INVALID_ADAPTER_ID; - } - current_telemetry_adapter_id_ = adapter_id; - } - return PM_STATUS::PM_STATUS_SUCCESS; -} - PM_STATUS PresentMonSession::SetGpuTelemetryPeriod(std::optional period_ms) { gpu_telemetry_period_ms_ = period_ms.value_or(default_gpu_telemetry_period_ms_); diff --git a/IntelPresentMon/PresentMonService/PresentMonSession.h b/IntelPresentMon/PresentMonService/PresentMonSession.h index b2f8b857..5ea82856 100644 --- a/IntelPresentMon/PresentMonService/PresentMonSession.h +++ b/IntelPresentMon/PresentMonService/PresentMonSession.h @@ -46,8 +46,6 @@ class PresentMonSession { std::string GetCpuName(); double GetCpuPowerLimit(); pmon::test::service::Status GetTestingStatus() const; - - PM_STATUS SelectAdapter(uint32_t adapter_id); PM_STATUS SetGpuTelemetryPeriod(std::optional period_ms); PM_STATUS SetEtwFlushPeriod(std::optional periodMs); std::optional GetEtwFlushPeriod(); @@ -70,8 +68,6 @@ class PresentMonSession { pwr::cpu::CpuTelemetry* cpu_ = nullptr; PowerTelemetryContainer* telemetry_container_ = nullptr; - uint32_t current_telemetry_adapter_id_ = 0; - // Set the initial telemetry period to 16ms static constexpr uint32_t default_gpu_telemetry_period_ms_ = 16; uint32_t gpu_telemetry_period_ms_ = default_gpu_telemetry_period_ms_; diff --git a/IntelPresentMon/PresentMonService/acts/EnumerateAdapters.h b/IntelPresentMon/PresentMonService/acts/EnumerateAdapters.h index ec3719be..a7de1174 100644 --- a/IntelPresentMon/PresentMonService/acts/EnumerateAdapters.h +++ b/IntelPresentMon/PresentMonService/acts/EnumerateAdapters.h @@ -1,8 +1,7 @@ -#pragma once +#pragma once #include "../../Interprocess/source/act/ActionHelper.h" #include "../ActionExecutionContext.h" #include -#include #define ACT_NAME EnumerateAdapters #define ACT_EXEC_CTX ActionExecutionContext @@ -12,8 +11,6 @@ namespace pmon::svc::acts { using namespace ipc::act; - namespace rn = std::ranges; - namespace vi = rn::views; // TODO: remove this action, it is redundant in light of Introspection class ACT_NAME : public ACT_TYPE @@ -47,9 +44,9 @@ namespace pmon::svc::acts static Response Execute_(const ACT_EXEC_CTX& ctx, SessionContext& stx, Params&& in) { Response out; - for (auto&&[i, adapter] : ctx.pPmon->EnumerateAdapters() | vi::enumerate) { + for (auto&& adapter : ctx.pPmon->EnumerateAdapters()) { out.adapters.push_back(Response::Adapter{ - .id = (uint32_t)i, + .id = adapter->GetDeviceId(), .vendor = adapter->GetVendor(), .name = adapter->GetName(), .gpuSustainedPowerLimit = adapter->GetSustainedPowerLimit(), diff --git a/IntelPresentMon/PresentMonService/acts/SelectAdapter.h b/IntelPresentMon/PresentMonService/acts/SelectAdapter.h deleted file mode 100644 index 4046ae46..00000000 --- a/IntelPresentMon/PresentMonService/acts/SelectAdapter.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once -#include "../../Interprocess/source/act/ActionHelper.h" -#include -#include - -#define ACT_NAME SelectAdapter -#define ACT_EXEC_CTX ActionExecutionContext -#define ACT_NS ::pmon::svc::acts -#define ACT_TYPE AsyncActionBase_ - -namespace pmon::svc::acts -{ - using namespace ipc::act; - namespace rn = std::ranges; - namespace vi = rn::views; - - class ACT_NAME : public ACT_TYPE - { - public: - static constexpr const char* Identifier = STRINGIFY(ACT_NAME); - struct Params - { - uint32_t adapterId; - - template void serialize(A& ar) { - ar(adapterId); - } - }; - struct Response {}; - private: - friend class ACT_TYPE; - static Response Execute_(const ACT_EXEC_CTX& ctx, SessionContext& stx, Params&& in) - { - if (auto sta = ctx.pPmon->SelectAdapter(in.adapterId); sta != PM_STATUS_SUCCESS) { - pmlog_error("Select adapter failed").code(sta); - throw util::Except(sta); - } - stx.requestedAdapterId = in.adapterId; - pmlog_dbg(std::format("selecting adapter id [{}]", in.adapterId)); - return {}; - } - }; - -#ifdef PM_ASYNC_ACTION_REGISTRATION_ - ACTION_REG(); -#endif -} - -ACTION_TRAITS_DEF(); - -#undef ACT_NAME -#undef ACT_EXEC_CTX -#undef ACT_NS -#undef ACT_TYPE From 1c3de5d0351457ab4d04dbd76751d3a8ab2868ce Mon Sep 17 00:00:00 2001 From: Chili Date: Sun, 8 Feb 2026 06:31:22 +0900 Subject: [PATCH 190/205] delete defunct cli projects --- IntelPresentMon/CliCore/CliCore.vcxproj | 222 ----------- .../CliCore/CliCore.vcxproj.filters | 47 --- IntelPresentMon/CliCore/source/Entry.cpp | 347 ------------------ IntelPresentMon/CliCore/source/Entry.h | 6 - .../source/cons/ConsoleWaitControl.cpp | 340 ----------------- .../CliCore/source/cons/ConsoleWaitControl.h | 70 ---- .../CliCore/source/cons/OptionalConsoleOut.h | 32 -- .../CliCore/source/dat/ColumnGroups.h | 13 - IntelPresentMon/CliCore/source/dat/Columns.h | 76 ---- .../CliCore/source/dat/CsvWriter.cpp | 80 ---- .../CliCore/source/dat/CsvWriter.h | 77 ---- .../CliCore/source/dat/FrameDemultiplexer.cpp | 29 -- .../CliCore/source/dat/FrameDemultiplexer.h | 24 -- .../CliCore/source/dat/FrameFilterPump.cpp | 64 ---- .../CliCore/source/dat/FrameFilterPump.h | 40 -- .../CliCore/source/dat/FrameSink.h | 14 - .../CliCore/source/dat/MakeCsvName.cpp | 63 ---- .../CliCore/source/dat/MakeCsvName.h | 8 - .../CliCore/source/opt/Framework.cpp | 61 --- .../CliCore/source/opt/Framework.h | 147 -------- .../CliCore/source/opt/Options.cpp | 20 - IntelPresentMon/CliCore/source/opt/Options.h | 78 ---- .../CliCore/source/pmon/AdapterInfo.h | 15 - .../CliCore/source/pmon/Client.cpp | 127 ------- IntelPresentMon/CliCore/source/pmon/Client.h | 65 ---- .../CliCore/source/pmon/FrameDataStream.h | 17 - .../CliCore/source/pmon/PresentMode.cpp | 23 -- .../CliCore/source/pmon/PresentMode.h | 33 -- .../pmon/stream/LiveFrameDataStream.cpp | 80 ---- .../source/pmon/stream/LiveFrameDataStream.h | 32 -- .../pmon/stream/LoggedFrameDataState.cpp | 95 ----- .../source/pmon/stream/LoggedFrameDataState.h | 29 -- .../pmon/stream/LoggedFrameDataStream.cpp | 30 -- .../pmon/stream/LoggedFrameDataStream.h | 32 -- IntelPresentMon/CliCore/source/svc/Boot.cpp | 77 ---- IntelPresentMon/CliCore/source/svc/Boot.h | 8 - .../PresentMonCli/PresentMonCli.cpp | 13 - .../PresentMonCli/PresentMonCli.vcxproj | 184 ---------- IntelPresentMon/PresentMonCli/README.md | 282 -------------- 39 files changed, 3000 deletions(-) delete mode 100644 IntelPresentMon/CliCore/CliCore.vcxproj delete mode 100644 IntelPresentMon/CliCore/CliCore.vcxproj.filters delete mode 100644 IntelPresentMon/CliCore/source/Entry.cpp delete mode 100644 IntelPresentMon/CliCore/source/Entry.h delete mode 100644 IntelPresentMon/CliCore/source/cons/ConsoleWaitControl.cpp delete mode 100644 IntelPresentMon/CliCore/source/cons/ConsoleWaitControl.h delete mode 100644 IntelPresentMon/CliCore/source/cons/OptionalConsoleOut.h delete mode 100644 IntelPresentMon/CliCore/source/dat/ColumnGroups.h delete mode 100644 IntelPresentMon/CliCore/source/dat/Columns.h delete mode 100644 IntelPresentMon/CliCore/source/dat/CsvWriter.cpp delete mode 100644 IntelPresentMon/CliCore/source/dat/CsvWriter.h delete mode 100644 IntelPresentMon/CliCore/source/dat/FrameDemultiplexer.cpp delete mode 100644 IntelPresentMon/CliCore/source/dat/FrameDemultiplexer.h delete mode 100644 IntelPresentMon/CliCore/source/dat/FrameFilterPump.cpp delete mode 100644 IntelPresentMon/CliCore/source/dat/FrameFilterPump.h delete mode 100644 IntelPresentMon/CliCore/source/dat/FrameSink.h delete mode 100644 IntelPresentMon/CliCore/source/dat/MakeCsvName.cpp delete mode 100644 IntelPresentMon/CliCore/source/dat/MakeCsvName.h delete mode 100644 IntelPresentMon/CliCore/source/opt/Framework.cpp delete mode 100644 IntelPresentMon/CliCore/source/opt/Framework.h delete mode 100644 IntelPresentMon/CliCore/source/opt/Options.cpp delete mode 100644 IntelPresentMon/CliCore/source/opt/Options.h delete mode 100644 IntelPresentMon/CliCore/source/pmon/AdapterInfo.h delete mode 100644 IntelPresentMon/CliCore/source/pmon/Client.cpp delete mode 100644 IntelPresentMon/CliCore/source/pmon/Client.h delete mode 100644 IntelPresentMon/CliCore/source/pmon/FrameDataStream.h delete mode 100644 IntelPresentMon/CliCore/source/pmon/PresentMode.cpp delete mode 100644 IntelPresentMon/CliCore/source/pmon/PresentMode.h delete mode 100644 IntelPresentMon/CliCore/source/pmon/stream/LiveFrameDataStream.cpp delete mode 100644 IntelPresentMon/CliCore/source/pmon/stream/LiveFrameDataStream.h delete mode 100644 IntelPresentMon/CliCore/source/pmon/stream/LoggedFrameDataState.cpp delete mode 100644 IntelPresentMon/CliCore/source/pmon/stream/LoggedFrameDataState.h delete mode 100644 IntelPresentMon/CliCore/source/pmon/stream/LoggedFrameDataStream.cpp delete mode 100644 IntelPresentMon/CliCore/source/pmon/stream/LoggedFrameDataStream.h delete mode 100644 IntelPresentMon/CliCore/source/svc/Boot.cpp delete mode 100644 IntelPresentMon/CliCore/source/svc/Boot.h delete mode 100644 IntelPresentMon/PresentMonCli/PresentMonCli.cpp delete mode 100644 IntelPresentMon/PresentMonCli/PresentMonCli.vcxproj delete mode 100644 IntelPresentMon/PresentMonCli/README.md diff --git a/IntelPresentMon/CliCore/CliCore.vcxproj b/IntelPresentMon/CliCore/CliCore.vcxproj deleted file mode 100644 index e70429fc..00000000 --- a/IntelPresentMon/CliCore/CliCore.vcxproj +++ /dev/null @@ -1,222 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {31551c65-38ea-4e21-8c0d-4bc4a097993c} - CliCore - 10.0 - - - - StaticLibrary - true - v143 - Unicode - - - StaticLibrary - false - v143 - true - Unicode - - - StaticLibrary - true - v143 - Unicode - - - StaticLibrary - false - v143 - true - Unicode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ..\..\build\obj\$(ProjectName)-$(Platform)-$(Configuration)\ - ..\..\build\obj\$(ProjectName)-$(Platform)-$(Configuration)\ - - - ..\..\build\obj\$(ProjectName)-$(Platform)-$(Configuration)\ - ..\..\build\obj\$(ProjectName)-$(Platform)-$(Configuration)\ - - - ..\..\build\obj\$(ProjectName)-$(Platform)-$(Configuration)\ - ..\..\build\obj\$(ProjectName)-$(Platform)-$(Configuration)\ - - - ..\..\build\obj\$(ProjectName)-$(Platform)-$(Configuration)\ - ..\..\build\obj\$(ProjectName)-$(Platform)-$(Configuration)\ - - - - - - - - - - - - Level3 - true - true - NotUsing - stdcpplatest - MultiThreadedDebug - ..;%(AdditionalIncludeDirectories) - - - true - - - - - Level3 - true - true - true - NDEBUG;%(PreprocessorDefinitions) - true - NotUsing - stdcpplatest - MultiThreaded - ..;%(AdditionalIncludeDirectories) - - - true - true - true - - - - - Level3 - true - true - NotUsing - stdcpplatest - true - MultiThreadedDebug - ..;%(AdditionalIncludeDirectories) - - - true - - - - - Level3 - true - true - true - NDEBUG;%(PreprocessorDefinitions) - true - NotUsing - stdcpplatest - true - MultiThreaded - ..;%(AdditionalIncludeDirectories) - - - true - true - true - - - - - {808f5ea9-ea09-4d72-87b4-5397d43cba54} - - - {811adcf0-da1d-4a94-97c7-7aa6c33b5368} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/IntelPresentMon/CliCore/CliCore.vcxproj.filters b/IntelPresentMon/CliCore/CliCore.vcxproj.filters deleted file mode 100644 index 6015f4ca..00000000 --- a/IntelPresentMon/CliCore/CliCore.vcxproj.filters +++ /dev/null @@ -1,47 +0,0 @@ - - - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/Entry.cpp b/IntelPresentMon/CliCore/source/Entry.cpp deleted file mode 100644 index 47935d74..00000000 --- a/IntelPresentMon/CliCore/source/Entry.cpp +++ /dev/null @@ -1,347 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "pmon/FrameDataStream.h" -#include "dat/CsvWriter.h" -#include "dat/FrameFilterPump.h" -#include "dat/FrameDemultiplexer.h" -#include -#include "opt/Options.h" -#include "cons/ConsoleWaitControl.h" -#include -#include -#include -#include -#include -#include -#include "svc/Boot.h" -#include "cons/OptionalConsoleOut.h" -#include "dat/MakeCsvName.h" -#include "pmon/Client.h" -#include "pmon/stream/LoggedFrameDataState.h" - - -namespace p2c::cli -{ - using infra::util::ToWide; - using infra::util::ToNarrow; - using namespace std::chrono_literals; - using namespace std::string_literals; - namespace rn = std::ranges; - namespace vi = rn::views; - - int Entry(int argc, char** argv) - { - // keep track of how long since cli app launch for the - // purposes of start/stop timing - infra::util::ChiliTimer launchTimer; - - // parse command line options - try { - opt::init(argc, argv); - } - catch (const CLI::ParseError& e) { - return e.get_exit_code(); - }; - - // shortcut for command line options - const auto& opts = opt::get(); - - // boot services - svc::Boot( - opts.log, - opts.logPath ? std::optional{ *opts.logPath } : std::nullopt, - opts.logLevel ? std::optional{ *opts.logLevel } : std::nullopt - ); - - try { - using WakeReason = cons::ConsoleWaitControl::WakeReason; - auto& waiter = cons::ConsoleWaitControl::Get(); - - std::shared_ptr pClient; - // stream used to make sure etl frames get processed when there are no other streams - // this is necessary because when processes are specified by name, we need to pull frames - // to generate the spawn events, but spawn events are only generated when there is a stream - // and a stream will only be created if there is a spawn event (chicken and egg problem) - const auto telemetryPeriod = opts.telemetryPeriod ? *opts.telemetryPeriod : 16; - std::shared_ptr pLogSpoolingStream; - if (opts.etlFile) { - pClient = std::make_shared(*opts.etlFile, waiter, telemetryPeriod); - pLogSpoolingStream = pClient->OpenStream(0); - } - else { - pClient = std::make_shared(telemetryPeriod); - } - - // list adapters - if (opts.listAdapters) { - const auto adapters = pClient->EnumerateAdapters(); - if (adapters.empty()) { - std::cout << "No adapters compatible with PresentMon GPU telemetry available." << std::endl; - } - else { - std::cout << "Available adapters:" << std::endl; - } - for (auto& a : adapters) { - std::cout << " " << a.id << ": [" << a.vendor << "] " << a.name << std::endl; - } - return 0; - } - - cons::OptionalConsoleOut out{ !opts.outputStdout }; - - // set adapter for gpu telemetry - if (opts.adapter) { - const auto adapters = pClient->EnumerateAdapters(); - if (const auto i = rn::find_if(adapters, [&opts](const auto& a) - {return a.id == *opts.adapter; }); i == adapters.end()) { - std::cout << "Adapter with ID [" << *opts.adapter << "] not found." << std::endl; - return -1; - } - else { - out << "Using for GPU telemetry adapter <" << i->id << ">: [" << i->vendor << "] " << i->name << std::endl; - } - pClient->SetAdapter(*opts.adapter); - } - - // count of the number of specifically-targetted processes (by name and pid) - const auto targetCount = (*opts.name).size() + (*opts.pid).size(); - // derived flag for whether we are capturing all processes - const auto captureAll = opts.captureAll || targetCount == 0; - // targets specified by name that have been detected and have had a stream created for them - std::set mappedProcessNames; - // frame pumps for each stream, which also own the sinks for their outgoing frame data - std::vector> pumps; - // count total number of processes being monitored for the purposes of exiting - int trackedProcessCount = 0; - // count number of monitored processes that have exited - int exitedProcessCount = 0; - // output file name option as std::optional - const auto outputFileName = opts.outputFile ? std::optional{ *opts.outputFile } : std::nullopt; - // setup csv column groups - auto csvGroups = opts.GetColumnGroups(); - // setup excludes - auto excludes = *opts.excludeName; - if (!opts.ignoreCase) { - for (auto& e : excludes) { - e = e | vi::transform([](char c) {return(char)std::tolower(c); }) - | rn::to(); - } - } - // function decides how streams should be added based on 2x2 matrix - // of options: single/multi-csv x omni/targeted-streams - const auto AddStream = [&](uint32_t pid, std::optional processName = {}) { - // non-omni stream usage cases: - // if we are targetting w/ multi-csv, add stream + writer - // if we are targetting single process (regardless of csv-mode), same - if (!captureAll && (opts.multiCsv || targetCount == 1)) { - auto name = dat::MakeCsvName(opts.noCsv, pid, std::move(processName), outputFileName); - auto pWriter = std::make_shared(std::move(name), csvGroups, opts.outputStdout); - pumps.push_back(std::make_shared( - pClient->OpenStream(pid), std::move(pWriter), - excludes, std::vector{}, opts.excludeDropped, opts.ignoreCase - )); - waiter.AddProcessDeathWatch(pid); - ++trackedProcessCount; - } - // omni stream usage cases: - // if we are doing omni capture x multi-csv, add omni stream w/ demux - else if (captureAll && opts.multiCsv) { - assert(pumps.empty()); - auto pDemux = std::make_shared(csvGroups, outputFileName); - pumps.push_back(std::make_shared( - pClient->OpenStream(0), std::move(pDemux), - excludes, std::vector{}, opts.excludeDropped, opts.ignoreCase - )); - } - // if we are doing omni capture x single-csv, add omni w/ writer - else if (captureAll && !opts.multiCsv) { - assert(pumps.empty()); - auto name = dat::MakeCsvName(opts.noCsv, {}, {}, outputFileName); - auto pWriter = std::make_shared(std::move(name), csvGroups, opts.outputStdout); - pumps.push_back(std::make_shared( - pClient->OpenStream(0), std::move(pWriter), - excludes, std::vector{}, opts.excludeDropped, opts.ignoreCase - )); - } - // if we are doing multi-target capture x single-csv, add omni w/ whitelist - // if one doesn't exist, or add whitelist to existing otherwise - else { - if (pumps.empty()) { - auto name = dat::MakeCsvName(opts.noCsv, {}, {}, outputFileName); - auto pWriter = std::make_shared(std::move(name), csvGroups, opts.outputStdout); - pumps.push_back(std::make_shared( - pClient->OpenStream(0), std::move(pWriter), excludes, - std::vector{ pid }, opts.excludeDropped, opts.ignoreCase - )); - } - else { - pumps.front()->AddInclude(pid); - } - waiter.AddProcessDeathWatch(pid); - ++trackedProcessCount; - } - }; - - // delay before starting any captures - if (opts.startAfter) { - // subtract time elapsed since launch from the desired start time to get wait time - const auto delaySeconds = std::max(0., *opts.startAfter - launchTimer.Peek()); - out << std::format("Waiting {:.3f}s for start delay.", *opts.startAfter) << std::endl; - const auto delayMs = 1ms * int(1000.f * delaySeconds); - waiter.SetTimer(delayMs); - if (waiter.Wait() == WakeReason::CtrlTermination) { - out << "Termination signal received during wait. Exiting..." << std::endl; - waiter.Exit(); - return 0; - } - } - - // immediately start streams for all processes specified by pid - if (opts.pid) { - for (auto pid : *opts.pid) { - out << "Capturing process with PID [" << pid << "]." << std::endl; - AddStream(pid); - } - } - - // try to start streams for all processes specified by name - // maintain a watch on all named processes not yet running so that a stream can be - // started as soon as a matching process spawns - if (opts.name) { - for (auto name : *opts.name) { - if (opts.ignoreCase) { - name = name - | vi::transform([](char c) {return(char)std::tolower(c); }) - | rn::to(); - } - // begin watching for process spawn before we check for pre-existence - // this guards against race conditions of spawn after check but before watch - waiter.AddProcessSpawnWatch(name); - - // now check for pre-existence and open stream if available (only for live captures) - if (!opts.etlFile) { - auto procMap = win::ProcessMapBuilder{}.AsNameMap(opts.ignoreCase); - if (const auto i = procMap.find(ToWide(name)); i != procMap.end()) { - // we found that our target process is already active! - const auto pid = i->second.pid; - out << "Capturing process [" << name << "] with PID [" << pid << "]." << std::endl; - AddStream(pid, name); - // we found by polling, so remove from the watch - waiter.RemoveProcessSpawnWatch(ToNarrow(i->first)); - // also add to set of opened process names so that we can ignore any - // spawn events if they were already emitted - mappedProcessNames.insert(i->first); - } - else { - // target process is not yet open - out << "Waiting for [" << name << "] to spawn." << std::endl; - } - } - } - } - - // start omni-stream capture - if (captureAll || (!opts.pid && !opts.name)) { - out << "Capturing all processes." << std::endl; - AddStream(0); - } - - // set stop timer - if (opts.captureFor) { - out << std::format("Setting capture timer to {:.3f}s.", *opts.captureFor) << std::endl; - const auto delayMs = 1ms * int(1000.f * *opts.captureFor); - waiter.SetTimer(delayMs); - } - - // periodically run FrameFilterPump Process() function for all active streams - // which reads in frame data and writes it to corresponding csv file(s) - // also respond to spawn events by adding new streams - // also respond to ctrl events by doing cleanup and shutting down - // also respond to timer events by doing cleanup and shutting down - // also respond to process death events by shutting down when all dead - // poll at high rate for offline etl processing to protect again race condition (early close) - const auto livePollPeriod = opts.pollPeriod ? 1ms * *opts.pollPeriod : 250ms; - const auto pollingPeriod = opts.etlFile ? 1ms : livePollPeriod; - // simulated timestamp used for pulling frames from streams - double timestamp = 0.; - for (WakeReason wakeReason = WakeReason::Timeout;; - wakeReason = waiter.WaitFor(pollingPeriod)) { - switch (wakeReason) { - case WakeReason::CtrlTermination: - out << "Received termination control signal. Exiting...." << std::endl; - // Exit() call unblocks the signal handler thread - waiter.Exit(); - return 0; - case WakeReason::EndOfLog: - out << "Reached end of ETW log. Exiting...." << std::endl; - waiter.Exit(); - return 0; - case WakeReason::TimerEvent: - out << "Requested stop time reached. Exiting...." << std::endl; - // Exit() call protects again race condition where if ctrl signal - // handler is call right after timer wake, we make sure it doesn't block - waiter.Exit(); - return 0; - case WakeReason::ProcessDeath: - while (auto p = waiter.GetProcessDeathEvent()) { - // non-omni target mode, remove process from stream set - if (!captureAll && (opts.multiCsv || targetCount == 1)) { - std::erase_if(pumps, [&](const auto& s) {return s->GetPid() == *p; }); - } - // keep track of how many processes have exited - out << "Detected that process with pid [" << *p << "] has exited." << std::endl; - exitedProcessCount++; - } - // if last target process dies, we will exit - if (exitedProcessCount >= trackedProcessCount && - exitedProcessCount >= targetCount) { - out << "All target processes have exited. Exiting...." << std::endl; - waiter.Exit(); - return 0; - } - break; - case WakeReason::ProcessSpawn: - while (auto p = waiter.GetSpawnEvent()) { - // ignore processes already opened - if (!mappedProcessNames.contains(p->name)) { - // add process to set of names to ignore - mappedProcessNames.insert(p->name); - // open stream - AddStream(p->pid, ToNarrow(p->name)); - out << "Detected process [" << ToNarrow(p->name) << "] with pid [" << p->pid << "]. Starting capture...." << std::endl; - } - } - break; - case WakeReason::Timeout: - // use this stream when processing etl file to make sure that frames are pulled - // and process spawn events are generated - if (pLogSpoolingStream) { - pLogSpoolingStream->Pull(timestamp); - } - // pump streams if there are no events pending - if (!waiter.EventPending()) { - // process all pumps if there was a polling timeout - for (auto& pump : pumps) { - pump->Process(timestamp); - } - // update timestamp so cache is refreshed - timestamp++; - } - break; - default: - assert(false && "Unknown WakeReason encountered in main stream processing loop"); - } - } - } - catch (const std::exception& e) { - std::cout << "Error: [" << e.what() << "].\n"; - std::cout << "Use --help to see a list of options supported by this program." << std::endl; - } - - return -1; - } -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/Entry.h b/IntelPresentMon/CliCore/source/Entry.h deleted file mode 100644 index 405d4fdb..00000000 --- a/IntelPresentMon/CliCore/source/Entry.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -namespace p2c::cli -{ - int Entry(int argc, char** argv); -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/cons/ConsoleWaitControl.cpp b/IntelPresentMon/CliCore/source/cons/ConsoleWaitControl.cpp deleted file mode 100644 index c784c46f..00000000 --- a/IntelPresentMon/CliCore/source/cons/ConsoleWaitControl.cpp +++ /dev/null @@ -1,340 +0,0 @@ -#include "ConsoleWaitControl.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include "../opt/Options.h" - - -namespace p2c::cli::cons -{ - using namespace std::chrono_literals; - namespace rn = std::ranges; - namespace vi = rn::views; - - class ProcessTracker : private win::com::ProcessSpawnSink::EventQueue - { - public: - ProcessTracker(std::function notify) - : - win::com::ProcessSpawnSink::EventQueue{ [this] { HandleEvents_(); } }, - notifyFunction_{ std::move(notify) }, - pSpawnListener_{ conn_.MakeListener( - static_cast(*this)) } - {} - void AddWatch(std::string watch) - { - std::lock_guard lk{ mtx_ }; - watchedNames_.insert(std::move(watch)); - } - void RemoveWatch(std::string watch) - { - std::lock_guard lk{ mtx_ }; - watchedNames_.erase(watch); - } - std::optional GetMatchEvent() - { - std::optional match; - std::lock_guard lk{ mtx_ }; - if (!matches_.empty()) { - match = std::move(matches_.back()); - matches_.pop_back(); - } - return match; - } - void SimulateSpawnEvent(p2c::win::Process process) - { - if (HandleOneEvent_(std::move(process))) { - notifyFunction_(); - } - } - private: - // functions - void HandleEvents_() - { - bool matchOccurred = false; - while (auto e = Pop()) { - if (HandleOneEvent_(std::move(*e))) { - matchOccurred = true; - } - } - if (matchOccurred) { - notifyFunction_(); - } - } - bool HandleOneEvent_(p2c::win::Process e) - { - bool matchOccurred = false; - auto moduleName = std::filesystem::path{ e.name }.filename().string(); - if (opt::get().ignoreCase) { - moduleName = moduleName - | vi::transform([](char c) {return(char)std::tolower(c); }) - | rn::to(); - } - std::lock_guard lk{ mtx_ }; - if (auto i = watchedNames_.find(moduleName); i != watchedNames_.end()) { - matches_.push_back(std::move(e)); - watchedNames_.erase(i); - matchOccurred = true; - } - return matchOccurred; - } - // data - win::com::WbemConnection conn_; - std::mutex mtx_; - std::set watchedNames_; - std::vector matches_; - std::function notifyFunction_; - std::unique_ptr pSpawnListener_; - }; - - class TargetDeathTracker - { - class Tracker - { - public: - Tracker(TargetDeathTracker* pParent, uint32_t pid) - : - pParent_{ pParent }, - processHandle_{ OpenProcess(SYNCHRONIZE, false, pid) }, - wakeEventHandle_{ CreateEventW(nullptr, true, false, nullptr) }, - thread_{ [this] {ThreadProc_(); } } - {} - ~Tracker() - { - SetEvent(wakeEventHandle_); - thread_.join(); - CloseHandle(processHandle_); - CloseHandle(wakeEventHandle_); - } - bool Died() const - { - return died_; - } - Tracker(const Tracker&) = delete; - Tracker& operator=(const Tracker&) = delete; - private: - // functions - void ThreadProc_() - { - const std::array handles{ processHandle_, wakeEventHandle_ }; - const auto wakeReason = WaitForMultipleObjects( - (DWORD)handles.size(), handles.data(), FALSE, INFINITE - ); - if (wakeReason == WAIT_OBJECT_0) { - died_ = true; - pParent_->SignalDeathEvent_(); - } - } - // data - TargetDeathTracker* pParent_; - HANDLE processHandle_; - HANDLE wakeEventHandle_; - std::atomic died_ = false; - std::thread thread_; - }; - public: - TargetDeathTracker(std::function notify) - : - notifyFunction_{ std::move(notify) } - {} - void Add(uint32_t pid) - { - std::lock_guard lk{ mtx_ }; - trackers_.emplace(pid, - std::make_unique(this, pid) - ); - } - void Remove(uint32_t pid) - { - std::lock_guard lk{ mtx_ }; - trackers_.erase(pid); - } - std::optional GetDeathEvent() - { - std::optional deathPid; - std::lock_guard lk{ mtx_ }; - if (auto i = rn::find_if(trackers_, [](const auto& t) - { return t.second->Died(); }); i != trackers_.end()) { - deathPid = i->first; - trackers_.erase(i); - } - return deathPid; - } - private: - // functions - void SignalDeathEvent_() - { - std::lock_guard lk{ mtx_ }; - notifyFunction_(); - } - // data - std::mutex mtx_; - std::unordered_map> trackers_; - std::function notifyFunction_; - }; - - - - ConsoleWaitControl::ConsoleWaitControl() - : - pProcessTracker_{ std::make_unique([this] { SignalSpawn(); }) }, - pTargetDeathTracker_{ std::make_unique([this] { SignalDeath(); }) } - { - // TODO: check return and log/throw - SetConsoleCtrlHandler(ConsoleWaitControl::CtrlHandler, TRUE); - } - ConsoleWaitControl::~ConsoleWaitControl() - { - SetConsoleCtrlHandler(ConsoleWaitControl::CtrlHandler, FALSE); - } - ConsoleWaitControl::WakeReason ConsoleWaitControl::WaitFor(std::chrono::milliseconds ms) - { - std::unique_lock lk{ mtx_ }; - cv_.wait_for(lk, ms, [this] { return EventPending(); }); - return ExtractWakeReason(); - } - ConsoleWaitControl::WakeReason ConsoleWaitControl::Wait() - { - std::unique_lock lk{ mtx_ }; - cv_.wait(lk, [this] { return EventPending(); }); - return ExtractWakeReason(); - } - void ConsoleWaitControl::AddProcessSpawnWatch(std::string processName) - { - pProcessTracker_->AddWatch(std::move(processName)); - } - void ConsoleWaitControl::RemoveProcessSpawnWatch(const std::string& processName) - { - pProcessTracker_->RemoveWatch(processName); - } - void ConsoleWaitControl::SetTimer(std::chrono::milliseconds ms) - { - if (!timerThread_.joinable()) { - timerThread_ = std::jthread{ &ConsoleWaitControl::TimerKernel, this, ms }; - } - else { - throw std::logic_error{ "Trying to set console wait control timer twice" }; - } - } - std::optional ConsoleWaitControl::GetSpawnEvent() - { - std::unique_lock lk{ mtx_ }; - return pProcessTracker_->GetMatchEvent(); - } - void ConsoleWaitControl::AddProcessDeathWatch(uint32_t pid) - { - std::unique_lock lk{ mtx_ }; - pTargetDeathTracker_->Add(pid); - } - std::optional ConsoleWaitControl::GetProcessDeathEvent() - { - std::unique_lock lk{ mtx_ }; - return pTargetDeathTracker_->GetDeathEvent(); - } - void ConsoleWaitControl::Exit() - { - exitSignal_.release(); - } - ConsoleWaitControl& ConsoleWaitControl::Get() - { - static ConsoleWaitControl singleton; - return singleton; - } - void ConsoleWaitControl::SimulateSpawnEvent(p2c::win::Process process) - { - pProcessTracker_->SimulateSpawnEvent(std::move(process)); - } - void ConsoleWaitControl::NotifyEndOfLogEvent() - { - { - std::lock_guard lk{ mtx_ }; - endOfLOgEventPending_ = true; - } - cv_.notify_all(); - } - int __stdcall ConsoleWaitControl::CtrlHandler(unsigned long type) - { - return Get().SignalCtrl(type); - } - int ConsoleWaitControl::SignalCtrl(unsigned long type) - { - { - std::lock_guard lk{ mtx_ }; - dying_ = true; - } - // unblock main thread with termination reason - cv_.notify_all(); - // wait 1s for ack from main thread - (void)exitSignal_.try_acquire_for(1s); - // returning here will kill process, either after main thread ack or 1s timeout - return TRUE; - } - void ConsoleWaitControl::SignalSpawn() - { - { - std::lock_guard lk{ mtx_ }; - processSpawnEventPending_ = true; - } - // unblock main thread with process spawn reason - cv_.notify_all(); - } - - void ConsoleWaitControl::SignalDeath() - { - { - std::lock_guard lk{ mtx_ }; - processDeathEventPending_ = true; - } - // unblock main thread with process spawn reason - cv_.notify_all(); - } - - void ConsoleWaitControl::TimerKernel(std::chrono::milliseconds ms) - { - auto st = timerThread_.get_stop_token(); - std::condition_variable_any cv; - std::unique_lock lk{ mtx_ }; - cv.wait_for(lk, st, ms, [] { return false; }); - if (!st.stop_requested()) { - timerEventPending_ = true; - lk.unlock(); - cv_.notify_all(); - } - } - ConsoleWaitControl::WakeReason ConsoleWaitControl::ExtractWakeReason() - { - if (dying_) { - return WakeReason::CtrlTermination; - } - else if (endOfLOgEventPending_) { - return WakeReason::EndOfLog; - } - else if (processSpawnEventPending_) { - processSpawnEventPending_ = false; - return WakeReason::ProcessSpawn; - } - else if (timerEventPending_) { - timerThread_.join(); - timerEventPending_ = false; - return WakeReason::TimerEvent; - } - else if (processDeathEventPending_) { - processDeathEventPending_ = false; - return WakeReason::ProcessDeath; - } - else { - return WakeReason::Timeout; - } - } - bool ConsoleWaitControl::EventPending() const - { - return dying_ || processSpawnEventPending_ || timerEventPending_ || - processDeathEventPending_ || endOfLOgEventPending_; - } -} - diff --git a/IntelPresentMon/CliCore/source/cons/ConsoleWaitControl.h b/IntelPresentMon/CliCore/source/cons/ConsoleWaitControl.h deleted file mode 100644 index 27af86b0..00000000 --- a/IntelPresentMon/CliCore/source/cons/ConsoleWaitControl.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include - -namespace p2c::cli::cons -{ - class ProcessTracker; - class TargetDeathTracker; - - class ConsoleWaitControl - { - friend ProcessTracker; - friend TargetDeathTracker; - public: - // types - enum class WakeReason - { - Timeout, - ProcessSpawn, - CtrlTermination, - TimerEvent, - ProcessDeath, - EndOfLog, - }; - // functions - ConsoleWaitControl(const ConsoleWaitControl&) = delete; - ConsoleWaitControl& operator=(const ConsoleWaitControl&) = delete; - ~ConsoleWaitControl(); - WakeReason WaitFor(std::chrono::milliseconds ms); - WakeReason Wait(); - void AddProcessSpawnWatch(std::string processName); - void RemoveProcessSpawnWatch(const std::string& processName); - void SetTimer(std::chrono::milliseconds ms); - std::optional GetSpawnEvent(); - void AddProcessDeathWatch(uint32_t pid); - std::optional GetProcessDeathEvent(); - void Exit(); - static ConsoleWaitControl& Get(); - void SimulateSpawnEvent(p2c::win::Process process); - void NotifyEndOfLogEvent(); - bool EventPending() const; - private: - // functions - ConsoleWaitControl(); - // TODO: consider race condition where main thread exists normally - // just as ctrl command triggers handler (delayed wait to exit) - static int __stdcall CtrlHandler(unsigned long type); - int SignalCtrl(unsigned long type); - void SignalSpawn(); - void SignalDeath(); - void TimerKernel(std::chrono::milliseconds ms); - WakeReason ExtractWakeReason(); - // data - std::condition_variable cv_; - std::mutex mtx_; - bool dying_ = false; - bool processSpawnEventPending_ = false; - bool timerEventPending_ = false; - bool processDeathEventPending_ = false; - bool endOfLOgEventPending_ = false; - std::binary_semaphore exitSignal_{ 0 }; - std::unique_ptr pProcessTracker_; - std::unique_ptr pTargetDeathTracker_; - std::jthread timerThread_; - }; -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/cons/OptionalConsoleOut.h b/IntelPresentMon/CliCore/source/cons/OptionalConsoleOut.h deleted file mode 100644 index bbfe68cf..00000000 --- a/IntelPresentMon/CliCore/source/cons/OptionalConsoleOut.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once -#include - - -namespace p2c::cli::cons -{ - class OptionalConsoleOut - { - public: - OptionalConsoleOut(bool enabled) - : - enabled_(enabled) - {} - template - OptionalConsoleOut& operator<<(T&& val) - { - if (enabled_) { - std::cout << std::forward(val); - } - return *this; - } - OptionalConsoleOut& operator<<(std::ostream& (*manip)(std::ostream&)) - { - if (enabled_) { - std::cout << manip; - } - return *this; - } - private: - bool enabled_; - }; -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/dat/ColumnGroups.h b/IntelPresentMon/CliCore/source/dat/ColumnGroups.h deleted file mode 100644 index 8ccccf67..00000000 --- a/IntelPresentMon/CliCore/source/dat/ColumnGroups.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#define COLUMN_GROUP_LIST \ -X_(track_gpu, "Duration of each process' GPU work performed between presents. No Win7 support.") \ -X_(track_gpu_video, "Duration of each process' video GPU work performed between presents. No Win7 support.") \ -X_(track_input, "Time of keyboard/mouse clicks that were used by each frame.") \ -X_(track_gpu_telemetry, "GPU telemetry relating to power, temperature, frequency, clock speed, etc.") \ -X_(track_vram_telemetry, "VRAM telemetry relating to power, temperature, frequency, etc.") \ -X_(track_gpu_memory, "GPU memory utilization.") \ -X_(track_gpu_fan, "GPU fanspeeds.") \ -X_(track_gpu_psu, "GPU PSU information.") \ -X_(track_perf_limit, "Flags denoting current reason for performance limitation.") \ -X_(track_cpu_telemetry, "CPU telemetry relating to power, temperature, frequency, clock speed, etc.") \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/dat/Columns.h b/IntelPresentMon/CliCore/source/dat/Columns.h deleted file mode 100644 index 626ea497..00000000 --- a/IntelPresentMon/CliCore/source/dat/Columns.h +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once - -#define COLUMN_LIST \ -X_("Application", "", application,,, core) \ -X_("ProcessID", "", process_id,,, core) \ -X_("SwapChainAddress", "", swap_chain_address,,, core) \ -X_("Runtime", "", runtime,,, core) \ -X_("SyncInterval", "", sync_interval,,, core) \ -X_("PresentFlags", "", present_flags,,, core) \ -X_("Dropped", "", dropped,,, core) \ -X_("TimeInSeconds", "[s]", time_in_seconds,,, core) \ -X_("QPCTime", "", qpc_time,,, core) \ -X_("msInPresentAPI", "[ms]", ms_in_present_api,,, core) \ -X_("msBetweenPresents", "[ms]", ms_between_presents,,, core) \ -X_("AllowsTearing", "", allows_tearing,,, core) \ -X_("PresentMode", "", present_mode, TransformPresentMode,, core) \ -X_("msUntilRenderComplete", "[ms]", ms_until_render_complete,,, core) \ -X_("msUntilDisplayed", "[ms]", ms_until_displayed,,, core) \ -X_("msBetweenDisplayChange", "[ms]", ms_between_display_change,,, core) \ -X_("msUntilRenderStart", "[ms]", ms_until_render_start,,, track_gpu) \ -X_("msGPUActive", "[ms]", ms_gpu_active,,, track_gpu) \ -X_("msGPUVideoActive", "[ms]", ms_gpu_video_active,,, track_gpu_video) \ -X_("msSinceInput", "[ms]", ms_since_input,,, track_input) \ -X_("GPUPower", "[W]", gpu_power_w,,, track_gpu_telemetry) \ -X_("GPUSustainedPowerLimit", "[W]", gpu_sustained_power_limit_w,,, track_gpu_telemetry) \ -X_("GPUVoltage", "[V]", gpu_voltage_v,,, track_gpu_telemetry) \ -X_("GPUFrequency", "[MHz]", gpu_frequency_mhz,,, track_gpu_telemetry) \ -X_("GPUTemperature", "[C]", gpu_temperature_c,,, track_gpu_telemetry) \ -X_("GPUUtilization", "[%]", gpu_utilization,,, track_gpu_telemetry) \ -X_("GPURenderComputeUtilization", "[%]", gpu_render_compute_utilization,,, track_gpu_telemetry) \ -X_("GPUMediaUtilization", "[%]", gpu_media_utilization,,, track_gpu_telemetry) \ -X_("VRAMPower", "[W]", vram_power_w,,, track_vram_telemetry) \ -X_("VRAMVoltage", "[V]", vram_voltage_v,,, track_vram_telemetry) \ -X_("VRAMFrequency", "[MHz]", vram_frequency_mhz,,, track_vram_telemetry) \ -X_("VRAMEffectiveFrequency", "[Gbps]", vram_effective_frequency_gbs,,, track_vram_telemetry) \ -X_("VRAMTemperature", "[C]", vram_temperature_c,,, track_vram_telemetry) \ -X_("GPUMemTotalSize", "[B]", gpu_mem_total_size_b,,, track_gpu_memory) \ -X_("GPUMemUsed", "[B]", gpu_mem_used_b,,, track_gpu_memory) \ -X_("GPUMemMaxBandwidth", "[bps]", gpu_mem_max_bandwidth_bps,,, track_gpu_memory) \ -X_("GPUMemReadBandwidth", "[bps]", gpu_mem_read_bandwidth_bps,,, track_gpu_memory) \ -X_("GPUMemWriteBandwidth", "[bps]", gpu_mem_write_bandwidth_bps,,, track_gpu_memory) \ -X_("GPUFanSpeed0", "[rpm]", fan_speed_rpm,, [0], track_gpu_fan) \ -X_("GPUFanSpeed1", "[rpm]", fan_speed_rpm,, [1], track_gpu_fan) \ -X_("GPUFanSpeed2", "[rpm]", fan_speed_rpm,, [2], track_gpu_fan) \ -X_("GPUFanSpeed3", "[rpm]", fan_speed_rpm,, [3], track_gpu_fan) \ -X_("GPUFanSpeed4", "[rpm]", fan_speed_rpm,, [4], track_gpu_fan) \ -X_("PSUType0", "", psu_type,, [0], track_gpu_psu) \ -X_("PSUType1", "", psu_type,, [1], track_gpu_psu) \ -X_("PSUType2", "", psu_type,, [2], track_gpu_psu) \ -X_("PSUType3", "", psu_type,, [3], track_gpu_psu) \ -X_("PSUType4", "", psu_type,, [4], track_gpu_psu) \ -X_("PSUPower0", "[W]", psu_power,, [0], track_gpu_psu) \ -X_("PSUPower1", "[W]", psu_power,, [1], track_gpu_psu) \ -X_("PSUPower2", "[W]", psu_power,, [2], track_gpu_psu) \ -X_("PSUPower3", "[W]", psu_power,, [3], track_gpu_psu) \ -X_("PSUPower4", "[W]", psu_power,, [4], track_gpu_psu) \ -X_("PSUVoltage0", "[V]", psu_voltage,, [0], track_gpu_psu) \ -X_("PSUVoltage1", "[V]", psu_voltage,, [1], track_gpu_psu) \ -X_("PSUVoltage2", "[V]", psu_voltage,, [2], track_gpu_psu) \ -X_("PSUVoltage3", "[V]", psu_voltage,, [3], track_gpu_psu) \ -X_("PSUVoltage4", "[V]", psu_voltage,, [4], track_gpu_psu) \ -X_("GPUPowerLimited", "", gpu_power_limited,,, track_perf_limit) \ -X_("GPUTemperatureLimited", "", gpu_temperature_limited,,, track_perf_limit) \ -X_("GPUCurrentLimited", "", gpu_current_limited,,, track_perf_limit) \ -X_("GPUVoltageLimited", "", gpu_voltage_limited,,, track_perf_limit) \ -X_("GPUUtilizationLimited", "", gpu_utilization_limited,,, track_perf_limit) \ -X_("VRAMPowerLimited", "", vram_power_limited,,, track_perf_limit) \ -X_("VRAMTemperatureLimited", "", vram_temperature_limited,,, track_perf_limit) \ -X_("VRAMCurrentLimited", "", vram_current_limited,,, track_perf_limit) \ -X_("VRAMVoltageLimited", "", vram_voltage_limited,,, track_perf_limit) \ -X_("VRAMUtilizationLimited", "", vram_utilization_limited,,, track_perf_limit) \ -X_("CPUUtilization", "[%]", cpu_utilization,,, track_cpu_telemetry) \ -X_("CPUPower", "[W]", cpu_power_w,,, track_cpu_telemetry) \ -X_("CPUPowerLimit", "[W]", cpu_power_limit_w,,, track_cpu_telemetry) \ -X_("CPUTemperature", "[C]", cpu_temperature_c,,, track_cpu_telemetry) \ -X_("CPUFrequency", "[GHz]", cpu_frequency,,, track_cpu_telemetry) \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/dat/CsvWriter.cpp b/IntelPresentMon/CliCore/source/dat/CsvWriter.cpp deleted file mode 100644 index e03f75d2..00000000 --- a/IntelPresentMon/CliCore/source/dat/CsvWriter.cpp +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#include "CsvWriter.h" -#include -#include -#include -#include -#include "Columns.h" -#include "ColumnGroups.h" - - -namespace p2c::cli::dat -{ - struct GroupFlags - { - bool core = true; -#define X_(name, description) bool name; - COLUMN_GROUP_LIST -#undef X_ - }; - - CsvWriter::~CsvWriter() = default; - CsvWriter::CsvWriter(CsvWriter&&) = default; - CsvWriter& CsvWriter::operator=(CsvWriter&&) = default; - - CsvWriter::CsvWriter(std::string path, const std::vector& groups, bool writeStdout) - : - writeStdout_{ writeStdout } - { - if (!path.empty()) { - file_.open(path, std::ios::trunc); - if (!file_) { - throw std::runtime_error{ "Failed to open file for writing: " + path }; - } - } - - // setup group flags - std::set groupSet{ groups.begin(), groups.end() }; - pGroupFlags_ = std::make_unique(); -#define X_(name, description) pGroupFlags_->name = groupSet.contains("all") || groupSet.contains(#name); - COLUMN_GROUP_LIST -#undef X_ - - int col = 0; - // write header -#define X_(name, unit, symbol, transform, index, group) if (pGroupFlags_->group) { if (col++) buffer_ << ','; buffer_ << name; } - COLUMN_LIST -#undef X_ - buffer_ << "\n"; - - Flush(); - } - - void CsvWriter::Process(const PM_FRAME_DATA& frame) - { - const auto TransformPresentMode = [](PM_PRESENT_MODE mode) { - return infra::util::ToNarrow(pmon::PresentModeToString(pmon::ConvertPresentMode(mode))); - }; - - int col = 0; -#define X_(name, unit, symbol, transform, index, group) if (pGroupFlags_->group) { if (col++) buffer_ << ','; buffer_ << transform(frame.symbol index); } - COLUMN_LIST -#undef X_ - buffer_ << "\n"; - - Flush(); - } - - void CsvWriter::Flush() - { - const auto string = buffer_.GetString(); - if (writeStdout_) { - std::cout << string; - } - if (file_) { - file_ << string; - } - buffer_.Clear(); - } -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/dat/CsvWriter.h b/IntelPresentMon/CliCore/source/dat/CsvWriter.h deleted file mode 100644 index 5868716a..00000000 --- a/IntelPresentMon/CliCore/source/dat/CsvWriter.h +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once -#include -#include -#include -#include -#include "FrameSink.h" -#include - -namespace p2c::cli::dat -{ - struct GroupFlags; - - template - concept PresentMonOptionalStrict_ = - std::same_as || - std::same_as || - std::same_as || - std::same_as; - - template - concept PresentMonOptional = PresentMonOptionalStrict_>; - - class CsvWriter : public FrameSink - { - public: - CsvWriter(std::string path, const std::vector& groups, bool writeStdout = false); - CsvWriter(const CsvWriter&) = delete; - CsvWriter(CsvWriter&&); - CsvWriter& operator=(const CsvWriter&) = delete; - CsvWriter& operator=(CsvWriter&&); - ~CsvWriter(); - void Process(const struct PM_FRAME_DATA& frame) override; - private: - // types - class Buffer_ - { - public: - template - Buffer_& operator<<(const T& input) - { - if (!input.valid) { - stream_ << "NA"; - } - else { - stream_ << input.data; - } - return *this; - } - template - Buffer_& operator<<(T&& input) - { - stream_ << std::forward(input); - return *this; - } - std::string GetString() const - { - return stream_.str(); - } - void Clear() - { - stream_.str({}); - stream_.clear(); - } - private: - std::stringstream stream_; - }; - // functions - void Flush(); - // data - std::unique_ptr pGroupFlags_; - Buffer_ buffer_; - std::ofstream file_; - bool writeStdout_; - }; -} diff --git a/IntelPresentMon/CliCore/source/dat/FrameDemultiplexer.cpp b/IntelPresentMon/CliCore/source/dat/FrameDemultiplexer.cpp deleted file mode 100644 index 191a8a7e..00000000 --- a/IntelPresentMon/CliCore/source/dat/FrameDemultiplexer.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#include "FrameDemultiplexer.h" -#include -#include -#include -#include "MakeCsvName.h" - -namespace p2c::cli::dat -{ - FrameDemultiplexer::FrameDemultiplexer(std::vector groups, std::optional customFileName) - : - customFileName_{ std::move(customFileName) }, - groups_{ std::move(groups) } - {} - void FrameDemultiplexer::Process(const PM_FRAME_DATA& frame) - { - if (auto i = procs_.find(frame.process_id); i != procs_.end()) { - i->second->Process(frame); - } - else { - auto processName = std::filesystem::path{ frame.application }.filename().string(); - auto fileName = MakeCsvName(false, frame.process_id, std::move(processName), customFileName_); - auto pWriter = std::make_shared(std::move(fileName), groups_); - pWriter->Process(frame); - procs_.emplace(frame.process_id, std::move(pWriter)); - } - } -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/dat/FrameDemultiplexer.h b/IntelPresentMon/CliCore/source/dat/FrameDemultiplexer.h deleted file mode 100644 index d1308fce..00000000 --- a/IntelPresentMon/CliCore/source/dat/FrameDemultiplexer.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once -#include "FrameSink.h" -#include "CsvWriter.h" -#include -#include -#include -#include - -namespace p2c::cli::dat -{ - // TODO: add ability to configure file names better - class FrameDemultiplexer : public FrameSink - { - public: - FrameDemultiplexer(std::vector groups, std::optional customFileName); - void Process(const PM_FRAME_DATA& frame) override; - private: - std::unordered_map> procs_; - std::vector groups_; - std::optional customFileName_; - }; -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/dat/FrameFilterPump.cpp b/IntelPresentMon/CliCore/source/dat/FrameFilterPump.cpp deleted file mode 100644 index d4bda53c..00000000 --- a/IntelPresentMon/CliCore/source/dat/FrameFilterPump.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include "FrameFilterPump.h" -#include -#include -#include - -namespace p2c::cli::dat -{ - namespace rn = std::ranges; - namespace vi = rn::views; - - FrameFilterPump::FrameFilterPump( - std::shared_ptr pSource, - std::shared_ptr pSink, - const std::vector& excludes, - const std::vector& includes, - bool excludeDropped, - bool ignoreCase) - : - pSource_{ std::move(pSource) }, - pSink_{ std::move(pSink) }, - excludes_{ excludes.begin(), excludes.end() }, - includes_{ includes.begin(), includes.end() }, - excludeDropped_{ excludeDropped }, - ignoreCase_{ ignoreCase } - {} - void FrameFilterPump::AddInclude(uint32_t pid) - { - includes_.insert(pid); - } - void FrameFilterPump::Process(double timestamp) - { - for (auto& f : pSource_->Pull(timestamp)) { - // skip rows for applications in the exclude list - if (!excludes_.empty()) { - auto appName = std::filesystem::path{ f.application }.filename().string(); - if (ignoreCase_) { - appName = appName - | vi::transform([](char c) {return(char)std::tolower(c); }) - | rn::to(); - } - if (excludes_.contains(appName)) { - continue; - } - } - // skip rows for pid NOT in the include list - if (!includes_.empty()) { - if (!includes_.contains(f.process_id)) { - continue; - } - } - // skip dropped frames - if (excludeDropped_) { - if (f.dropped) { - continue; - } - } - pSink_->Process(f); - } - } - uint32_t FrameFilterPump::GetPid() const - { - return pSource_->GetPid(); - } -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/dat/FrameFilterPump.h b/IntelPresentMon/CliCore/source/dat/FrameFilterPump.h deleted file mode 100644 index d73a8bab..00000000 --- a/IntelPresentMon/CliCore/source/dat/FrameFilterPump.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once -#include "FrameSink.h" -#include -#include -#include -#include - -namespace p2c::cli::pmon -{ - class FrameDataStream; -} - -namespace p2c::cli::dat -{ - class FrameFilterPump - { - public: - FrameFilterPump( - std::shared_ptr pSource, - std::shared_ptr pSink, - const std::vector& excludes, - const std::vector& includes, - bool excludeDropped, - bool ignoreCase); - void AddInclude(uint32_t pid); - void Process(double timestamp); - uint32_t GetPid() const; - private: - std::shared_ptr pSource_; - std::shared_ptr pSink_; - // TODO: add exclude-by-pid to speed up filtering - std::unordered_set excludes_; - // TODO: use includes for an early-pass in non-whitelist situations - std::unordered_set includes_; - bool ignoreCase_; - bool excludeDropped_; - }; -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/dat/FrameSink.h b/IntelPresentMon/CliCore/source/dat/FrameSink.h deleted file mode 100644 index 583f8438..00000000 --- a/IntelPresentMon/CliCore/source/dat/FrameSink.h +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once - -struct PM_FRAME_DATA; - -namespace p2c::cli::dat -{ - class FrameSink - { - public: - virtual void Process(const PM_FRAME_DATA&) = 0; - }; -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/dat/MakeCsvName.cpp b/IntelPresentMon/CliCore/source/dat/MakeCsvName.cpp deleted file mode 100644 index f183f685..00000000 --- a/IntelPresentMon/CliCore/source/dat/MakeCsvName.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "MakeCsvName.h" -#include -#include - -using namespace std::string_literals; - -namespace p2c::cli::dat -{ - std::string MakeCsvName(bool noCsv, std::optional pid, std::optional processName, std::optional customName) - { - if (noCsv) { - return {}; - } - std::string customPrefix; - std::string customExtension; - if (customName) { - std::filesystem::path customPath{ *customName }; - if (customPath.has_parent_path()) { - customPrefix += customPath.parent_path().string() + "\\"; - } - if (customPath.has_stem()) { - customPrefix += customPath.stem().string(); - } - else { - customPrefix += "PresentMon"; - } - if (customPath.has_extension()) { - customExtension = customPath.extension().string(); - } - else { - customExtension = ".csv"s; - } - } - const std::chrono::zoned_time timestamp{ - std::chrono::current_zone(), - std::chrono::system_clock::now() - }; - std::stringstream buf; - if (customName) { - buf << customPrefix; - } - else { - buf << "PresentMon"; - } - if (processName) { - buf << "-" << *processName; - } - if (processName && pid) { - buf << std::format("[{}]", *pid); - } - else if (pid) { - buf << std::format("-[{}]", *pid); - } - buf << std::format("-{0:%y}{0:%m}{0:%d}-{0:%H}{0:%M}{0:%OS}", timestamp); - if (customName) { - buf << customExtension; - } - else { - buf << ".csv"; - } - return buf.str(); - } -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/dat/MakeCsvName.h b/IntelPresentMon/CliCore/source/dat/MakeCsvName.h deleted file mode 100644 index f52e64ec..00000000 --- a/IntelPresentMon/CliCore/source/dat/MakeCsvName.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once -#include -#include - -namespace p2c::cli::dat -{ - std::string MakeCsvName(bool noCsv, std::optional pid, std::optional processName, std::optional customName); -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/opt/Framework.cpp b/IntelPresentMon/CliCore/source/opt/Framework.cpp deleted file mode 100644 index 745ec8e4..00000000 --- a/IntelPresentMon/CliCore/source/opt/Framework.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "Framework.h" -#include - - -namespace p2c::cli::opt::impl -{ - std::optional AppBase_::app_; - std::optional AppBase_::line_; - const char* AppBase_::group_ = nullptr; - - void AppBase_::InitInternal_(int argc, char** argv) - { - assert(!line_); - line_.emplace(argc, argv); - assert(!app_); - app_.emplace(); - } - - CLI::App& AppBase_::GetApp_() - { - assert(app_); - return *app_; - } - - void AppBase_::Finalize_(const OptionStructBase_& options) - { - auto& app = GetApp_(); - assert(!app.parsed()); - - app.description(options.GetDescription()); - app.name(options.GetName()); - try { - app.parse(line_->argc, line_->argv); - } - catch (const CLI::ParseError& e) { - app.exit(e); - throw; - }; - } - - - Flag::Flag(std::string names, std::string description) - { - pOption_ = AppBase_::GetApp_().add_flag(std::move(names), value_, std::move(description)); - if (AppBase_::group_) { - pOption_->group(AppBase_::group_); - } - } - - Flag::operator bool() const - { - return value_; - } - - CLI::Option* Flag::opt() const { return pOption_; } - - Group::Group(const char* name) - { - AppBase_::group_ = name; - } -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/opt/Framework.h b/IntelPresentMon/CliCore/source/opt/Framework.h deleted file mode 100644 index 5543ecb9..00000000 --- a/IntelPresentMon/CliCore/source/opt/Framework.h +++ /dev/null @@ -1,147 +0,0 @@ -#pragma once -#include -#include -#include -#include - -namespace p2c::cli::opt::impl -{ - struct OptionStructBase_ - { - protected: - friend class AppBase_; - virtual std::string GetDescription() const = 0; - virtual std::string GetName() const = 0; - }; - - class AppBase_ - { - template - friend class Option; - friend class Flag; - friend class Group; - protected: - AppBase_() = default; - static void InitInternal_(int argc, char** argv); - static CLI::App& GetApp_(); - static void Finalize_(const OptionStructBase_&); - private: - // types - struct CommandLine - { - int argc; - char** argv; - }; - // data - static std::optional app_; - static std::optional line_; - static const char* group_; - }; - - template concept OptionStructable = std::derived_from; - - template - class App : public AppBase_ - { - public: - static void Init(int argc, char** argv) - { - InitInternal_(argc, argv); - GetOptions(); - } - static OptionStruct& GetOptions() - { - static App singleton; - return singleton.options_; - } - static bool CheckOptionPresence(const std::string& key) - { - const auto pOption = GetApp_()[key]; - return pOption && pOption->count() > 0; - } - private: - App() { Finalize_(options_); } - OptionStruct options_; - }; - - class Group - { - public: - Group(const char* name); - private: - OptionStructBase_* optionStruct_ = nullptr; - }; - - template - class Option - { - public: - template - Option(std::string names, std::string description, U&& customizer) - { - pOption_ = AppBase_::GetApp_().add_option(std::move(names), value_, std::move(description)); - if (AppBase_::group_) { - pOption_->group(AppBase_::group_); - } - // if the customizer returns a T, it is the default value - using R = std::invoke_result_t; - if constexpr (std::is_same_v) { - value_ = customizer(pOption_); - } - else { - customizer(pOption_); - } - } - Option(std::string names, std::string description) - { - pOption_ = AppBase_::GetApp_().add_option(std::move(names), value_, std::move(description)); - if (AppBase_::group_) { - pOption_->group(AppBase_::group_); - } - } - operator bool() const - { - return (bool)*pOption_; - } - bool operator!() const - { - return !bool(*this); - } - const T& operator*() const - { - return value_; - } - CLI::Option* opt() const { return pOption_; } - private: - T value_{}; - CLI::Option* pOption_ = nullptr; - }; - - class Flag - { - public: - template - Flag(std::string names, std::string description, U&& customizer) - { - pOption_ = AppBase_::GetApp_().add_flag(std::move(names), value_, std::move(description)); - if (AppBase_::group_) { - pOption_->group(AppBase_::group_); - } - // if the customizer returns a bool, it is the default value - using R = std::invoke_result_t; - if constexpr (std::is_same_v) { - value_ = customizer(pOption_); - } - else { - customizer(pOption_); - } - } - Flag(std::string names, std::string description); - operator bool() const; - CLI::Option* opt() const; - private: - // data - bool value_ = false; - CLI::Option* pOption_ = nullptr; - }; -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/opt/Options.cpp b/IntelPresentMon/CliCore/source/opt/Options.cpp deleted file mode 100644 index 92f9fa8a..00000000 --- a/IntelPresentMon/CliCore/source/opt/Options.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once -#define PMCLI_OPTS_CPP -#include "Options.h" -#include - -namespace p2c::cli::opt::impl -{ - std::vector OptionsStruct::GetColumnGroups() const - { - std::vector groups{ -#define X_(name, desc) #name, - COLUMN_GROUP_LIST -#undef X_ - }; - std::erase_if(groups, [](const std::string& g) { - return !App::CheckOptionPresence("--" + g); - }); - return groups; - } -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/opt/Options.h b/IntelPresentMon/CliCore/source/opt/Options.h deleted file mode 100644 index 0ac97eab..00000000 --- a/IntelPresentMon/CliCore/source/opt/Options.h +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once -#include "Framework.h" -#include -#include -#include - -namespace p2c::cli::opt -{ - namespace impl - { - struct OptionsStruct : public OptionStructBase_ - { - // add options and switches here to augment the CLI - private: Group gGeneric{ "Options" }; public: - Flag log{ "-l, --log", "Enable logging (off by default)" }; - Option logLevel{ "--log_level", "Level to log at from 4 (most logging) to 0 (least logging, default)", [this](CLI::Option* p) { - p->needs(log.opt()); } }; - Option logPath{ "--log_path", "Directory to write logs to (defaults to working dir)", [this](CLI::Option* p) { - p->needs(log.opt()); } }; - - private: Group gTarget{ "Capture Target Options" }; public: - Flag captureAll{ "-A,--captureall", "Capture all processes presenting on system. Default behavior." }; - Option> name{ "--process_name", "Name(s) of process(es) to capture" }; - Option> excludeName{ "--exclude", "Name(s) of process(es) to exclude", [this](CLI::Option* p) { - p->excludes(name.opt()); } }; - Option> pid{ "--process_id", "ID(s) of process(es) to capture", [this](CLI::Option* p) { - p->check(CLI::PositiveNumber)->excludes(captureAll.opt(), name.opt(), excludeName.opt()); }}; - Option etlFile{ "--etl_file", "Source frame data from an externally-captured ETL file instead of live ETW events" }; - Flag ignoreCase{ "-i,--ignore_case", "Ignore case when matching a process name" }; - - private: Group gOutput{ "Output Options" }; public: - Option outputFile{ "-o,--output_file", "Path, root name, and extension used for CSV file(s)" }; - Flag outputStdout{ "-s,--output_stdout", "Write frame data to stdout instead of status messages" }; - Flag multiCsv{ "-m,--multi_csv", "Write frame data from each process to a separate CSV file", [this](CLI::Option* p) { - p->excludes(outputStdout.opt()); } }; - Flag noCsv{ "-v,--no_csv", "Disable CSV file output", [this](CLI::Option* p) { - p->needs(outputStdout.opt()); } }; - - private: Group gRecording{ "Recording Options" }; public: - Option startAfter{ "--delay", "Time in seconds from launch to start of capture" }; - Option captureFor{ "--timed", "Time in seconds from start of capture to exit" }; - Flag excludeDropped{ "-d,--exclude_dropped", "Exclude dropped frames from capture" }; - - private: Group gColumns{ "CSV Column Options" }; public: -#define X_(name, desc) Flag name{ "--" #name, desc }; - COLUMN_GROUP_LIST -#undef X_ - - private: Group gAdapters{ "Graphics Adapter Options" }; public: - Flag listAdapters{ "-a,--list_adapters", "List adaptors available for selection as telemetry source" }; - Option adapter{ "--adapter", "Index of adapter to use as telemetry source", [this](CLI::Option* p) { - p->check(CLI::NonNegativeNumber); } }; - - private: Group gHidden{ "" }; public: - Option telemetryPeriod{ "--telemetry_period", "Period that the service uses to poll hardware telemetry", [this](CLI::Option* p) { - p->check(CLI::PositiveNumber); } }; - Option pollPeriod{ "--poll_period", "Period that this client uses to poll the service for frame data", [this](CLI::Option* p) { - p->check(CLI::PositiveNumber); } }; - - // used for getting vector of enabled CSV column groups - std::vector GetColumnGroups() const; - - protected: - // edit application name and description here - std::string GetName() const override - { return "PresentMonCli"; } - std::string GetDescription() const override - { return "Command line interface for capturing frame presentation data using the PresentMon service."; } - }; - } - - inline void init(int argc, char** argv) { impl::App::Init(argc, argv); } - inline auto& get() { return impl::App::GetOptions(); } -} - -#ifndef PMCLI_OPTS_CPP -#undef COLUMN_GROUP_LIST -#endif \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/pmon/AdapterInfo.h b/IntelPresentMon/CliCore/source/pmon/AdapterInfo.h deleted file mode 100644 index b89578bf..00000000 --- a/IntelPresentMon/CliCore/source/pmon/AdapterInfo.h +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once -#include -#include - -namespace p2c::cli::pmon -{ - struct AdapterInfo - { - uint32_t id; - std::string vendor; - std::string name; - }; -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/pmon/Client.cpp b/IntelPresentMon/CliCore/source/pmon/Client.cpp deleted file mode 100644 index 6a6eadfd..00000000 --- a/IntelPresentMon/CliCore/source/pmon/Client.cpp +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#include "Client.h" -#include -#include -#include "stream/LiveFrameDataStream.h" -#include "stream/LoggedFrameDataStream.h" - -namespace p2c::cli::pmon -{ - Client::Client(uint32_t telemetrySamplePeriodMs) - { - if (auto sta = pmInitialize(nullptr); sta != PM_STATUS::PM_STATUS_SUCCESS) - { - p2clog.note(L"could not init pmon").code(sta).commit(); - } - SetGpuTelemetryPeriod(telemetrySamplePeriodMs); - } - Client::~Client() - { - if (auto sta = pmShutdown(); sta != PM_STATUS::PM_STATUS_SUCCESS) { - p2clog.warn(L"could not shutdown pmon").code(sta).commit(); - } - } - std::shared_ptr Client::OpenStream(uint32_t pid) - { - // if process exists in our map - if (auto i = streams_.find(pid); i != streams_.end()) { - // check if fresh - if (auto pStream = i->second.lock()) { - return pStream; - } - else { - // if stale, replace stale entry with new proc stream and return - pStream = MakeStream(pid); - i->second = pStream; - return pStream; - } - } - else { - auto pStream = MakeStream(pid); - streams_[pid] = pStream; - return pStream; - } - } - void Client::SetGpuTelemetryPeriod(uint32_t period) - { - if (auto sta = pmSetGPUTelemetryPeriod(period); sta != PM_STATUS::PM_STATUS_SUCCESS) - { - p2clog.warn(std::format(L"could not set gpu telemetry sample period to {}", period)).code(sta).commit(); - } - else - { - telemetrySamplePeriod_ = period; - } - } - uint32_t Client::GetGpuTelemetryPeriod() - { - return telemetrySamplePeriod_; - } - std::vector Client::EnumerateAdapters() const - { - uint32_t count = 0; - if (auto sta = pmEnumerateAdapters(nullptr, &count); sta != PM_STATUS::PM_STATUS_SUCCESS) - { - p2clog.note(L"could not query adapter count").code(sta).commit(); - } - std::vector buffer{ size_t(count) }; - if (auto sta = pmEnumerateAdapters(buffer.data(), &count); sta != PM_STATUS::PM_STATUS_SUCCESS) - { - p2clog.note(L"could not enumerate adapters").code(sta).commit(); - } - std::vector infos; - for (const auto& info : buffer) - { - const auto GetVendorName = [vendor = info.vendor] { - using namespace std::string_literals; - switch (vendor) { - case PM_GPU_VENDOR::PM_GPU_VENDOR_AMD: return "AMD"s; - case PM_GPU_VENDOR::PM_GPU_VENDOR_INTEL: return "Intel"s; - case PM_GPU_VENDOR::PM_GPU_VENDOR_NVIDIA: return "Nvidia"s; - default: return "Unknown"s; - } - }; - infos.push_back(AdapterInfo{ - .id = info.id, - .vendor = GetVendorName(), - .name = info.name, - }); - } - return infos; - } - void Client::SetAdapter(uint32_t id) - { - if (auto sta = pmSetActiveAdapter(id); sta != PM_STATUS::PM_STATUS_SUCCESS) - { - p2clog.note(L"could not set active adapter").code(sta).nox().commit(); - } - } - std::optional Client::GetSelectedAdapter() const - { - return selectedAdapter_; - } - - - - LiveClient::LiveClient(uint32_t telemetrySampleRateMs) - : - Client{ telemetrySampleRateMs } - {} - std::shared_ptr LiveClient::MakeStream(uint32_t pid) - { - return std::make_shared(pid); - } - - - - LoggedClient::LoggedClient(std::string filePath, cons::ConsoleWaitControl& waitControl, uint32_t telemetrySampleRateMs) - : - Client{ telemetrySampleRateMs }, - pLoggedState_{ std::make_shared(std::move(filePath), &waitControl) } - {} - std::shared_ptr LoggedClient::MakeStream(uint32_t pid) - { - return std::make_shared(pid, pLoggedState_); - } -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/pmon/Client.h b/IntelPresentMon/CliCore/source/pmon/Client.h deleted file mode 100644 index c3dff7a6..00000000 --- a/IntelPresentMon/CliCore/source/pmon/Client.h +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once -#include -#include -#include -#include -#include "AdapterInfo.h" -// adapters corresponding to pmapi structs/functions -#include "FrameDataStream.h" - -namespace p2c::cli::cons -{ - class ConsoleWaitControl; -} - -namespace p2c::cli::pmon -{ - namespace stream - { - class LoggedFrameDataState; - } - - class Client - { - public: - // types - using AdapterInfo = pmon::AdapterInfo; - // functions - Client(const Client&) = delete; - Client& operator=(const Client&) = delete; - virtual ~Client(); - std::shared_ptr OpenStream(uint32_t pid); - void SetGpuTelemetryPeriod(uint32_t period); - uint32_t GetGpuTelemetryPeriod(); - std::vector EnumerateAdapters() const; - void SetAdapter(uint32_t id); - std::optional GetSelectedAdapter() const; - protected: - Client(uint32_t telemetrySampleRateMs); - virtual std::shared_ptr MakeStream(uint32_t pid) = 0; - private: - std::unordered_map> streams_; - uint32_t telemetrySamplePeriod_ = 0; - std::optional selectedAdapter_; - }; - - class LiveClient : public Client - { - public: - LiveClient(uint32_t telemetrySampleRateMs = 16); - protected: - std::shared_ptr MakeStream(uint32_t pid) override; - }; - - class LoggedClient : public Client - { - public: - LoggedClient(std::string filePath, cons::ConsoleWaitControl& waitControl, uint32_t telemetrySampleRateMs = 16); - protected: - std::shared_ptr MakeStream(uint32_t pid) override; - private: - std::shared_ptr pLoggedState_; - }; -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/pmon/FrameDataStream.h b/IntelPresentMon/CliCore/source/pmon/FrameDataStream.h deleted file mode 100644 index 760df3e9..00000000 --- a/IntelPresentMon/CliCore/source/pmon/FrameDataStream.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once -#include -#include - -namespace p2c::cli::pmon -{ - class FrameDataStream - { - public: - using Struct = PM_FRAME_DATA; - virtual std::span Pull(double timestamp) = 0; - virtual uint32_t GetPid() const = 0; - virtual ~FrameDataStream() = default; - }; -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/pmon/PresentMode.cpp b/IntelPresentMon/CliCore/source/pmon/PresentMode.cpp deleted file mode 100644 index acb80f6a..00000000 --- a/IntelPresentMon/CliCore/source/pmon/PresentMode.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#define KEEP_PM_PRESENT_MODE_X_LIST -#include "PresentMode.h" - -namespace p2c::cli::pmon -{ - PresentMode ConvertPresentMode(PM_PRESENT_MODE mode) - { - return PresentMode(mode); - } - - std::wstring PresentModeToString(PresentMode mode) - { - switch (mode) - { -#define X_(a, b, c) case PresentMode::b: return c; - PM_PRESENT_MODE_X_LIST -#undef X_ - default: return L"Invalid"; - } - } -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/pmon/PresentMode.h b/IntelPresentMon/CliCore/source/pmon/PresentMode.h deleted file mode 100644 index 0ee7c124..00000000 --- a/IntelPresentMon/CliCore/source/pmon/PresentMode.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once -#include -#include -#include - -#define PM_PRESENT_MODE_X_LIST \ - X_(PM_PRESENT_MODE_HARDWARE_LEGACY_FLIP, HardwareLegacyFlip, L"HW Legacy Flip") \ - X_(PM_PRESENT_MODE_HARDWARE_LEGACY_COPY_TO_FRONT_BUFFER, HardwareLegacyCopyToFrontBuffer, L"HW Legacy Copy To Front Buffer") \ - X_(PM_PRESENT_MODE_HARDWARE_INDEPENDENT_FLIP, HardwareIndependentFlip, L"HW Independent Flip") \ - X_(PM_PRESENT_MODE_COMPOSED_FLIP, ComposedFlip, L"Composed Flip") \ - X_(PM_PRESENT_MODE_HARDWARE_COMPOSED_INDEPENDENT_FLIP, HardwareComposedIndependentFlip, L"HW Composed Independent Flip") \ - X_(PM_PRESENT_MODE_COMPOSED_COPY_WITH_GPU_GDI, ComposedCopyWithGpuGdi, L"Composed Copy With GPU GDI") \ - X_(PM_PRESENT_MODE_COMPOSED_COPY_WITH_CPU_GDI, ComposedCopyWithCpuGdi, L"Composed Copy With CPU GDI") \ - X_(PM_PRESENT_MODE_UNKNOWN, Unknown, L"Unknown") - -namespace p2c::cli::pmon -{ - enum class PresentMode : uint32_t - { -#define X_(a, b, c) b = uint32_t(a), - PM_PRESENT_MODE_X_LIST -#undef X_ - }; - - PresentMode ConvertPresentMode(PM_PRESENT_MODE mode); - std::wstring PresentModeToString(PresentMode mode); -} - -#ifndef KEEP_PM_PRESENT_MODE_X_LIST -#undef PM_PRESENT_MODE_X_LIST -#endif \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/pmon/stream/LiveFrameDataStream.cpp b/IntelPresentMon/CliCore/source/pmon/stream/LiveFrameDataStream.cpp deleted file mode 100644 index 82ab3ceb..00000000 --- a/IntelPresentMon/CliCore/source/pmon/stream/LiveFrameDataStream.cpp +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#include "LiveFrameDataStream.h" -#include -#include -#include - -namespace p2c::cli::pmon::stream -{ - LiveFrameDataStream::LiveFrameDataStream(uint32_t pid) - : - pid_(pid), - cache_(initialCacheSize_) - { - if (auto sta = pmStartStream(pid_); sta != PM_STATUS::PM_STATUS_SUCCESS) { - p2clog.note(std::format(L"could not start stream for pid {}", pid_)).code(sta).commit(); - } - p2clog.info(std::format(L"started pmon stream for pid {}", pid_)).commit(); - } - - LiveFrameDataStream::~LiveFrameDataStream() - { - if (auto sta = pmStopStream(pid_); sta != PM_STATUS::PM_STATUS_SUCCESS) { - p2clog.warn(std::format(L"could not stop stream for pid {}", pid_)).code(sta).commit(); - } - p2clog.info(std::format(L"stopped pmon stream for pid {}", pid_)).commit(); - } - - std::span LiveFrameDataStream::Pull(double timestamp) - { - if (!cacheTimestamp_ || cacheTimestamp_ != timestamp) - { - // in: max writeable buffer size, out: actual num frames written - auto frameCountInOut = (uint32_t)initialCacheSize_; - // cache buffer might have shrunk down real small, reset to initial size - cache_.resize(initialCacheSize_); - // where in vector to begin writing - uint32_t writePosition = 0; - while (true) { - if (auto sta = pmGetFrameData(pid_, &frameCountInOut, cache_.data() + writePosition); - sta == PM_STATUS::PM_STATUS_NO_DATA) { - // "shrink to fit" - cache_.resize(size_t(writePosition)); - break; - } - else if (sta != PM_STATUS::PM_STATUS_SUCCESS) { - p2clog.warn(std::format(L"failed to get raw frame data with error [{}]", pid_)).code(sta).commit(); - cache_.clear(); - break; - } - // case where there *might* be more data - // if the api writes to all available elements - // means there might have been exactly the needed number - // of elements in the buffer, or that there are more remaining - if (size_t(writePosition) + size_t(frameCountInOut) >= std::size(cache_)) { - // if we're full, we should have filled up to exact size of buffer - CORE_ASSERT(size_t(writePosition) + size_t(frameCountInOut) == std::size(cache_)); - // next frames write start just after end of current cache - writePosition = (uint32_t)std::size(cache_); - // double size of cache to accomodate more entries - cache_.resize(cache_.size() * 2); - // available space is total size of cache minus size already written - frameCountInOut = (uint32_t)std::size(cache_) - writePosition; - } - else { - // "shrink to fit" - cache_.resize(size_t(writePosition) + size_t(frameCountInOut)); - break; - } - } - cacheTimestamp_ = timestamp; - } - return cache_; - } - - uint32_t LiveFrameDataStream::GetPid() const - { - return pid_; - } -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/pmon/stream/LiveFrameDataStream.h b/IntelPresentMon/CliCore/source/pmon/stream/LiveFrameDataStream.h deleted file mode 100644 index 581a003a..00000000 --- a/IntelPresentMon/CliCore/source/pmon/stream/LiveFrameDataStream.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once -#include "../FrameDataStream.h" -#include -#include -#include -#include - -namespace p2c::cli::pmon -{ - class ProcessStream; -} - -namespace p2c::cli::pmon::stream -{ - class LiveFrameDataStream : public FrameDataStream - { - public: - LiveFrameDataStream(uint32_t pid); - std::span Pull(double timestamp) override; - uint32_t GetPid() const override; - LiveFrameDataStream(const LiveFrameDataStream&) = delete; - LiveFrameDataStream& operator=(const LiveFrameDataStream&) = delete; - ~LiveFrameDataStream() override; - private: - static constexpr size_t initialCacheSize_ = 120; - uint32_t pid_; - std::optional cacheTimestamp_; - std::vector cache_; - }; -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/pmon/stream/LoggedFrameDataState.cpp b/IntelPresentMon/CliCore/source/pmon/stream/LoggedFrameDataState.cpp deleted file mode 100644 index 96d4288a..00000000 --- a/IntelPresentMon/CliCore/source/pmon/stream/LoggedFrameDataState.cpp +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#include "LoggedFrameDataState.h" -#include -#include -#include -#include -#include -#include -#include - -namespace rn = std::ranges; -namespace vi = rn::views; - -namespace p2c::cli::pmon::stream -{ - LoggedFrameDataState::LoggedFrameDataState(std::string filePath, cons::ConsoleWaitControl* pWaitControl) - : - cache_(initialCacheSize_), - pWaitControl_{ pWaitControl } - { - if (auto sta = pmStartStreamEtl(filePath.c_str()); sta != PM_STATUS::PM_STATUS_SUCCESS) - { - p2clog.note(std::format(L"failed to open etl log file {} with error", - infra::util::ToWide(filePath))).code(sta).commit(); - } - } - - std::vector LoggedFrameDataState::Pull(double timestamp, uint32_t pid) - { - if (!cacheTimestamp_ || cacheTimestamp_ != timestamp) - { - // in: max writeable buffer size, out: actual num frames written - auto frameCountInOut = (uint32_t)initialCacheSize_; - // cache buffer might have shrunk down real small, reset to initial size - cache_.resize(initialCacheSize_); - // where in vector to begin writing - uint32_t writePosition = 0; - while (true) { - if (auto sta = pmGetEtlFrameData(&frameCountInOut, cache_.data() + writePosition); - sta == PM_STATUS::PM_STATUS_NO_DATA) { - // "shrink to fit" - cache_.resize(size_t(writePosition)); - break; - } - else if (sta == PM_STATUS::PM_STATUS_PROCESS_NOT_EXIST) { - pWaitControl_->NotifyEndOfLogEvent(); - break; - } - else if (sta != PM_STATUS::PM_STATUS_SUCCESS) { - p2clog.warn(std::format(L"failed to get etl log frame data with error")).code(sta).commit(); - cache_.clear(); - break; - } - // case where there *might* be more data - // if the api writes to all available elements - // means there might have been exactly the needed number - // of elements in the buffer, or that there are more remaining - if (size_t(writePosition) + size_t(frameCountInOut) >= std::size(cache_)) { - // if we're full, we should have filled up to exact size of buffer - CORE_ASSERT(size_t(writePosition) + size_t(frameCountInOut) == std::size(cache_)); - // next frames write start just after end of current cache - writePosition = (uint32_t)std::size(cache_); - // double size of cache to accomodate more entries - cache_.resize(cache_.size() * 2); - // available space is total size of cache minus size already written - frameCountInOut = (uint32_t)std::size(cache_) - writePosition; - } - else { - // "shrink to fit" - cache_.resize(size_t(writePosition) + size_t(frameCountInOut)); - break; - } - } - cacheTimestamp_ = timestamp; - // process all frames for simulating process spawn events - for (const auto& frame : cache_) { - if (auto&&[i, fresh] = pids_.insert(frame.process_id); fresh) { - pWaitControl_->SimulateSpawnEvent({ - .pid = frame.process_id, - .name = infra::util::ToWide(frame.application), - }); - } - } - } - // if pid is 0, return all frames - if (pid == 0) { - return cache_; - } - // filter out frames not for this pid - return cache_ | - vi::filter([pid](const auto& frame){ return frame.process_id == pid; }) | - rn::to(); - } -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/pmon/stream/LoggedFrameDataState.h b/IntelPresentMon/CliCore/source/pmon/stream/LoggedFrameDataState.h deleted file mode 100644 index 7550f262..00000000 --- a/IntelPresentMon/CliCore/source/pmon/stream/LoggedFrameDataState.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once -#include "../FrameDataStream.h" -#include -#include -#include -#include - -namespace p2c::cli::cons -{ - class ConsoleWaitControl; -} - -namespace p2c::cli::pmon::stream -{ - class LoggedFrameDataState - { - public: - LoggedFrameDataState(std::string filePath, cons::ConsoleWaitControl* pWaitControl); - std::vector Pull(double timestamp, uint32_t pid); - private: - static constexpr size_t initialCacheSize_ = 120; - std::optional cacheTimestamp_; - std::vector cache_; - cons::ConsoleWaitControl* pWaitControl_; - std::unordered_set pids_; - }; -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/pmon/stream/LoggedFrameDataStream.cpp b/IntelPresentMon/CliCore/source/pmon/stream/LoggedFrameDataStream.cpp deleted file mode 100644 index b9b410e1..00000000 --- a/IntelPresentMon/CliCore/source/pmon/stream/LoggedFrameDataStream.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#include "LoggedFrameDataStream.h" -#include -#include -#include - -namespace p2c::cli::pmon::stream -{ - LoggedFrameDataStream::LoggedFrameDataStream(uint32_t pid, std::shared_ptr pState) - : - pid_(pid), - cache_(initialCacheSize_), - pState_{ std::move(pState) } - {} - - std::span LoggedFrameDataStream::Pull(double timestamp) - { - if (!cacheTimestamp_ || cacheTimestamp_ != timestamp) - { - cache_ = pState_->Pull(timestamp, pid_); - cacheTimestamp_ = timestamp; - } - return cache_; - } - uint32_t LoggedFrameDataStream::GetPid() const - { - return pid_; - } -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/pmon/stream/LoggedFrameDataStream.h b/IntelPresentMon/CliCore/source/pmon/stream/LoggedFrameDataStream.h deleted file mode 100644 index 98924ed1..00000000 --- a/IntelPresentMon/CliCore/source/pmon/stream/LoggedFrameDataStream.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once -#include "../FrameDataStream.h" -#include "LoggedFrameDataState.h" -#include -#include -#include -#include -#include - -namespace p2c::cli::pmon -{ - class ProcessStream; -} - -namespace p2c::cli::pmon::stream -{ - class LoggedFrameDataStream : public FrameDataStream - { - public: - LoggedFrameDataStream(uint32_t pid, std::shared_ptr pState); - std::span Pull(double timestamp) override; - uint32_t GetPid() const override; - private: - static constexpr size_t initialCacheSize_ = 120; - uint32_t pid_; - std::shared_ptr pState_; - std::optional cacheTimestamp_; - std::vector cache_; - }; -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/svc/Boot.cpp b/IntelPresentMon/CliCore/source/svc/Boot.cpp deleted file mode 100644 index 617e456f..00000000 --- a/IntelPresentMon/CliCore/source/svc/Boot.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include "Boot.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace p2c::cli::svc -{ - void Boot(bool logging, std::optional logPath, std::optional logLevel) - { - using Services = infra::svc::Services; - -#ifdef NDEBUG - constexpr bool is_debug = false; -#else - constexpr bool is_debug = true; -#endif - - // appfolder and docfolder resolve to cwd - Services::Bind( - [] { return std::make_shared(L"", L"", false); } - ); - - if (!is_debug) { - const auto level = infra::log::Level(std::clamp(logLevel.value_or(0), 0, 3)); - infra::log::SetDefaultChannelFactory([=] { - auto pDefaultChannel = std::make_shared(); - if (logging) { - auto logFilePath = logPath - .transform([](auto& p) {return p + "\\pm-cli.log"; }) - .value_or("pm-cli.log"); - pDefaultChannel->AddDriver(std::make_unique(std::move(logFilePath))); - pDefaultChannel->AddPolicy({ [=](infra::log::EntryOutputBase& entry) { - return (int)entry.data.level <= (int)level; - } }); - } - return pDefaultChannel; - }); - const auto glogLevel = [&] { - switch (level) { - case infra::log::Level::Error: return 2; - case infra::log::Level::Warning: return 1; - case infra::log::Level::Info: return 0; - case infra::log::Level::Debug: return 0; - default: return 2; - } - }(); - if (logging && logPath) { - std::filesystem::create_directories(*logPath); - } - if (logging) { - pmInitializeLogging( - logPath.transform(&std::string::c_str).value_or("."), - "pm-sdk", - ".log", - glogLevel - ); - } - else { - pmInitializeLogging(nullptr, nullptr, nullptr, glogLevel); - } - } - else { - std::filesystem::create_directories("logs"); - pmInitializeLogging("logs", "pm-sdk", ".log", 0); - } - - // error code translators - infra::util::errtl::HResult::RegisterService(); - infra::util::errtl::PMStatus::RegisterService(); - } -} \ No newline at end of file diff --git a/IntelPresentMon/CliCore/source/svc/Boot.h b/IntelPresentMon/CliCore/source/svc/Boot.h deleted file mode 100644 index 9fe89535..00000000 --- a/IntelPresentMon/CliCore/source/svc/Boot.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once -#include -#include - -namespace p2c::cli::svc -{ - void Boot(bool logging, std::optional logPath, std::optional logLevel); -} \ No newline at end of file diff --git a/IntelPresentMon/PresentMonCli/PresentMonCli.cpp b/IntelPresentMon/PresentMonCli/PresentMonCli.cpp deleted file mode 100644 index 44ba1427..00000000 --- a/IntelPresentMon/PresentMonCli/PresentMonCli.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include -#include - -int main(int argc, char** argv) -{ - try { - return p2c::cli::Entry(argc, argv); - } - catch (...) { - puts("\nError: Unhandled exception.\n"); - return -1; - } -} \ No newline at end of file diff --git a/IntelPresentMon/PresentMonCli/PresentMonCli.vcxproj b/IntelPresentMon/PresentMonCli/PresentMonCli.vcxproj deleted file mode 100644 index 17a202ba..00000000 --- a/IntelPresentMon/PresentMonCli/PresentMonCli.vcxproj +++ /dev/null @@ -1,184 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {4197c787-370f-4886-a357-f8a5e1357a9c} - PresentMonCli - 10.0 - - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - - - - - - - - - - - - - - - - - - - - - - - ..\..\build\obj\$(ProjectName)-$(Platform)-$(Configuration)\ - ..\..\build\$(Configuration)\ - $(ProjectName)-x86 - - - ..\..\build\obj\$(ProjectName)-$(Platform)-$(Configuration)\ - ..\..\build\$(Configuration)\ - $(ProjectName)-x86 - - - ..\..\build\obj\$(ProjectName)-$(Platform)-$(Configuration)\ - ..\..\build\$(Configuration)\ - - - ..\..\build\obj\$(ProjectName)-$(Platform)-$(Configuration)\ - ..\..\build\$(Configuration)\ - - - false - - - false - - - false - - - false - - - - Level3 - true - true - stdcpplatest - MultiThreadedDebug - ..;%(AdditionalIncludeDirectories) - - - Console - true - - - - - Level3 - true - true - true - true - stdcpplatest - MultiThreaded - ..;%(AdditionalIncludeDirectories) - NDEBUG;%(PreprocessorDefinitions) - - - Console - true - true - true - - - - - Level3 - true - true - stdcpplatest - MultiThreadedDebug - ..;%(AdditionalIncludeDirectories) - - - Console - true - - - - - Level3 - true - true - true - true - stdcpplatest - MultiThreaded - ..;%(AdditionalIncludeDirectories) - NDEBUG;%(PreprocessorDefinitions) - - - Console - true - true - true - - - - - {31551c65-38ea-4e21-8c0d-4bc4a097993c} - - - - - - - - - \ No newline at end of file diff --git a/IntelPresentMon/PresentMonCli/README.md b/IntelPresentMon/PresentMonCli/README.md deleted file mode 100644 index 272b5ec7..00000000 --- a/IntelPresentMon/PresentMonCli/README.md +++ /dev/null @@ -1,282 +0,0 @@ -# PresentMon CLI - -The PresentMon CLI uses the PresentMon Service to report out ETW based frame metrics and sampled CPU/GPU telemetry. It shares many of the command line arguments as the original PresentMon application with additional arguments related to the sampled GPU/CPU telemetry. - -## License - -Copyright (C) 2017-2023 Intel Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -## Command line options - -| Capture Target Options | | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------- | -| `-A, --captureall` | Record all processes (default). | -| `--process_name name` | Record only processes with the provided exe name. This argument can be repeated to capture multiple processes. | -| `--exclude name` | Don't record processes with the provided exe name. This argument can be repeated to exclude multiple processes. | -| `--process_id id` | Record only the process specified by ID. | -| `--etl_file path` | Consume events from an ETW log file instead of running processes. | -| -i, --ignore_case | Ignore case when matching a process name | - -| Output Options | | -| ------------------- | ------------------------------------------------------------------------ | -| `-o, --output_file path` | Write CSV output to the provided path. | -| `-s, --output_stdout` | Write CSV output to STDOUT. | -| `-m, --multi_csv` | Create a separate CSV file for each captured process. | -| `-v, --no_csv` | Do not create any output file. | - -| Recording Options | | -| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -| `--delay seconds` | Wait for provided time before starting to record. | -| `--timed seconds` | Stop recording after the provided amount of time. | -| `-d, --exclude_dropped` | Exclude dropped presents from the csv output. | - -| GPU/CPU Options | | -|-----------------|----------------------------------------------------------------------------------------------| -| `--track_gpu_telemetry` | Tracks GPU telemetry relating to power, temperature, frequency, clock speed, etc. | -| `--track_vram_telemetry` | Tracks VRAM telemetry relating to power, temperature, frequency, etc. | -| `--track_gpu_memory` | Tracks GPU memory utilization | -| `--track_gpu_fan` | Tracks GPU fanspeeds | -| `--track_gpu_psu` | Tracks GPU PSU information | -| `--track_gpu_psu` | Tracks GPU PSU information | -| `--track_perf_limit` | Tracks flags denoting current reason for performance limitation | -| `--track_cpu_telemetry` | Tracks CPU telemetry relating to power, temperature, frequency, clock speed, etc. | -| `--track_powershare_bias` | Tracks powershare bias information. | - -| Beta Options | | -| ------------------------- | ------------------------------------------------------------------------------------------------------- | -| `--track_gpu` | Tracks the duration of each process' GPU work performed between presents. Not supported on Win7. | -| `--track_gpu_video` | Track the video encode/decode portion of GPU work separately from other engines. Not supported on Win7. | -| `--track_input` | Tracks the time of keyboard/mouse clicks that were used by each frame. | -| `-track_memory_residency` | Capture CPU time spent in memory residency and paging operations during each frame. | - -| Internal Options | | -| --------------------- | ---------------------------------------------------------- | -| `--track_queue_timers` | Capture Intel D3D11 driver producer/consumer queue timers. | -| `--track_cpu_gpu_sync` | Capture Intel D3D11 driver CPU/GPU syncs. | -| `--track_shader_compilation` | Capture Intel D3D11 driver shader compilation. | - -Note: internal options require a release-internal driver, with the GfxEvents manifest installed from its corresponding TestTools package. Some options may also require specific driver feature branches. - -## Comma-separated value (CSV) file output - -### CSV file names - -By default, PresentMon creates a CSV file named "PresentMon-\