Skip to content

Commit c4af934

Browse files
guhetierCopilot
andcommitted
Add QUIC_PARAM_GLOBAL_WORKER_STATISTICS for per-worker statistics
Adds a preview global parameter to retrieve per-worker active and wall time statistics. Active time is tracked via timestamps in the platform worker loop. Kernel mode returns QUIC_STATUS_NOT_SUPPORTED. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent da5fdca commit c4af934

10 files changed

Lines changed: 347 additions & 0 deletions

File tree

src/core/library.c

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1539,6 +1539,56 @@ QuicLibrarySetGlobalParam(
15391539
return Status;
15401540
}
15411541

1542+
#ifndef _KERNEL_MODE
1543+
//
1544+
// Fills the caller's buffer with the per-worker statistics from the worker pool.
1545+
//
1546+
_IRQL_requires_max_(PASSIVE_LEVEL)
1547+
QUIC_STATUS
1548+
QuicLibraryGetGlobalWorkerStatistics(
1549+
_Inout_ uint32_t* BufferLength,
1550+
_Out_writes_bytes_opt_(*BufferLength)
1551+
void* Buffer
1552+
)
1553+
{
1554+
if (MsQuicLib.WorkerPool == NULL) {
1555+
return QUIC_STATUS_INVALID_STATE;
1556+
}
1557+
1558+
uint32_t WorkerCount = CxPlatWorkerPoolGetCount(MsQuicLib.WorkerPool);
1559+
uint32_t RequiredSize =
1560+
sizeof(QUIC_WORKER_STATISTICS_LIST) +
1561+
WorkerCount * sizeof(QUIC_WORKER_STATISTICS);
1562+
1563+
if (*BufferLength < RequiredSize) {
1564+
*BufferLength = RequiredSize;
1565+
return QUIC_STATUS_BUFFER_TOO_SMALL;
1566+
}
1567+
1568+
if (Buffer == NULL) {
1569+
return QUIC_STATUS_INVALID_PARAMETER;
1570+
}
1571+
1572+
QUIC_WORKER_STATISTICS_LIST* List = (QUIC_WORKER_STATISTICS_LIST*)Buffer;
1573+
List->WorkerCount = WorkerCount;
1574+
List->WorkerStatsSize = sizeof(QUIC_WORKER_STATISTICS);
1575+
1576+
QUIC_WORKER_STATISTICS* Stats =
1577+
(QUIC_WORKER_STATISTICS*)((uint8_t*)Buffer + sizeof(QUIC_WORKER_STATISTICS_LIST));
1578+
1579+
for (uint32_t i = 0; i < WorkerCount; i++) {
1580+
CXPLAT_WORKER_STATISTICS WorkerStats = {0};
1581+
CxPlatWorkerPoolGetStatistics(MsQuicLib.WorkerPool, i, &WorkerStats);
1582+
Stats[i].IdealProcessor = WorkerStats.IdealProcessor;
1583+
Stats[i].CumulativeActiveTimeUs = WorkerStats.CumulativeActiveTimeUs;
1584+
Stats[i].CumulativeWallTimeUs = WorkerStats.CumulativeWallTimeUs;
1585+
}
1586+
1587+
*BufferLength = RequiredSize;
1588+
return QUIC_STATUS_SUCCESS;
1589+
}
1590+
#endif // !_KERNEL_MODE
1591+
15421592
_IRQL_requires_max_(PASSIVE_LEVEL)
15431593
_Success_(return == QUIC_STATUS_SUCCESS)
15441594
QUIC_STATUS
@@ -1914,6 +1964,19 @@ QuicLibraryGetGlobalParam(
19141964
break;
19151965
}
19161966

1967+
case QUIC_PARAM_GLOBAL_WORKER_STATISTICS: {
1968+
#ifdef _KERNEL_MODE
1969+
//
1970+
// Worker statistics are not supported in kernel mode, where the worker
1971+
// threads are not owned by the platform worker pool.
1972+
//
1973+
Status = QUIC_STATUS_NOT_SUPPORTED;
1974+
#else
1975+
Status = QuicLibraryGetGlobalWorkerStatistics(BufferLength, Buffer);
1976+
#endif
1977+
break;
1978+
}
1979+
19171980
default:
19181981
Status = QUIC_STATUS_INVALID_PARAMETER;
19191982
break;

