feat: measure gateway latency (#10)

This commit is contained in:
Slipstream 2025-06-10 15:40:16 -06:00 committed by GitHub
parent 84b4e49c6a
commit c6fb120449
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 30 additions and 3 deletions

View File

@ -365,6 +365,13 @@ class Client:
"""Indicates if the client has successfully connected to the Gateway and is ready.""" """Indicates if the client has successfully connected to the Gateway and is ready."""
return self._ready_event.is_set() return self._ready_event.is_set()
@property
def latency(self) -> Optional[float]:
"""Returns the gateway latency in seconds, or ``None`` if unavailable."""
if self._gateway:
return self._gateway.latency
return None
async def wait_until_ready(self) -> None: async def wait_until_ready(self) -> None:
"""|coro| """|coro|
Waits until the client is fully connected to Discord and the initial state is processed. Waits until the client is fully connected to Discord and the initial state is processed.

View File

@ -63,6 +63,9 @@ class GatewayClient:
self._keep_alive_task: Optional[asyncio.Task] = None self._keep_alive_task: Optional[asyncio.Task] = None
self._receive_task: Optional[asyncio.Task] = None self._receive_task: Optional[asyncio.Task] = None
self._last_heartbeat_sent: Optional[float] = None
self._last_heartbeat_ack: Optional[float] = None
# For zlib decompression # For zlib decompression
self._buffer = bytearray() self._buffer = bytearray()
self._inflator = zlib.decompressobj() self._inflator = zlib.decompressobj()
@ -103,6 +106,7 @@ class GatewayClient:
async def _heartbeat(self): async def _heartbeat(self):
"""Sends a heartbeat to the Gateway.""" """Sends a heartbeat to the Gateway."""
self._last_heartbeat_sent = time.monotonic()
payload = {"op": GatewayOpcode.HEARTBEAT, "d": self._last_sequence} payload = {"op": GatewayOpcode.HEARTBEAT, "d": self._last_sequence}
await self._send_json(payload) await self._send_json(payload)
# print("Sent heartbeat.") # print("Sent heartbeat.")
@ -388,6 +392,7 @@ class GatewayClient:
print("Performing initial IDENTIFY.") print("Performing initial IDENTIFY.")
await self._identify() await self._identify()
elif op == GatewayOpcode.HEARTBEAT_ACK: elif op == GatewayOpcode.HEARTBEAT_ACK:
self._last_heartbeat_ack = time.monotonic()
# print("Received heartbeat ACK.") # print("Received heartbeat ACK.")
pass # Good, connection is alive pass # Good, connection is alive
else: else:
@ -491,3 +496,18 @@ class GatewayClient:
self._session_id = None self._session_id = None
self._last_sequence = None self._last_sequence = None
self._resume_gateway_url = None # This might be re-fetched anyway self._resume_gateway_url = None # This might be re-fetched anyway
@property
def latency(self) -> Optional[float]:
"""Returns the latency between heartbeat and ACK in seconds."""
if self._last_heartbeat_sent is None or self._last_heartbeat_ack is None:
return None
return self._last_heartbeat_ack - self._last_heartbeat_sent
@property
def last_heartbeat_sent(self) -> Optional[float]:
return self._last_heartbeat_sent
@property
def last_heartbeat_ack(self) -> Optional[float]:
return self._last_heartbeat_ack

View File

@ -92,10 +92,10 @@ class TestCog(Cog):
async def ping_hybrid( async def ping_hybrid(
self, ctx: Union[CommandContext, AppCommandContext], arg: Optional[str] = None self, ctx: Union[CommandContext, AppCommandContext], arg: Optional[str] = None
): ):
# latency = self.client.latency # Assuming client has latency attribute from gateway - Commented out for now latency = self.client.latency
latency_ms = "N/A" # Placeholder latency_ms = f"{latency * 1000:.0f}" if latency is not None else "N/A"
hybrid = HybridContext(ctx) hybrid = HybridContext(ctx)
await hybrid.send(f"Pong! Arg: {arg} (Hybrid)") await hybrid.send(f"Pong! {latency_ms}ms. Arg: {arg} (Hybrid)")
@slash_command(name="options_test", description="Tests various option types.") @slash_command(name="options_test", description="Tests various option types.")
async def options_test_slash( async def options_test_slash(