Skip to content

Commit 326c9b0

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 51d449b commit 326c9b0

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
@@ -1506,6 +1506,56 @@ QuicLibrarySetGlobalParam(
15061506
return Status;
15071507
}
15081508

1509+
#ifndef _KERNEL_MODE
1510+
//
1511+
// Fills the caller's buffer with the per-worker statistics from the worker pool.
1512+
//
1513+
_IRQL_requires_max_(PASSIVE_LEVEL)
1514+
QUIC_STATUS
1515+
QuicLibraryGetGlobalWorkerStatistics(
1516+
_Inout_ uint32_t* BufferLength,
1517+
_Out_writes_bytes_opt_(*BufferLength)
1518+
void* Buffer
1519+
)
1520+
{
1521+
if (MsQuicLib.WorkerPool == NULL) {
1522+
return QUIC_STATUS_INVALID_STATE;
1523+
}
1524+
1525+
uint32_t WorkerCount = CxPlatWorkerPoolGetCount(MsQuicLib.WorkerPool);
1526+
uint32_t RequiredSize =
1527+
sizeof(QUIC_WORKER_STATISTICS_LIST) +
1528+
WorkerCount * sizeof(QUIC_WORKER_STATISTICS);
1529+
1530+
if (*BufferLength < RequiredSize) {
1531+
*BufferLength = RequiredSize;
1532+
return QUIC_STATUS_BUFFER_TOO_SMALL;
1533+
}
1534+
1535+
if (Buffer == NULL) {
1536+
return QUIC_STATUS_INVALID_PARAMETER;
1537+
}
1538+
1539+
QUIC_WORKER_STATISTICS_LIST* List = (QUIC_WORKER_STATISTICS_LIST*)Buffer;
1540+
List->WorkerCount = WorkerCount;
1541+
List->WorkerStatsSize = sizeof(QUIC_WORKER_STATISTICS);
1542+
1543+
QUIC_WORKER_STATISTICS* Stats =
1544+
(QUIC_WORKER_STATISTICS*)((uint8_t*)Buffer + sizeof(QUIC_WORKER_STATISTICS_LIST));
1545+
1546+
for (uint32_t i = 0; i < WorkerCount; i++) {
1547+
CXPLAT_WORKER_STATISTICS WorkerStats = {0};
1548+
CxPlatWorkerPoolGetStatistics(MsQuicLib.WorkerPool, i, &WorkerStats);
1549+
Stats[i].IdealProcessor = WorkerStats.IdealProcessor;
1550+
Stats[i].CumulativeActiveTimeUs = WorkerStats.CumulativeActiveTimeUs;
1551+
Stats[i].CumulativeWallTimeUs = WorkerStats.CumulativeWallTimeUs;
1552+
}
1553+
1554+
*BufferLength = RequiredSize;
1555+
return QUIC_STATUS_SUCCESS;
1556+
}
1557+
#endif // !_KERNEL_MODE
1558+
15091559
_IRQL_requires_max_(PASSIVE_LEVEL)
15101560
QUIC_STATUS
15111561
QuicLibraryGetGlobalParam(
@@ -1876,6 +1926,19 @@ QuicLibraryGetGlobalParam(
18761926
break;
18771927
}
18781928

1929+
case QUIC_PARAM_GLOBAL_WORKER_STATISTICS: {
1930+
#ifdef _KERNEL_MODE
1931+
//
1932+
// Worker statistics are not supported in kernel mode, where the worker
1933+
// threads are not owned by the platform worker pool.
1934+
//
1935+
Status = QUIC_STATUS_NOT_SUPPORTED;
1936+
#else
1937+
Status = QuicLibraryGetGlobalWorkerStatistics(BufferLength, Buffer);
1938+
#endif
1939+
break;
1940+
}
1941+
18791942
default:
18801943
Status = QUIC_STATUS_INVALID_PARAMETER;
18811944
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;
@@ -996,6 +1017,7 @@ void
9961017
#define QUIC_PARAM_GLOBAL_STATELESS_RETRY_CONFIG 0x0100000D // QUIC_STATELESS_RETRY_CONFIG
9971018
#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES
9981019
#define QUIC_PARAM_GLOBAL_XDP_MAP_CONFIG 0x0100000E // QUIC_XDP_MAP_CONFIG[]
1020+
#define QUIC_PARAM_GLOBAL_WORKER_STATISTICS 0x0100000F // QUIC_WORKER_STATISTICS_LIST
9991021
#endif
10001022

10011023
//

src/inc/quic_platform.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,22 @@ CxPlatWorkerPoolWorkerPoll(
557557
_In_ QUIC_EXECUTION* Execution
558558
);
559559

560+
typedef struct CXPLAT_WORKER_STATISTICS {
561+
uint64_t CumulativeActiveTimeUs; // Time the worker spent active (not idle).
562+
uint64_t CumulativeWallTimeUs; // Wall time since the worker thread started.
563+
uint16_t IdealProcessor; // CPU the worker is affinitized to.
564+
} CXPLAT_WORKER_STATISTICS;
565+
566+
//
567+
// Gets the statistics for a worker in the pool
568+
//
569+
void
570+
CxPlatWorkerPoolGetStatistics(
571+
_In_ CXPLAT_WORKER_POOL* WorkerPool,
572+
_In_ uint32_t Index,
573+
_Out_ CXPLAT_WORKER_STATISTICS* Stats
574+
);
575+
560576
//
561577
// Supports more dynamic operations, but must be submitted to the platform worker
562578
// 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
@@ -91,6 +91,7 @@ void QuicTestStreamParam();
9191
void QuicTestGetPerfCounters();
9292
#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES
9393
void QuicTestValidateEncryptDecryptPerfCounters();
94+
void QuicTestGetWorkerStatistics();
9495
#endif
9596
void QuicTestVersionSettings();
9697
void QuicTestValidateParamApi();

src/test/bin/quic_gtest.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,15 @@ TEST(ParameterValidation, ValidateEncryptDecryptPerfCounters) {
399399
QuicTestValidateEncryptDecryptPerfCounters();
400400
}
401401
}
402+
403+
TEST(ParameterValidation, ValidateGetWorkerStatistics) {
404+
TestLogger Logger("QuicTestGetWorkerStatistics");
405+
if (TestingKernelMode) {
406+
ASSERT_TRUE(InvokeKernelTest(FUNC(QuicTestGetWorkerStatistics)));
407+
} else {
408+
QuicTestGetWorkerStatistics();
409+
}
410+
}
402411
#endif // QUIC_API_ENABLE_PREVIEW_FEATURES
403412

404413
TEST(ParameterValidation, ValidateConfiguration) {

src/test/bin/winkernel/control.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,7 @@ ExecuteTestRequest(
465465
RegisterTestFunction(QuicTestGetPerfCounters);
466466
#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES
467467
RegisterTestFunction(QuicTestValidateEncryptDecryptPerfCounters);
468+
RegisterTestFunction(QuicTestGetWorkerStatistics);
468469
#endif
469470
RegisterTestFunction(QuicTestValidateConfiguration);
470471
RegisterTestFunction(QuicTestValidateListener);

0 commit comments

Comments
 (0)