src/inc/msquic.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,27 @@ typedef struct QUIC_XDP_MAP_CONFIG {
370370

371371
#endif // QUIC_API_ENABLE_PREVIEW_FEATURES
372372

373+
#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES
374+
375+
//
376+
// Per-worker statistics.
377+
//
378+
typedef struct QUIC_WORKER_STATISTICS {
379+
uint64_t CumulativeActiveTimeUs; // total time the worker spent active (not idle)
380+
uint64_t CumulativeWallTimeUs; // wall time since the worker thread started
381+
uint16_t IdealProcessor; // CPU the partition is affinitized to
382+
383+
// -- append-only beyond this point --
384+
} QUIC_WORKER_STATISTICS;
385+
386+
typedef struct QUIC_WORKER_STATISTICS_LIST {
387+
uint32_t WorkerCount; // number of QUIC_WORKER_STATISTICS entries that follow
388+
uint32_t WorkerStatsSize; // sizeof(QUIC_WORKER_STATISTICS) as written by this msquic
389+
// QUIC_WORKER_STATISTICS Workers[WorkerCount]; // stride == WorkerStatsSize
390+
} QUIC_WORKER_STATISTICS_LIST;
391+
392+
#endif // QUIC_API_ENABLE_PREVIEW_FEATURES
393+
373394
typedef struct QUIC_REGISTRATION_CONFIG { // All fields may be NULL/zero.
374395
const char* AppName;
375396
QUIC_EXECUTION_PROFILE ExecutionProfile;
@@ -1008,6 +1029,7 @@ void
10081029
#define QUIC_PARAM_GLOBAL_STATELESS_RETRY_CONFIG 0x0100000D // QUIC_STATELESS_RETRY_CONFIG
10091030
#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES
10101031
#define QUIC_PARAM_GLOBAL_XDP_MAP_CONFIG 0x0100000E // QUIC_XDP_MAP_CONFIG[]
1032+
#define QUIC_PARAM_GLOBAL_WORKER_STATISTICS 0x0100000F // QUIC_WORKER_STATISTICS_LIST
10111033
#endif
10121034

10131035
//

src/inc/quic_platform.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,22 @@ CxPlatWorkerPoolWorkerPoll(
576576
_In_ QUIC_EXECUTION* Execution
577577
);
578578

579+
typedef struct CXPLAT_WORKER_STATISTICS {
580+
uint64_t CumulativeActiveTimeUs; // Time the worker spent active (not idle).
581+
uint64_t CumulativeWallTimeUs; // Wall time since the worker thread started.
582+
uint16_t IdealProcessor; // CPU the worker is affinitized to.
583+
} CXPLAT_WORKER_STATISTICS;
584+
585+
//
586+
// Gets the statistics for a worker in the pool
587+
//
588+
void
589+
CxPlatWorkerPoolGetStatistics(
590+
_In_ CXPLAT_WORKER_POOL* WorkerPool,
591+
_In_ uint32_t Index,
592+
_Out_ CXPLAT_WORKER_STATISTICS* Stats
593+
);
594+
579595
//
580596
// Supports more dynamic operations, but must be submitted to the platform worker
581597
// to manage.

src/platform/platform_worker.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,26 @@ typedef struct QUIC_CACHEALIGN CXPLAT_WORKER {
102102
//
103103
BOOLEAN Running;
104104

105+
//
106+
// Statistics tracking for active time measurement.
107+
//
108+
struct {
109+
//
110+
// Timestamp (in microseconds) when the worker thread started.
111+
//
112+
uint64_t StartedTimeUs;
113+
114+
//
115+
// Timestamp (in microseconds) when the current active period started.
116+
//
117+
uint64_t ActiveStartTimeUs;
118+
119+
//
120+
// Cumulative time (in microseconds) the worker was active (not waiting or yielding).
121+
//
122+
uint64_t CumulativeActiveTimeUs;
123+
} Stats;
124+
105125
} CXPLAT_WORKER;
106126

107127
typedef struct CXPLAT_WORKER_POOL {
@@ -734,12 +754,23 @@ CxPlatProcessEvents(
734754
)
735755
{
736756
CXPLAT_CQE Cqes[16];
757+
758+
if (Worker->State.WaitTime > 0) {
759+
Worker->Stats.CumulativeActiveTimeUs +=
760+
CxPlatTimeDiff64(Worker->Stats.ActiveStartTimeUs, CxPlatTimeUs64());
761+
}
762+
737763
uint32_t CqeCount =
738764
CxPlatEventQDequeue(
739765
&Worker->EventQ,
740766
Cqes,
741767
ARRAYSIZE(Cqes),
742768
Worker->State.WaitTime);
769+
770+
if (Worker->State.WaitTime > 0) {
771+
Worker->Stats.ActiveStartTimeUs = CxPlatTimeUs64();
772+
}
773+
743774
uint32_t CurrentCqeCount = CqeCount;
744775
CXPLAT_CQE* CurrentCqe = Cqes;
745776

@@ -796,6 +827,8 @@ CXPLAT_THREAD_CALLBACK(CxPlatWorkerThread, Context)
796827

797828
Worker->State.ThreadID = CxPlatCurThreadID();
798829
Worker->Running = TRUE;
830+
Worker->Stats.StartedTimeUs = CxPlatTimeUs64();
831+
Worker->Stats.ActiveStartTimeUs = Worker->Stats.StartedTimeUs;
799832

800833
while (!Worker->StoppedThread) {
801834

@@ -816,7 +849,10 @@ CXPLAT_THREAD_CALLBACK(CxPlatWorkerThread, Context)
816849
if (Worker->State.NoWorkCount == 0) {
817850
Worker->State.LastWorkTime = Worker->State.TimeNow;
818851
} else if (Worker->State.NoWorkCount > CXPLAT_WORKER_IDLE_WORK_THRESHOLD_COUNT) {
852+
Worker->Stats.CumulativeActiveTimeUs +=
853+
CxPlatTimeDiff64(Worker->Stats.ActiveStartTimeUs, CxPlatTimeUs64());
819854
CxPlatSchedulerYield();
855+
Worker->Stats.ActiveStartTimeUs = CxPlatTimeUs64();
820856
Worker->State.NoWorkCount = 0;
821857
}
822858

@@ -826,6 +862,8 @@ CXPLAT_THREAD_CALLBACK(CxPlatWorkerThread, Context)
826862
}
827863
}
828864

865+
Worker->Stats.CumulativeActiveTimeUs +=
866+
CxPlatTimeDiff64(Worker->Stats.ActiveStartTimeUs, CxPlatTimeUs64());
829867
Worker->Running = FALSE;
830868

831869
#if DEBUG
@@ -839,3 +877,25 @@ CXPLAT_THREAD_CALLBACK(CxPlatWorkerThread, Context)
839877

840878
CXPLAT_THREAD_RETURN(0);
841879
}
880+
881+
void
882+
CxPlatWorkerPoolGetStatistics(
883+
_In_ CXPLAT_WORKER_POOL* WorkerPool,
884+
_In_ uint32_t Index,
885+
_Out_ CXPLAT_WORKER_STATISTICS* Stats
886+
)
887+
{
888+
CXPLAT_DBG_ASSERT(Index < WorkerPool->WorkerCount);
889+
890+
const uint64_t Now = CxPlatTimeUs64();
891+
const CXPLAT_WORKER* Worker = &WorkerPool->Workers[Index];
892+
893+
Stats->IdealProcessor = Worker->IdealProcessor;
894+
Stats->CumulativeWallTimeUs = CxPlatTimeDiff64(Worker->Stats.StartedTimeUs, Now);
895+
Stats->CumulativeActiveTimeUs = Worker->Stats.CumulativeActiveTimeUs;
896+
897+
const uint64_t ActiveStartTime = Worker->Stats.ActiveStartTimeUs;
898+
if (ActiveStartTime < Now) {
899+
Stats->CumulativeActiveTimeUs += CxPlatTimeDiff64(ActiveStartTime, Now);
900+
}
901+
}

src/rs/ffi/linux_bindings.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ pub const QUIC_PARAM_GLOBAL_STATELESS_RESET_KEY: u32 = 16777227;
176176
pub const QUIC_PARAM_GLOBAL_STATISTICS_V2_SIZES: u32 = 16777228;
177177
pub const QUIC_PARAM_GLOBAL_STATELESS_RETRY_CONFIG: u32 = 16777229;
178178
pub const QUIC_PARAM_GLOBAL_XDP_MAP_CONFIG: u32 = 16777230;
179+
pub const QUIC_PARAM_GLOBAL_WORKER_STATISTICS: u32 = 16777231;
179180
pub const QUIC_PARAM_CONFIGURATION_SETTINGS: u32 = 50331648;
180181
pub const QUIC_PARAM_CONFIGURATION_TICKET_KEYS: u32 = 50331649;
181182
pub const QUIC_PARAM_CONFIGURATION_VERSION_SETTINGS: u32 = 50331650;
@@ -519,6 +520,42 @@ const _: () = {
519520
};
520521
#[repr(C)]
521522
#[derive(Debug, Copy, Clone)]
523+
pub struct QUIC_WORKER_STATISTICS {
524+
pub CumulativeActiveTimeUs: u64,
525+
pub CumulativeWallTimeUs: u64,
526+
pub IdealProcessor: u16,
527+
}
528+
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
529+
const _: () = {
530+
["Size of QUIC_WORKER_STATISTICS"][::std::mem::size_of::<QUIC_WORKER_STATISTICS>() - 24usize];
531+
["Alignment of QUIC_WORKER_STATISTICS"]
532+
[::std::mem::align_of::<QUIC_WORKER_STATISTICS>() - 8usize];
533+
["Offset of field: QUIC_WORKER_STATISTICS::CumulativeActiveTimeUs"]
534+
[::std::mem::offset_of!(QUIC_WORKER_STATISTICS, CumulativeActiveTimeUs) - 0usize];
535+
["Offset of field: QUIC_WORKER_STATISTICS::CumulativeWallTimeUs"]
536+
[::std::mem::offset_of!(QUIC_WORKER_STATISTICS, CumulativeWallTimeUs) - 8usize];
537+
["Offset of field: QUIC_WORKER_STATISTICS::IdealProcessor"]
538+
[::std::mem::offset_of!(QUIC_WORKER_STATISTICS, IdealProcessor) - 16usize];
539+
};
540+
#[repr(C)]
541+
#[derive(Debug, Copy, Clone)]
542+
pub struct QUIC_WORKER_STATISTICS_LIST {
543+
pub WorkerCount: u32,
544+
pub WorkerStatsSize: u32,
545+
}
546+
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
547+
const _: () = {
548+
["Size of QUIC_WORKER_STATISTICS_LIST"]
549+
[::std::mem::size_of::<QUIC_WORKER_STATISTICS_LIST>() - 8usize];
550+
["Alignment of QUIC_WORKER_STATISTICS_LIST"]
551+
[::std::mem::align_of::<QUIC_WORKER_STATISTICS_LIST>() - 4usize];
552+
["Offset of field: QUIC_WORKER_STATISTICS_LIST::WorkerCount"]
553+
[::std::mem::offset_of!(QUIC_WORKER_STATISTICS_LIST, WorkerCount) - 0usize];
554+
["Offset of field: QUIC_WORKER_STATISTICS_LIST::WorkerStatsSize"]
555+
[::std::mem::offset_of!(QUIC_WORKER_STATISTICS_LIST, WorkerStatsSize) - 4usize];
556+
};
557+
#[repr(C)]
558+
#[derive(Debug, Copy, Clone)]
522559
pub struct QUIC_REGISTRATION_CONFIG {
523560
pub AppName: *const ::std::os::raw::c_char,
524561
pub ExecutionProfile: QUIC_EXECUTION_PROFILE,

src/rs/ffi/win_bindings.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ pub const QUIC_PARAM_GLOBAL_STATELESS_RESET_KEY: u32 = 16777227;
170170
pub const QUIC_PARAM_GLOBAL_STATISTICS_V2_SIZES: u32 = 16777228;
171171
pub const QUIC_PARAM_GLOBAL_STATELESS_RETRY_CONFIG: u32 = 16777229;
172172
pub const QUIC_PARAM_GLOBAL_XDP_MAP_CONFIG: u32 = 16777230;
173+
pub const QUIC_PARAM_GLOBAL_WORKER_STATISTICS: u32 = 16777231;
173174
pub const QUIC_PARAM_CONFIGURATION_SETTINGS: u32 = 50331648;
174175
pub const QUIC_PARAM_CONFIGURATION_TICKET_KEYS: u32 = 50331649;
175176
pub const QUIC_PARAM_CONFIGURATION_VERSION_SETTINGS: u32 = 50331650;
@@ -517,6 +518,42 @@ const _: () = {
517518
};
518519
#[repr(C)]
519520
#[derive(Debug, Copy, Clone)]
521+
pub struct QUIC_WORKER_STATISTICS {
522+
pub CumulativeActiveTimeUs: u64,
523+
pub CumulativeWallTimeUs: u64,
524+
pub IdealProcessor: u16,
525+
}
526+
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
527+
const _: () = {
528+
["Size of QUIC_WORKER_STATISTICS"][::std::mem::size_of::<QUIC_WORKER_STATISTICS>() - 24usize];
529+
["Alignment of QUIC_WORKER_STATISTICS"]
530+
[::std::mem::align_of::<QUIC_WORKER_STATISTICS>() - 8usize];
531+
["Offset of field: QUIC_WORKER_STATISTICS::CumulativeActiveTimeUs"]
532+
[::std::mem::offset_of!(QUIC_WORKER_STATISTICS, CumulativeActiveTimeUs) - 0usize];
533+
["Offset of field: QUIC_WORKER_STATISTICS::CumulativeWallTimeUs"]
534+
[::std::mem::offset_of!(QUIC_WORKER_STATISTICS, CumulativeWallTimeUs) - 8usize];
535+
["Offset of field: QUIC_WORKER_STATISTICS::IdealProcessor"]
536+
[::std::mem::offset_of!(QUIC_WORKER_STATISTICS, IdealProcessor) - 16usize];
537+
};
538+
#[repr(C)]
539+
#[derive(Debug, Copy, Clone)]
540+
pub struct QUIC_WORKER_STATISTICS_LIST {
541+
pub WorkerCount: u32,
542+
pub WorkerStatsSize: u32,
543+
}
544+
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
545+
const _: () = {
546+
["Size of QUIC_WORKER_STATISTICS_LIST"]
547+
[::std::mem::size_of::<QUIC_WORKER_STATISTICS_LIST>() - 8usize];
548+
["Alignment of QUIC_WORKER_STATISTICS_LIST"]
549+
[::std::mem::align_of::<QUIC_WORKER_STATISTICS_LIST>() - 4usize];
550+
["Offset of field: QUIC_WORKER_STATISTICS_LIST::WorkerCount"]
551+
[::std::mem::offset_of!(QUIC_WORKER_STATISTICS_LIST, WorkerCount) - 0usize];
552+
["Offset of field: QUIC_WORKER_STATISTICS_LIST::WorkerStatsSize"]
553+
[::std::mem::offset_of!(QUIC_WORKER_STATISTICS_LIST, WorkerStatsSize) - 4usize];
554+
};
555+
#[repr(C)]
556+
#[derive(Debug, Copy, Clone)]
520557
pub struct QUIC_REGISTRATION_CONFIG {
521558
pub AppName: *const ::std::os::raw::c_char,
522559
pub ExecutionProfile: QUIC_EXECUTION_PROFILE,

src/test/MsQuicTests.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ void QuicTestGetPerfCounters();
9696
#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES
9797
void QuicTestValidateEncryptDecryptPerfCounters();
9898
void QuicTestConnQueueDelayStatistics();
99+
void QuicTestGetWorkerStatistics();
99100
#endif
100101
void QuicTestVersionSettings();
101102
void QuicTestValidateParamApi();

src/test/bin/quic_gtest.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,15 @@ TEST(ParameterValidation, ConnQueueDelayStatistics) {
462462
QuicTestConnQueueDelayStatistics();
463463
}
464464
}
465+
466+
TEST(ParameterValidation, ValidateGetWorkerStatistics) {
467+
TestLogger Logger("QuicTestGetWorkerStatistics");
468+
if (TestingKernelMode) {
469+
ASSERT_TRUE(InvokeKernelTest(FUNC(QuicTestGetWorkerStatistics)));
470+
} else {
471+
QuicTestGetWorkerStatistics();
472+
}
473+
}
465474
#endif // QUIC_API_ENABLE_PREVIEW_FEATURES
466475

467476
TEST(ParameterValidation, ValidateConfiguration) {

src/test/bin/winkernel/control.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,7 @@ ExecuteTestRequest(
466466
#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES
467467
RegisterTestFunction(QuicTestValidateEncryptDecryptPerfCounters);
468468
RegisterTestFunction(QuicTestConnQueueDelayStatistics);
469+
RegisterTestFunction(QuicTestGetWorkerStatistics);
469470
#endif
470471
RegisterTestFunction(QuicTestValidateConfiguration);
471472
RegisterTestFunction(QuicTestValidateListener);

0 commit comments

Comments
 (0)