Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions src/core/library.c
Original file line number Diff line number Diff line change
Expand Up @@ -1539,6 +1539,64 @@ QuicLibrarySetGlobalParam(
return Status;
}

//
// Fills the caller's buffer with the per-worker statistics from the worker pool.
//
_IRQL_requires_max_(PASSIVE_LEVEL)
QUIC_STATUS
QuicLibraryGetGlobalWorkerStatistics(
_Inout_ uint32_t* BufferLength,
_Out_writes_bytes_opt_(*BufferLength)
void* Buffer
)
{
#ifdef _KERNEL_MODE
//
// Worker statistics are not supported in kernel mode, where the worker
// threads are not owned by the platform worker pool.
//
UNREFERENCED_PARAMETER(BufferLength);
UNREFERENCED_PARAMETER(Buffer);
return QUIC_STATUS_NOT_SUPPORTED;
#else
if (MsQuicLib.WorkerPool == NULL) {
Comment thread
guhetier marked this conversation as resolved.
Outdated
return QUIC_STATUS_INVALID_STATE;
}

uint32_t WorkerCount = CxPlatWorkerPoolGetCount(MsQuicLib.WorkerPool);
uint32_t RequiredSize =
sizeof(QUIC_WORKER_STATISTICS_LIST) +
WorkerCount * sizeof(QUIC_WORKER_STATISTICS);

if (*BufferLength < RequiredSize) {
*BufferLength = RequiredSize;
return QUIC_STATUS_BUFFER_TOO_SMALL;
}

if (Buffer == NULL) {
return QUIC_STATUS_INVALID_PARAMETER;
}

QUIC_WORKER_STATISTICS_LIST* List = (QUIC_WORKER_STATISTICS_LIST*)Buffer;
List->WorkerCount = WorkerCount;
List->WorkerStatsSize = sizeof(QUIC_WORKER_STATISTICS);

QUIC_WORKER_STATISTICS* Stats =
(QUIC_WORKER_STATISTICS*)((uint8_t*)Buffer + sizeof(QUIC_WORKER_STATISTICS_LIST));

for (uint32_t i = 0; i < WorkerCount; i++) {
CXPLAT_WORKER_STATISTICS WorkerStats = {0};
CxPlatWorkerPoolGetStatistics(MsQuicLib.WorkerPool, i, &WorkerStats);
Stats[i].IdealProcessor = WorkerStats.IdealProcessor;
Stats[i].CumulativeActiveTimeUs = WorkerStats.CumulativeActiveTimeUs;
Stats[i].CumulativeWallTimeUs = WorkerStats.CumulativeWallTimeUs;
}

*BufferLength = RequiredSize;
return QUIC_STATUS_SUCCESS;
#endif // _KERNEL_MODE
}

_IRQL_requires_max_(PASSIVE_LEVEL)
_Success_(return == QUIC_STATUS_SUCCESS)
QUIC_STATUS
Expand Down Expand Up @@ -1914,6 +1972,10 @@ QuicLibraryGetGlobalParam(
break;
}

case QUIC_PARAM_GLOBAL_WORKER_STATISTICS:
Status = QuicLibraryGetGlobalWorkerStatistics(BufferLength, Buffer);
break;

