[client/idd] input: add transport diagnostics

This commit is contained in:
Geoffrey McRae
2026-08-09 01:55:45 +10:00
parent 5b90384123
commit 487920ada3
9 changed files with 634 additions and 34 deletions

View File

@@ -35,6 +35,10 @@ bool CInputPipeServer::Init()
DeInit();
m_state.store(0, std::memory_order_release);
m_performanceFrequency.QuadPart = 0;
if (!QueryPerformanceFrequency(&m_performanceFrequency))
m_performanceFrequency.QuadPart = 0;
m_lastStatistics = GetTickCount64();
m_stopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
m_queueEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if (!m_stopEvent || !m_queueEvent)
@@ -98,6 +102,19 @@ void CInputPipeServer::DeInit()
m_absoluteValid = false;
m_relativeButtons = 0;
m_absoluteButtons = 0;
m_statEnqueued = 0;
m_statRelativeCoalesced = 0;
m_statAbsoluteCoalesced = 0;
m_statResyncs = 0;
m_statResyncDiscarded = 0;
m_statQueueHighWater = 0;
m_statSent = 0;
m_statStale = 0;
m_statWriteFailed = 0;
m_statSlowWrites = 0;
m_statWriteTicks = 0;
m_statMaxWriteTicks = 0;
}
bool CInputPipeServer::QueueLocked(
@@ -133,6 +150,7 @@ bool CInputPipeServer::QueueLocked(
{
tail.payload.mouseRelative.deltaX = static_cast<int32_t>(x);
tail.payload.mouseRelative.deltaY = static_cast<int32_t>(y);
++m_statRelativeCoalesced;
return true;
}
}
@@ -145,6 +163,7 @@ bool CInputPipeServer::QueueLocked(
{
tail.payload.mouseAbsolute.x = payload.mouseAbsolute.x;
tail.payload.mouseAbsolute.y = payload.mouseAbsolute.y;
++m_statAbsoluteCoalesced;
return true;
}
}
@@ -169,6 +188,9 @@ bool CInputPipeServer::QueueRawLocked(
m_queue[index].payload = payload;
m_queue[index].pureMotion = pureMotion;
++m_queueCount;
++m_statEnqueued;
if (m_queueCount > m_statQueueHighWater)
m_statQueueHighWater = m_queueCount;
SetEvent(m_queueEvent);
return true;
}
@@ -201,6 +223,8 @@ bool CInputPipeServer::QueueResetLocked()
void CInputPipeServer::ResyncLocked()
{
++m_statResyncs;
m_statResyncDiscarded += m_queueCount;
m_queueHead = 0;
m_queueCount = 0;
uint64_t state = m_state.load(std::memory_order_relaxed);
@@ -375,6 +399,9 @@ bool CInputPipeServer::Send(const QueueItem& item)
bool current;
bool sent = true;
bool timed = false;
LARGE_INTEGER start = {};
LARGE_INTEGER end = {};
{
CSRWSharedLock lock(&m_connectionLock);
const uint64_t state = m_state.load(std::memory_order_acquire);
@@ -382,10 +409,33 @@ bool CInputPipeServer::Send(const QueueItem& item)
if (current)
{
message.sequence = ++m_sequence;
timed = QueryPerformanceCounter(&start) != FALSE;
sent = m_endpoint.Send(&message, sizeof(message));
timed = timed && QueryPerformanceCounter(&end) != FALSE;
}
}
if (!current)
++m_statStale;
else
{
if (timed)
{
const int64_t ticks = end.QuadPart - start.QuadPart;
m_statWriteTicks += ticks;
if (ticks > m_statMaxWriteTicks)
m_statMaxWriteTicks = ticks;
if (m_performanceFrequency.QuadPart &&
ticks * 1000 >= m_performanceFrequency.QuadPart)
++m_statSlowWrites;
}
if (sent)
++m_statSent;
else
++m_statWriteFailed;
}
if (!sent)
{
Invalidate(item.state, true);
@@ -394,6 +444,78 @@ bool CInputPipeServer::Send(const QueueItem& item)
return current;
}
void CInputPipeServer::LogStatistics()
{
const ULONGLONG now = GetTickCount64();
if (now - m_lastStatistics < STATISTICS_INTERVAL_MS)
return;
uint64_t enqueued;
uint64_t relativeCoalesced;
uint64_t absoluteCoalesced;
uint64_t resyncs;
uint64_t resyncDiscarded;
size_t queueHighWater;
{
CSRWExclusiveLock lock(&m_queueLock);
enqueued = m_statEnqueued;
relativeCoalesced = m_statRelativeCoalesced;
absoluteCoalesced = m_statAbsoluteCoalesced;
resyncs = m_statResyncs;
resyncDiscarded = m_statResyncDiscarded;
queueHighWater = m_statQueueHighWater;
m_statEnqueued = 0;
m_statRelativeCoalesced = 0;
m_statAbsoluteCoalesced = 0;
m_statResyncs = 0;
m_statResyncDiscarded = 0;
m_statQueueHighWater = m_queueCount;
}
const uint64_t sent = m_statSent;
const uint64_t stale = m_statStale;
const uint64_t writeFailed = m_statWriteFailed;
const uint64_t slowWrites = m_statSlowWrites;
const int64_t writeTicks = m_statWriteTicks;
const int64_t maxWriteTicks = m_statMaxWriteTicks;
m_statSent = 0;
m_statStale = 0;
m_statWriteFailed = 0;
m_statSlowWrites = 0;
m_statWriteTicks = 0;
m_statMaxWriteTicks = 0;
m_lastStatistics = now;
if (!(enqueued || relativeCoalesced || absoluteCoalesced || resyncs ||
resyncDiscarded || sent || stale || writeFailed || slowWrites))
return;
const double writeMs = m_performanceFrequency.QuadPart ?
static_cast<double>(writeTicks) * 1000.0 /
m_performanceFrequency.QuadPart : 0.0;
const double maxWriteMs = m_performanceFrequency.QuadPart ?
static_cast<double>(maxWriteTicks) * 1000.0 /
m_performanceFrequency.QuadPart : 0.0;
DEBUG_TRACE("LGInput pipe: %llu queued, %llu sent, %llu stale, "
"%llu failed; %llu relative and %llu absolute coalesced, "
"%llu resyncs discarded %llu, peak %zu; %.3f ms writes, "
"%.3f ms max, %llu slow",
static_cast<unsigned long long>(enqueued),
static_cast<unsigned long long>(sent),
static_cast<unsigned long long>(stale),
static_cast<unsigned long long>(writeFailed),
static_cast<unsigned long long>(relativeCoalesced),
static_cast<unsigned long long>(absoluteCoalesced),
static_cast<unsigned long long>(resyncs),
static_cast<unsigned long long>(resyncDiscarded),
queueHighWater,
writeMs,
maxWriteMs,
static_cast<unsigned long long>(slowWrites));
}
void CInputPipeServer::Invalidate(uint64_t state, bool requireMatch)
{
CSRWExclusiveLock connectionLock(&m_connectionLock);
@@ -435,8 +557,12 @@ void CInputPipeServer::Thread()
QueueItem item = {};
while (Pop(item))
if (!Send(item))
{
const bool sent = Send(item);
if (!sent)
break;
}
LogStatistics();
}
Invalidate(0, false);

View File

@@ -33,6 +33,7 @@ class CInputPipeServer : public IInputSink, private IPipeEndpointHandler
private:
static constexpr size_t QUEUE_LENGTH = 128;
static constexpr size_t MOTION_COALESCE_THRESHOLD = QUEUE_LENGTH / 2;
static constexpr DWORD STATISTICS_INTERVAL_MS = 5000;
struct QueueItem
{
@@ -72,6 +73,22 @@ private:
uint32_t m_relativeButtons = 0;
uint32_t m_absoluteButtons = 0;
uint64_t m_statEnqueued = 0;
uint64_t m_statRelativeCoalesced = 0;
uint64_t m_statAbsoluteCoalesced = 0;
uint64_t m_statResyncs = 0;
uint64_t m_statResyncDiscarded = 0;
size_t m_statQueueHighWater = 0;
uint64_t m_statSent = 0;
uint64_t m_statStale = 0;
uint64_t m_statWriteFailed = 0;
uint64_t m_statSlowWrites = 0;
int64_t m_statWriteTicks = 0;
int64_t m_statMaxWriteTicks = 0;
ULONGLONG m_lastStatistics = 0;
LARGE_INTEGER m_performanceFrequency = {};
bool QueueLocked(LGInputPipeMessageType type,
const KVMFRInputPayload& payload, bool pureMotion);
bool QueueRawLocked(LGInputPipeMessageType type,
@@ -81,6 +98,7 @@ private:
bool Pop(QueueItem& item);
bool Send(const QueueItem& item);
void Invalidate(uint64_t state, bool requireMatch);
void LogStatistics();
void Thread();
static DWORD WINAPI ThreadProc(void * context);

View File

@@ -289,19 +289,31 @@ bool CLGMPInputTransport::IsOwner(
bool CLGMPInputTransport::Claim(
uint32_t sourceClientID, const KVMFRInputMessage& message)
{
if (message.sequence != 1 || !m_sink)
if (message.sequence != 1)
{
++m_statistics.sequenceErrors;
return false;
}
if (!m_sink)
{
++m_statistics.deliveryFailures;
return false;
}
const uint64_t sinkState = m_sink->GetState();
if (!(sinkState & 1) || sinkState != m_sinkState ||
!m_sink->Reset() || m_sink->GetState() != sinkState)
{
++m_statistics.deliveryFailures;
return false;
}
m_ownerClientID = sourceClientID;
m_ownerGeneration = message.generation;
m_ownerSequence = message.sequence;
RenewLease();
m_statusDirty = true;
++m_statistics.claims;
DEBUG_INFO("Input owner %u generation %u acquired",
m_ownerClientID, m_ownerGeneration);
return true;
@@ -328,6 +340,7 @@ void CLGMPInputTransport::ReleaseOwner(
m_ownerSequence = 0;
m_ownerDeadline = 0;
m_statusDirty = true;
++m_statistics.releases;
DEBUG_INFO("Input owner %u generation %u released (%s)",
clientID, generation, reason);
}
@@ -420,6 +433,7 @@ bool CLGMPInputTransport::ProcessMessage(
if (!message.generation || !message.sequence || message.reserved ||
!ValidatePayload(message))
{
++m_statistics.malformedMessage;
if (owner)
ReleaseOwner(true, "invalid input message");
return false;
@@ -430,10 +444,14 @@ bool CLGMPInputTransport::ProcessMessage(
if (m_ownerClientID)
{
if (!owner)
{
++m_statistics.nonOwner;
return true;
}
if (message.sequence == 1 && m_ownerSequence == 1)
return true;
++m_statistics.sequenceErrors;
ReleaseOwner(true, "sequence discontinuity");
return false;
}
@@ -441,18 +459,23 @@ bool CLGMPInputTransport::ProcessMessage(
}
if (!owner)
{
++m_statistics.nonOwner;
return true;
}
uint32_t expectedSequence = m_ownerSequence + 1;
if (!expectedSequence)
expectedSequence = 1;
if (message.sequence != expectedSequence)
{
++m_statistics.sequenceErrors;
ReleaseOwner(true, "sequence discontinuity");
return false;
}
bool accepted = false;
bool accepted = false;
bool inputReport = false;
switch (message.type)
{
case KVMFR_INPUT_MESSAGE_RELEASE:
@@ -468,6 +491,7 @@ bool CLGMPInputTransport::ProcessMessage(
break;
case KVMFR_INPUT_MESSAGE_MOUSE_RELATIVE:
inputReport = true;
accepted = m_sink && m_sink->SendMouseRelative(
message.payload.mouseRelative.deltaX,
message.payload.mouseRelative.deltaY,
@@ -476,6 +500,7 @@ bool CLGMPInputTransport::ProcessMessage(
break;
case KVMFR_INPUT_MESSAGE_MOUSE_ABSOLUTE:
inputReport = true;
accepted = m_sink && m_sink->SendMouseAbsolute(
message.payload.mouseAbsolute.x,
message.payload.mouseAbsolute.y,
@@ -484,6 +509,7 @@ bool CLGMPInputTransport::ProcessMessage(
break;
case KVMFR_INPUT_MESSAGE_KEYBOARD:
inputReport = true;
accepted = m_sink && m_sink->SendKeyboard(
message.payload.keyboard.modifiers,
message.payload.keyboard.keys);
@@ -495,6 +521,7 @@ bool CLGMPInputTransport::ProcessMessage(
if (!accepted)
{
++m_statistics.deliveryFailures;
ReleaseOwner(true, "input delivery failed");
return false;
}
@@ -509,13 +536,16 @@ bool CLGMPInputTransport::ProcessMessage(
m_ownerSequence = message.sequence;
RenewLease();
if (inputReport)
++m_statistics.reports;
return true;
}
bool CLGMPInputTransport::DrainMessages()
{
bool received = false;
for (unsigned count = 0; count < 256; ++count)
unsigned count = 0;
for (; count < 256; ++count)
{
uint8_t data[LGMP_MSGS_SIZE] = {};
size_t size = 0;
@@ -532,9 +562,11 @@ bool CLGMPInputTransport::DrainMessages()
}
received = true;
++m_statistics.messages;
if (size != sizeof(KVMFRInputMessage))
{
DEBUG_WARN("Ignoring invalid KVMFR input message size");
++m_statistics.malformedSize;
if (sourceClientID == m_ownerClientID)
ReleaseOwner(true, "invalid input message");
}
@@ -547,9 +579,50 @@ bool CLGMPInputTransport::DrainMessages()
lgmpHostAckData(m_queue);
}
if (count > m_statistics.maxDrain)
m_statistics.maxDrain = count;
if (count == 256)
++m_statistics.drainLimit;
return received;
}
void CLGMPInputTransport::LogStatistics(ULONGLONG now)
{
if (!m_statistics.lastLog)
{
m_statistics.lastLog = now;
return;
}
if (now - m_statistics.lastLog < LOG_INTERVAL_MS)
return;
const Statistics statistics = m_statistics;
m_statistics = {};
m_statistics.lastLog = now;
if (!statistics.messages && !statistics.claims &&
!statistics.releases && !statistics.deliveryFailures)
return;
const double elapsed =
static_cast<double>(now - statistics.lastLog) / 1000.0;
DEBUG_TRACE("LGMP input host: %.1f msg/s, %llu reports, "
"drain max %u, %llu limit; %llu bad size, %llu malformed, "
"%llu sequence, %llu non-owner, %llu delivery failures; "
"%llu claims, %llu releases",
statistics.messages / elapsed,
static_cast<unsigned long long>(statistics.reports),
statistics.maxDrain,
static_cast<unsigned long long>(statistics.drainLimit),
static_cast<unsigned long long>(statistics.malformedSize),
static_cast<unsigned long long>(statistics.malformedMessage),
static_cast<unsigned long long>(statistics.sequenceErrors),
static_cast<unsigned long long>(statistics.nonOwner),
static_cast<unsigned long long>(statistics.deliveryFailures),
static_cast<unsigned long long>(statistics.claims),
static_cast<unsigned long long>(statistics.releases));
}
DWORD CALLBACK CLGMPInputTransport::ThreadProc(void * context)
{
static_cast<CLGMPInputTransport *>(context)->Thread();
@@ -567,15 +640,20 @@ void CLGMPInputTransport::Thread()
GetLastError());
ULONGLONG activeUntil = 0;
m_statistics = {};
m_statistics.lastLog = GetTickCount64();
const HANDLE waitHandles[] = { m_stopEvent, m_pollTimer };
for (;;)
{
CheckOwner();
if (DrainMessages())
activeUntil = GetTickCount64() + ACTIVE_POLL_MS;
const bool received = DrainMessages();
PublishStatus();
const ULONGLONG now = GetTickCount64();
if (received)
activeUntil = now + ACTIVE_POLL_MS;
LogStatistics(now);
const bool active = GetTickCount64() < activeUntil;
const bool active = now < activeUntil;
if (!ArmPollTimer(m_pollTimer, active))
{
DEBUG_ERROR_HR(GetLastError(), "Failed to arm LGMP input timer");

View File

@@ -40,6 +40,23 @@ class CLGMPInputTransport final : public IInputTransport
private:
static constexpr ULONGLONG OWNER_LEASE_MS = 500;
static constexpr ULONGLONG ACTIVE_POLL_MS = 50;
static constexpr ULONGLONG LOG_INTERVAL_MS = 5000;
struct Statistics
{
ULONGLONG lastLog;
uint64_t messages;
uint64_t reports;
uint64_t drainLimit;
uint64_t malformedSize;
uint64_t malformedMessage;
uint64_t sequenceErrors;
uint64_t nonOwner;
uint64_t deliveryFailures;
uint64_t claims;
uint64_t releases;
unsigned maxDrain;
};
CLGMPHost& m_host;
@@ -52,19 +69,21 @@ private:
HANDLE m_pollTimer = nullptr;
HANDLE m_thread = nullptr;
uint32_t m_ownerClientID = 0;
uint32_t m_ownerGeneration = 0;
uint32_t m_ownerSequence = 0;
ULONGLONG m_ownerDeadline = 0;
uint64_t m_sinkState = 0;
uint32_t m_endpointGeneration = 0;
uint32_t m_statusSerial = 0;
bool m_statusDirty = false;
uint32_t m_ownerClientID = 0;
uint32_t m_ownerGeneration = 0;
uint32_t m_ownerSequence = 0;
ULONGLONG m_ownerDeadline = 0;
uint64_t m_sinkState = 0;
uint32_t m_endpointGeneration = 0;
uint32_t m_statusSerial = 0;
bool m_statusDirty = false;
Statistics m_statistics = {};
bool Initialize();
void DeInit();
void UpdateSinkState(uint64_t state);
void PublishStatus();
void LogStatistics(ULONGLONG now);
bool DrainMessages();
bool ProcessMessage(uint32_t sourceClientID,
const KVMFRInputMessage& message);

View File

@@ -44,6 +44,20 @@ struct HIDQueuedReport
size_t size;
};
struct HIDStatistics
{
uint64_t direct;
uint64_t queued;
uint64_t relativeCoalesced;
uint64_t absoluteCoalesced;
uint64_t staleAbsoluteCompacted;
uint64_t keyboardDuplicates;
uint64_t consumerDuplicates;
uint64_t overflows;
uint64_t resetDiscarded;
size_t queueHighWater;
};
struct HIDDeviceContext
{
WDFQUEUE reportQueue;
@@ -63,6 +77,7 @@ struct HIDDeviceContext
bool absoluteValid;
uint16_t absoluteX;
uint16_t absoluteY;
HIDStatistics statistics;
HIDQueuedReport reports[REPORT_QUEUE_LENGTH];
CInputPipeClient * inputPipe;
};
@@ -199,6 +214,7 @@ static bool CompactStaleMotion(
if (superseded)
{
RemoveQueuedReport(context, i);
++context->statistics.staleAbsoluteCompacted;
return true;
}
}
@@ -236,6 +252,7 @@ static NTSTATUS QueueReport(
{
previous->x = static_cast<int16_t>(x);
previous->y = static_cast<int16_t>(y);
++context->statistics.relativeCoalesced;
return STATUS_SUCCESS;
}
}
@@ -250,19 +267,26 @@ static NTSTATUS QueueReport(
{
CopyMemory(tail.data, data, size);
tail.size = size;
++context->statistics.absoluteCoalesced;
return STATUS_SUCCESS;
}
}
else if (reportId == HID_REPORT_ID_KEYBOARD &&
tail.size == size && memcmp(tail.data, data, size) == 0)
{
++context->statistics.keyboardDuplicates;
return STATUS_SUCCESS;
}
}
}
if (context->reportCount == REPORT_QUEUE_LENGTH)
{
if (!CompactStaleMotion(context, reportId))
{
++context->statistics.overflows;
return STATUS_BUFFER_OVERFLOW;
}
}
const size_t index =
@@ -272,6 +296,9 @@ static NTSTATUS QueueReport(
report->size = size;
report->pureMotion = pureMotion;
++context->reportCount;
++context->statistics.queued;
if (context->reportCount > context->statistics.queueHighWater)
context->statistics.queueHighWater = context->reportCount;
return STATUS_SUCCESS;
}
@@ -454,7 +481,10 @@ NTSTATUS CHIDDevice::SubmitReport(
if (reportId == HID_REPORT_ID_CONSUMER &&
context->consumerUsage ==
static_cast<const HIDConsumerReport *>(report)->usage)
{
++context->statistics.consumerDuplicates;
return STATUS_SUCCESS;
}
bool pureMotion = false;
switch (reportId)
@@ -493,7 +523,11 @@ NTSTATUS CHIDDevice::SubmitReport(
status = QueueReport(context, report, size, pureMotion);
}
else if (NT_SUCCESS(status))
{
status = CopyToRequest(request, report, size);
if (NT_SUCCESS(status))
++context->statistics.direct;
}
if (NT_SUCCESS(status))
{
@@ -553,6 +587,7 @@ NTSTATUS CHIDDevice::ResetReports()
absoluteValid = context->absoluteValid;
absoluteX = context->absoluteX;
absoluteY = context->absoluteY;
context->statistics.resetDiscarded += context->reportCount;
context->reportHead = 0;
context->reportCount = 0;
context->mouseMode = 0;
@@ -601,6 +636,44 @@ NTSTATUS CHIDDevice::ResetReports()
return status;
}
void CHIDDevice::LogStatistics()
{
HIDStatistics statistics = {};
{
CSRWSharedLock deviceLock(&s_deviceLock);
HIDDeviceContext * context = s_device;
if (!context)
return;
CSRWExclusiveLock reportLock(&context->reportLock);
statistics = context->statistics;
context->statistics = {};
context->statistics.queueHighWater = context->reportCount;
}
if (!(statistics.direct || statistics.queued ||
statistics.relativeCoalesced || statistics.absoluteCoalesced ||
statistics.staleAbsoluteCompacted ||
statistics.keyboardDuplicates || statistics.consumerDuplicates ||
statistics.overflows || statistics.resetDiscarded))
return;
DEBUG_TRACE("HID reports: %llu direct, %llu queued, peak %zu; "
"%llu relative and %llu absolute coalesced, %llu stale absolute "
"compacted, %llu keyboard and %llu consumer duplicates, "
"%llu overflows, %llu discarded on reset",
static_cast<unsigned long long>(statistics.direct),
static_cast<unsigned long long>(statistics.queued),
statistics.queueHighWater,
static_cast<unsigned long long>(statistics.relativeCoalesced),
static_cast<unsigned long long>(statistics.absoluteCoalesced),
static_cast<unsigned long long>(statistics.staleAbsoluteCompacted),
static_cast<unsigned long long>(statistics.keyboardDuplicates),
static_cast<unsigned long long>(statistics.consumerDuplicates),
static_cast<unsigned long long>(statistics.overflows),
static_cast<unsigned long long>(statistics.resetDiscarded));
}
VOID HIDEvtIoDeviceControl(
_In_ WDFQUEUE queue,
_In_ WDFREQUEST request,

View File

@@ -31,4 +31,5 @@ public:
_In_reads_bytes_(size) const void * report,
_In_ size_t size);
static NTSTATUS ResetReports();
static void LogStatistics();
};

View File

@@ -37,7 +37,13 @@ static constexpr uint16_t HID_CONSUMER_USAGE_VOLUME_DOWN = 0xea;
bool CInputPipeClient::Start()
{
m_lastSequence = 0;
m_endpoint.Stop();
m_lastSequence = 0;
m_statReceived = 0;
m_statMalformed = 0;
m_statSequenceResets = 0;
m_statSubmitFailed = 0;
m_lastStatistics = GetTickCount64();
m_endpoint.SetHandler(this);
return m_endpoint.Start(
LG_INPUT_PIPE_NAME,
@@ -51,7 +57,10 @@ void CInputPipeClient::Stop()
m_endpoint.Stop();
m_lastSequence = 0;
if (wasRunning)
{
LogStatistics(true);
CHIDDevice::ResetReports();
}
}
void CInputPipeClient::OnPipeConnected()
@@ -63,6 +72,7 @@ void CInputPipeClient::OnPipeConnected()
void CInputPipeClient::OnPipeDisconnected()
{
m_lastSequence = 0;
LogStatistics(true);
CHIDDevice::ResetReports();
DEBUG_INFO("Disconnected from the LGIdd input transport; reconnecting");
}
@@ -72,7 +82,11 @@ bool CInputPipeClient::OnPipeMessage(
size_t size)
{
if (size != sizeof(LGInputPipeMessage))
{
++m_statMalformed;
DEBUG_WARN("Received a malformed LGInput pipe message");
return false;
}
const LGInputPipeMessage & message =
*static_cast<const LGInputPipeMessage *>(frame);
@@ -80,16 +94,22 @@ bool CInputPipeClient::OnPipeMessage(
message.version != LG_INPUT_PIPE_VERSION ||
!message.payloadSize ||
message.payloadSize > sizeof(message.payload))
{
++m_statMalformed;
DEBUG_WARN("Received a malformed LGInput pipe message");
return false;
}
if (!message.sequence ||
(m_lastSequence && message.sequence != m_lastSequence + 1))
{
++m_statSequenceResets;
DEBUG_WARN("LGInput pipe report sequence changed unexpectedly");
CHIDDevice::ResetReports();
return false;
}
bool handled = false;
const uint64_t submitFailures = m_statSubmitFailed;
switch (message.type)
{
case LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE:
@@ -107,13 +127,24 @@ bool CInputPipeClient::OnPipeMessage(
break;
default:
++m_statMalformed;
DEBUG_WARN("Received an unknown LGInput pipe message");
return false;
}
if (!handled)
{
if (m_statSubmitFailed == submitFailures)
{
++m_statMalformed;
DEBUG_WARN("Received a malformed LGInput pipe payload");
}
return false;
}
m_lastSequence = message.sequence;
++m_statReceived;
LogStatistics(false);
return true;
}
@@ -265,19 +296,53 @@ bool CInputPipeClient::SubmitReport(
const NTSTATUS status = CHIDDevice::SubmitReport(report, size);
if (status == STATUS_INVALID_PARAMETER)
{
++m_statSubmitFailed;
return false;
}
if (status == STATUS_BUFFER_OVERFLOW)
{
++m_statSubmitFailed;
DEBUG_WARN("LGInput HID report queue overflowed; resetting input state");
CHIDDevice::ResetReports();
return false;
}
if (!NT_SUCCESS(status))
{
++m_statSubmitFailed;
if (status != STATUS_DEVICE_NOT_READY)
DEBUG_WARN_HR(status, "Failed to submit an LGInput HID report");
return false;
}
return true;
}
void CInputPipeClient::LogStatistics(bool force)
{
const ULONGLONG now = GetTickCount64();
if (!force && now - m_lastStatistics < STATISTICS_INTERVAL_MS)
return;
const uint64_t received = m_statReceived;
const uint64_t malformed = m_statMalformed;
const uint64_t sequenceResets = m_statSequenceResets;
const uint64_t submitFailed = m_statSubmitFailed;
m_statReceived = 0;
m_statMalformed = 0;
m_statSequenceResets = 0;
m_statSubmitFailed = 0;
m_lastStatistics = now;
if (received || malformed || sequenceResets || submitFailed)
{
DEBUG_TRACE("LGInput pipe receive: %llu reports, %llu malformed, "
"%llu sequence resets, %llu HID submit failures",
static_cast<unsigned long long>(received),
static_cast<unsigned long long>(malformed),
static_cast<unsigned long long>(sequenceResets),
static_cast<unsigned long long>(submitFailed));
}
CHIDDevice::LogStatistics();
}

View File

@@ -34,6 +34,8 @@ public:
void Stop();
private:
static constexpr ULONGLONG STATISTICS_INTERVAL_MS = 5000;
void OnPipeConnected() override;
void OnPipeDisconnected() override;
bool OnPipeMessage(const void * message, size_t size) override;
@@ -41,7 +43,13 @@ private:
bool HandleMouseAbsolute(const void * payload, size_t size);
bool HandleKeyboard(const void * payload, size_t size);
bool SubmitReport(const void * report, size_t size);
void LogStatistics(bool force);
CPipeEndpoint m_endpoint;
uint64_t m_lastSequence = 0;
uint64_t m_lastSequence = 0;
uint64_t m_statReceived = 0;
uint64_t m_statMalformed = 0;
uint64_t m_statSequenceResets = 0;
uint64_t m_statSubmitFailed = 0;
ULONGLONG m_lastStatistics = 0;
};