feat(client): track connection time (#84)

This commit is contained in:
Slipstream 2025-06-15 15:20:06 -06:00 committed by GitHub
parent c811e2b578
commit f24c1befac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 85 additions and 29 deletions

View File

@ -18,6 +18,8 @@ from typing import (
) )
from types import ModuleType from types import ModuleType
from datetime import datetime, timedelta
from .http import HTTPClient from .http import HTTPClient
from .gateway import GatewayClient from .gateway import GatewayClient
from .shard_manager import ShardManager from .shard_manager import ShardManager
@ -36,6 +38,7 @@ from .interactions import Interaction, Snowflake
from .error_handler import setup_global_error_handler from .error_handler import setup_global_error_handler
from .voice_client import VoiceClient from .voice_client import VoiceClient
from .models import Activity from .models import Activity
from .utils import utcnow
if TYPE_CHECKING: if TYPE_CHECKING:
from .models import ( from .models import (
@ -168,6 +171,8 @@ class Client:
None # The bot's own user object, populated on READY None # The bot's own user object, populated on READY
) )
self.start_time: Optional[datetime] = None
# Internal Caches # Internal Caches
self._guilds: GuildCache = GuildCache() self._guilds: GuildCache = GuildCache()
self._channels: ChannelCache = ChannelCache() self._channels: ChannelCache = ChannelCache()
@ -244,6 +249,7 @@ class Client:
f"Client connected using {self.shard_count} shards, waiting for READY signal..." f"Client connected using {self.shard_count} shards, waiting for READY signal..."
) )
await self.wait_until_ready() await self.wait_until_ready()
self.start_time = utcnow()
print("Client is READY!") print("Client is READY!")
return return
@ -260,6 +266,7 @@ class Client:
# and its READY handler will set self._ready_event via dispatcher. # and its READY handler will set self._ready_event via dispatcher.
print("Client connected to Gateway, waiting for READY signal...") print("Client connected to Gateway, waiting for READY signal...")
await self.wait_until_ready() # Wait for the READY event from Gateway await self.wait_until_ready() # Wait for the READY event from Gateway
self.start_time = utcnow()
print("Client is READY!") print("Client is READY!")
return # Successfully connected and ready return # Successfully connected and ready
except AuthenticationError: # Non-recoverable by retry here except AuthenticationError: # Non-recoverable by retry here
@ -373,6 +380,7 @@ class Client:
await self._http.close() await self._http.close()
self._ready_event.set() # Ensure any waiters for ready are unblocked self._ready_event.set() # Ensure any waiters for ready are unblocked
self.start_time = None
print("Client closed.") print("Client closed.")
async def __aenter__(self) -> "Client": async def __aenter__(self) -> "Client":
@ -421,6 +429,12 @@ class Client:
latency = getattr(self._gateway, "latency_ms", None) latency = getattr(self._gateway, "latency_ms", None)
return round(latency, 2) if latency is not None else None return round(latency, 2) if latency is not None else None
def uptime(self) -> Optional[timedelta]:
"""Return the duration since the client connected, or ``None`` if not connected."""
if self.start_time is None:
return None
return utcnow() - self.start_time
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

@ -0,0 +1,42 @@
import pytest
from datetime import datetime, timedelta, timezone
from types import SimpleNamespace
from unittest.mock import AsyncMock
from disagreement.client import Client
@pytest.mark.asyncio
async def test_client_records_start_time(monkeypatch):
start = datetime(2020, 1, 1, tzinfo=timezone.utc)
monkeypatch.setattr("disagreement.client.utcnow", lambda: start)
client = Client(token="t")
monkeypatch.setattr(client, "_initialize_gateway", AsyncMock())
client._gateway = SimpleNamespace(connect=AsyncMock())
monkeypatch.setattr(client, "wait_until_ready", AsyncMock())
assert client.start_time is None
await client.connect()
assert client.start_time == start
@pytest.mark.asyncio
async def test_client_uptime(monkeypatch):
start = datetime(2020, 1, 1, tzinfo=timezone.utc)
end = start + timedelta(seconds=5)
times = [start, end]
def fake_now():
return times.pop(0)
monkeypatch.setattr("disagreement.client.utcnow", fake_now)
client = Client(token="t")
monkeypatch.setattr(client, "_initialize_gateway", AsyncMock())
client._gateway = SimpleNamespace(connect=AsyncMock())
monkeypatch.setattr(client, "wait_until_ready", AsyncMock())
await client.connect()
assert client.uptime() == timedelta(seconds=5)