default:
Status = QUIC_STATUS_INVALID_PARAMETER;
break;
Expand Down
22 changes: 22 additions & 0 deletions src/inc/msquic.h
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,27 @@ typedef struct QUIC_XDP_MAP_CONFIG {

#endif // QUIC_API_ENABLE_PREVIEW_FEATURES

#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES

//
// Per-worker statistics.
//
typedef struct QUIC_WORKER_STATISTICS {
uint64_t CumulativeActiveTimeUs; // total time the worker spent active (not idle)
uint64_t CumulativeWallTimeUs; // wall time since the worker thread started
uint16_t IdealProcessor; // CPU the partition is affinitized to

// -- append-only beyond this point --
} QUIC_WORKER_STATISTICS;

typedef struct QUIC_WORKER_STATISTICS_LIST {
uint32_t WorkerCount; // number of QUIC_WORKER_STATISTICS entries that follow
uint32_t WorkerStatsSize; // sizeof(QUIC_WORKER_STATISTICS) as written by this msquic
// QUIC_WORKER_STATISTICS Workers[WorkerCount]; // stride == WorkerStatsSize
} QUIC_WORKER_STATISTICS_LIST;

#endif // QUIC_API_ENABLE_PREVIEW_FEATURES

typedef struct QUIC_REGISTRATION_CONFIG { // All fields may be NULL/zero.
const char* AppName;
QUIC_EXECUTION_PROFILE ExecutionProfile;
Expand Down Expand Up @@ -1008,6 +1029,7 @@ void
#define QUIC_PARAM_GLOBAL_STATELESS_RETRY_CONFIG 0x0100000D // QUIC_STATELESS_RETRY_CONFIG
#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES
#define QUIC_PARAM_GLOBAL_XDP_MAP_CONFIG 0x0100000E // QUIC_XDP_MAP_CONFIG[]
#define QUIC_PARAM_GLOBAL_WORKER_STATISTICS 0x0100000F // QUIC_WORKER_STATISTICS_LIST
#endif

//
Expand Down
16 changes: 16 additions & 0 deletions src/inc/quic_platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,22 @@ CxPlatWorkerPoolWorkerPoll(
_In_ QUIC_EXECUTION* Execution
);

typedef struct CXPLAT_WORKER_STATISTICS {
uint64_t CumulativeActiveTimeUs; // Time the worker spent active (not idle).
uint64_t CumulativeWallTimeUs; // Wall time since the worker thread started.
uint16_t IdealProcessor; // CPU the worker is affinitized to.
} CXPLAT_WORKER_STATISTICS;

//
// Gets the statistics for a worker in the pool
//
void
CxPlatWorkerPoolGetStatistics(
_In_ CXPLAT_WORKER_POOL* WorkerPool,
_In_ uint32_t Index,
_Out_ CXPLAT_WORKER_STATISTICS* Stats
);

//
// Supports more dynamic operations, but must be submitted to the platform worker
// to manage.
Expand Down
88 changes: 88 additions & 0 deletions src/platform/platform_worker.c
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,27 @@ typedef struct QUIC_CACHEALIGN CXPLAT_WORKER {
//
BOOLEAN Running;

//
// Statistics tracking for active time measurement.
//
struct {
//
// Timestamp (in microseconds) when the worker thread started.
//
uint64_t StartedTimeUs;

//
// Timestamp (in microseconds) when the current active period started,
// or 0 when the worker is currently idle (waiting or yielding).
//
uint64_t ActiveStartTimeUs;

//
// Cumulative time (in microseconds) the worker was active (not waiting or yielding).
//
uint64_t CumulativeActiveTimeUs;
} Stats;

} CXPLAT_WORKER;

typedef struct CXPLAT_WORKER_POOL {
Expand Down Expand Up @@ -728,18 +749,55 @@ CxPlatProcessDynamicPoolAllocators(
CxPlatLockRelease(&Worker->ECLock);
}

//
// Marks the end of an active period, folding the elapsed active time into the
// cumulative total and marking the worker idle (ActiveStartTimeUs == 0).
//
void
CxPlatWorkerStatsPause(
_Inout_ CXPLAT_WORKER* Worker
)
{
if (Worker->Stats.ActiveStartTimeUs != 0) {
Worker->Stats.CumulativeActiveTimeUs +=
CxPlatTimeDiff64(Worker->Stats.ActiveStartTimeUs, CxPlatTimeUs64());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, looks fine, but you need to be careful with all the CxPlatTimeUs64() calls, as they are quite expensive. Especially since I believe the processing call (once you dequeue an event) almost immediately calls it again.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The timestamp in the worker loop is snapped again only after the events have been processed. I considered re-ordering the loop so that Worker->TimeNow can be used as the ActiveStartTimeUs, that might be worth it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at it again, we currently snap time:

  • right before running execution context (prior to this PR)
  • right before processing events (added by this PR)

Both are potentially time consuming, so it seems reasonable to me to snap the time again - at least because the ECs seems to need a pretty fresh timestamp (see the comment about solving a potential race, it isn't clear which race this is but I suspect it has something to do with timers being already expired).

Worker->Stats.ActiveStartTimeUs = 0;
}
}

//
// Marks the start of an active period.
//
void
CxPlatWorkerStatsResume(
_Inout_ CXPLAT_WORKER* Worker
)
{
Worker->Stats.ActiveStartTimeUs = CxPlatTimeUs64();
}

void
CxPlatProcessEvents(
_In_ CXPLAT_WORKER* Worker
)
{
CXPLAT_CQE Cqes[16];

if (Worker->State.WaitTime > 0) {
CxPlatWorkerStatsPause(Worker);
}

uint32_t CqeCount =
CxPlatEventQDequeue(
&Worker->EventQ,
Cqes,
ARRAYSIZE(Cqes),
Worker->State.WaitTime);

if (Worker->State.WaitTime > 0) {
CxPlatWorkerStatsResume(Worker);
}

uint32_t CurrentCqeCount = CqeCount;
CXPLAT_CQE* CurrentCqe = Cqes;

Expand Down Expand Up @@ -796,6 +854,8 @@ CXPLAT_THREAD_CALLBACK(CxPlatWorkerThread, Context)

Worker->State.ThreadID = CxPlatCurThreadID();
Worker->Running = TRUE;
Worker->Stats.StartedTimeUs = CxPlatTimeUs64();
Worker->Stats.ActiveStartTimeUs = Worker->Stats.StartedTimeUs;

while (!Worker->StoppedThread) {

Expand All @@ -816,7 +876,9 @@ CXPLAT_THREAD_CALLBACK(CxPlatWorkerThread, Context)
if (Worker->State.NoWorkCount == 0) {
Worker->State.LastWorkTime = Worker->State.TimeNow;
} else if (Worker->State.NoWorkCount > CXPLAT_WORKER_IDLE_WORK_THRESHOLD_COUNT) {
CxPlatWorkerStatsPause(Worker);
CxPlatSchedulerYield();
CxPlatWorkerStatsResume(Worker);
Worker->State.NoWorkCount = 0;
}

Expand All @@ -826,6 +888,7 @@ CXPLAT_THREAD_CALLBACK(CxPlatWorkerThread, Context)
}
}

CxPlatWorkerStatsPause(Worker);
Worker->Running = FALSE;

#if DEBUG
Expand All @@ -839,3 +902,28 @@ CXPLAT_THREAD_CALLBACK(CxPlatWorkerThread, Context)

CXPLAT_THREAD_RETURN(0);
}

void
CxPlatWorkerPoolGetStatistics(
_In_ CXPLAT_WORKER_POOL* WorkerPool,
_In_ uint32_t Index,
_Out_ CXPLAT_WORKER_STATISTICS* Stats
)
{
CXPLAT_FRE_ASSERT(Index < WorkerPool->WorkerCount);

const uint64_t Now = CxPlatTimeUs64();
Comment thread
guhetier marked this conversation as resolved.
Outdated
CXPLAT_WORKER* Worker = &WorkerPool->Workers[Index];

Stats->IdealProcessor = Worker->IdealProcessor;
Stats->CumulativeWallTimeUs = CxPlatTimeDiff64(Worker->Stats.StartedTimeUs, Now);
Stats->CumulativeActiveTimeUs = Worker->Stats.CumulativeActiveTimeUs;

//
// If the worker is currently active, include the in-progress active period.
//
const uint64_t ActiveStartTimeUs = Worker->Stats.ActiveStartTimeUs;
if (ActiveStartTimeUs != 0 && ActiveStartTimeUs < Now) {
Stats->CumulativeActiveTimeUs += CxPlatTimeDiff64(ActiveStartTimeUs, Now);
}
}
37 changes: 37 additions & 0 deletions src/rs/ffi/linux_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ pub const QUIC_PARAM_GLOBAL_STATELESS_RESET_KEY: u32 = 16777227;
pub const QUIC_PARAM_GLOBAL_STATISTICS_V2_SIZES: u32 = 16777228;
pub const QUIC_PARAM_GLOBAL_STATELESS_RETRY_CONFIG: u32 = 16777229;
pub const QUIC_PARAM_GLOBAL_XDP_MAP_CONFIG: u32 = 16777230;
pub const QUIC_PARAM_GLOBAL_WORKER_STATISTICS: u32 = 16777231;
pub const QUIC_PARAM_CONFIGURATION_SETTINGS: u32 = 50331648;
pub const QUIC_PARAM_CONFIGURATION_TICKET_KEYS: u32 = 50331649;
pub const QUIC_PARAM_CONFIGURATION_VERSION_SETTINGS: u32 = 50331650;
Expand Down Expand Up @@ -519,6 +520,42 @@ const _: () = {
};
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct QUIC_WORKER_STATISTICS {
pub CumulativeActiveTimeUs: u64,
pub CumulativeWallTimeUs: u64,
pub IdealProcessor: u16,
}
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
const _: () = {
["Size of QUIC_WORKER_STATISTICS"][::std::mem::size_of::<QUIC_WORKER_STATISTICS>() - 24usize];
["Alignment of QUIC_WORKER_STATISTICS"]
[::std::mem::align_of::<QUIC_WORKER_STATISTICS>() - 8usize];
["Offset of field: QUIC_WORKER_STATISTICS::CumulativeActiveTimeUs"]
[::std::mem::offset_of!(QUIC_WORKER_STATISTICS, CumulativeActiveTimeUs) - 0usize];
["Offset of field: QUIC_WORKER_STATISTICS::CumulativeWallTimeUs"]
[::std::mem::offset_of!(QUIC_WORKER_STATISTICS, CumulativeWallTimeUs) - 8usize];
["Offset of field: QUIC_WORKER_STATISTICS::IdealProcessor"]
[::std::mem::offset_of!(QUIC_WORKER_STATISTICS, IdealProcessor) - 16usize];
};
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct QUIC_WORKER_STATISTICS_LIST {
pub WorkerCount: u32,
pub WorkerStatsSize: u32,
}
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
const _: () = {
["Size of QUIC_WORKER_STATISTICS_LIST"]
[::std::mem::size_of::<QUIC_WORKER_STATISTICS_LIST>() - 8usize];
["Alignment of QUIC_WORKER_STATISTICS_LIST"]
[::std::mem::align_of::<QUIC_WORKER_STATISTICS_LIST>() - 4usize];
["Offset of field: QUIC_WORKER_STATISTICS_LIST::WorkerCount"]
[::std::mem::offset_of!(QUIC_WORKER_STATISTICS_LIST, WorkerCount) - 0usize];
["Offset of field: QUIC_WORKER_STATISTICS_LIST::WorkerStatsSize"]
[::std::mem::offset_of!(QUIC_WORKER_STATISTICS_LIST, WorkerStatsSize) - 4usize];
};
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct QUIC_REGISTRATION_CONFIG {
pub AppName: *const ::std::os::raw::c_char,
pub ExecutionProfile: QUIC_EXECUTION_PROFILE,
Expand Down
37 changes: 37 additions & 0 deletions src/rs/ffi/win_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ pub const QUIC_PARAM_GLOBAL_STATELESS_RESET_KEY: u32 = 16777227;
pub const QUIC_PARAM_GLOBAL_STATISTICS_V2_SIZES: u32 = 16777228;
pub const QUIC_PARAM_GLOBAL_STATELESS_RETRY_CONFIG: u32 = 16777229;
pub const QUIC_PARAM_GLOBAL_XDP_MAP_CONFIG: u32 = 16777230;
pub const QUIC_PARAM_GLOBAL_WORKER_STATISTICS: u32 = 16777231;
pub const QUIC_PARAM_CONFIGURATION_SETTINGS: u32 = 50331648;
pub const QUIC_PARAM_CONFIGURATION_TICKET_KEYS: u32 = 50331649;
pub const QUIC_PARAM_CONFIGURATION_VERSION_SETTINGS: u32 = 50331650;
Expand Down Expand Up @@ -517,6 +518,42 @@ const _: () = {
};
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct QUIC_WORKER_STATISTICS {
pub CumulativeActiveTimeUs: u64,
pub CumulativeWallTimeUs: u64,
pub IdealProcessor: u16,
}
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
const _: () = {
["Size of QUIC_WORKER_STATISTICS"][::std::mem::size_of::<QUIC_WORKER_STATISTICS>() - 24usize];
["Alignment of QUIC_WORKER_STATISTICS"]
[::std::mem::align_of::<QUIC_WORKER_STATISTICS>() - 8usize];
["Offset of field: QUIC_WORKER_STATISTICS::CumulativeActiveTimeUs"]
[::std::mem::offset_of!(QUIC_WORKER_STATISTICS, CumulativeActiveTimeUs) - 0usize];
["Offset of field: QUIC_WORKER_STATISTICS::CumulativeWallTimeUs"]
[::std::mem::offset_of!(QUIC_WORKER_STATISTICS, CumulativeWallTimeUs) - 8usize];
["Offset of field: QUIC_WORKER_STATISTICS::IdealProcessor"]
[::std::mem::offset_of!(QUIC_WORKER_STATISTICS, IdealProcessor) - 16usize];
};
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct QUIC_WORKER_STATISTICS_LIST {
pub WorkerCount: u32,
pub WorkerStatsSize: u32,
}
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
const _: () = {
["Size of QUIC_WORKER_STATISTICS_LIST"]
[::std::mem::size_of::<QUIC_WORKER_STATISTICS_LIST>() - 8usize];
["Alignment of QUIC_WORKER_STATISTICS_LIST"]
[::std::mem::align_of::<QUIC_WORKER_STATISTICS_LIST>() - 4usize];
["Offset of field: QUIC_WORKER_STATISTICS_LIST::WorkerCount"]
[::std::mem::offset_of!(QUIC_WORKER_STATISTICS_LIST, WorkerCount) - 0usize];
["Offset of field: QUIC_WORKER_STATISTICS_LIST::WorkerStatsSize"]
[::std::mem::offset_of!(QUIC_WORKER_STATISTICS_LIST, WorkerStatsSize) - 4usize];
};
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct QUIC_REGISTRATION_CONFIG {
pub AppName: *const ::std::os::raw::c_char,
pub ExecutionProfile: QUIC_EXECUTION_PROFILE,
Expand Down
1 change: 1 addition & 0 deletions src/test/MsQuicTests.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ void QuicTestGetPerfCounters();
#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES
void QuicTestValidateEncryptDecryptPerfCounters();
void QuicTestConnQueueDelayStatistics();
void QuicTestGetWorkerStatistics();
#endif
void QuicTestVersionSettings();
Comment thread
Copilot marked this conversation as resolved.
void QuicTestValidateParamApi();
Expand Down
Loading
Loading